Merge pull request #312 from mposolda/ldap
Refactoring of AuthenticationProvider SPI. Add Ldap settings into admin console
This commit is contained in:
commit
7d7b9e5b73
40 changed files with 404 additions and 462 deletions
|
@ -129,7 +129,7 @@ module.config([ '$routeProvider', function($routeProvider) {
|
||||||
return RealmLoader();
|
return RealmLoader();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
controller : 'RealmSMTPSettingsCtrl'
|
controller : 'RealmLdapSettingsCtrl'
|
||||||
})
|
})
|
||||||
.when('/create/user/:realm', {
|
.when('/create/user/:realm', {
|
||||||
templateUrl : 'partials/user-detail.html',
|
templateUrl : 'partials/user-detail.html',
|
||||||
|
|
|
@ -886,3 +886,32 @@ module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, real
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.controller('RealmLdapSettingsCtrl', function($scope, Realm, realm, $location, Notifications) {
|
||||||
|
console.log('RealmLdapSettingsCtrl');
|
||||||
|
|
||||||
|
$scope.realm = realm;
|
||||||
|
|
||||||
|
var oldCopy = angular.copy($scope.realm);
|
||||||
|
$scope.changed = false;
|
||||||
|
|
||||||
|
$scope.$watch('realm', function() {
|
||||||
|
if (!angular.equals($scope.realm, oldCopy)) {
|
||||||
|
$scope.changed = true;
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
$scope.save = function() {
|
||||||
|
var realmCopy = angular.copy($scope.realm);
|
||||||
|
$scope.changed = false;
|
||||||
|
Realm.update(realmCopy, function () {
|
||||||
|
$location.url("/realms/" + realm.realm + "/ldap-settings");
|
||||||
|
Notifications.success("Your changes have been saved to the realm.");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.reset = function() {
|
||||||
|
$scope.realm = angular.copy(oldCopy);
|
||||||
|
$scope.changed = false;
|
||||||
|
};
|
||||||
|
});
|
|
@ -7,60 +7,39 @@
|
||||||
<li><a href="#/realms/{{realm.realm}}">Settings</a></li>
|
<li><a href="#/realms/{{realm.realm}}">Settings</a></li>
|
||||||
<li class="active">Ldap Configuration</li>
|
<li class="active">Ldap Configuration</li>
|
||||||
</ol>
|
</ol>
|
||||||
<h2><span>{{realm.realm}}</span> Email Server Settings</h2>
|
<h2><span>{{realm.realm}}</span> Ldap Server Settings</h2>
|
||||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||||
<span class="fieldset-notice"><span class="required">*</span> Required fields</span>
|
<span class="fieldset-notice"><span class="required">*</span> Required fields</span>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend><span class="text">Required Settings</span></legend>
|
<legend><span class="text">Required Settings</span></legend>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-sm-2 control-label" for="smtpHost">Host <span class="required">*</span></label>
|
<label class="col-sm-2 control-label" for="ldapConnectionUrl">Connection URL <span class="required">*</span></label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input class="form-control" id="smtpHost" type="text" ng-model="realm.smtpServer.host" placeholder="SMTP Host" required>
|
<input class="form-control" id="ldapConnectionUrl" type="text" ng-model="realm.ldapServer.connectionUrl" placeholder="LDAP connection URL" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-sm-2 control-label" for="smtpPort">Port <span class="required">*</span></label>
|
<label class="col-sm-2 control-label" for="ldapBaseDn">Base DN <span class="required">*</span></label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input class="form-control" id="smtpPort" type="number" ng-model="realm.smtpServer.port" placeholder="SMTP Port (defaults to 25)" required>
|
<input class="form-control" id="ldapBaseDn" type="text" ng-model="realm.ldapServer.baseDn" placeholder="LDAP Base DN" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-sm-2 control-label" for="smtpFrom">From <span class="required">*</span></label>
|
<label class="col-sm-2 control-label" for="ldapUserDnSuffix">User DN Suffix <span class="required">*</span></label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input class="form-control" id="smtpFrom" type="email" ng-model="realm.smtpServer.from" placeholder="Sender Email Address" required>
|
<input class="form-control" id="ldapUserDnSuffix" type="text" ng-model="realm.ldapServer.userDnSuffix" placeholder="LDAP User DN Suffix" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-sm-2 control-label" for="smtpSSL">Enable SSL</label>
|
<label class="col-sm-2 control-label" for="ldapBindDn">Bind DN <span class="required">*</span></label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input ng-model="realm.smtpServer.ssl" name="smtpSSL" id="smtpSSL" onoffswitch />
|
<input class="form-control" id="ldapBindDn" type="text" ng-model="realm.ldapServer.bindDn" placeholder="LDAP Bind DN" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label class="col-sm-2 control-label" for="smtpStartTLS">Enable StartTLS</label>
|
<label class="col-sm-2 control-label" for="ldapBindCredential">Bind Credential <span class="required">*</span></label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input ng-model="realm.smtpServer.starttls" name="smtpStartTLS" id="smtpStartTLS" onoffswitch />
|
<input class="form-control" id="ldapBindCredential" type="text" ng-model="realm.ldapServer.bindCredential" placeholder="LDAP Bind Credentials" required>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
|
||||||
<legend><span class="text">Authentication</span></legend>
|
|
||||||
<div class="form-group clearfix">
|
|
||||||
<label class="col-sm-2 control-label" for="smtpAuth">Enable Authentication</label>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<input ng-model="realm.smtpServer.auth" name="smtpAuth" id="smtpAuth" onoffswitch />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group clearfix" data-ng-show="realm.smtpServer.auth">
|
|
||||||
<label class="col-sm-2 control-label" for="smtpUsername">Username <span class="required" ng-show="realm.smtpServer.auth">*</span></label>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<input class="form-control" id="smtpUsername" type="text" ng-model="realm.smtpServer.user" placeholder="Login Username" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group clearfix" data-ng-show="realm.smtpServer.auth">
|
|
||||||
<label class="col-sm-2 control-label" for="smtpPassword">Password <span class="required" ng-show="realm.smtpServer.auth">*</span></label>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<input class="form-control" id="smtpPassword" type="password" ng-model="realm.smtpServer.password" placeholder="Login Password" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<ul data-ng-hide="createRealm">
|
<ul data-ng-hide="createRealm">
|
||||||
<li data-ng-show="access.viewRealm" data-ng-class="((!path[2] || path[1] == 'role' || path[2] == 'roles' || path[2] == 'token-settings' ||
|
<li data-ng-show="access.viewRealm" data-ng-class="((!path[2] || path[1] == 'role' || path[2] == 'roles' || path[2] == 'token-settings' ||
|
||||||
path[2] == 'social-settings' || path[2] == 'required-credentials' || path[2] == 'default-roles' || path[2] == 'registration-settings' ||
|
path[2] == 'social-settings' || path[2] == 'required-credentials' || path[2] == 'default-roles' || path[2] == 'registration-settings' ||
|
||||||
path[2] == 'keys-settings' || path[2] == 'smtp-settings') && path[3] != 'applications') && 'active'"><a href="#/realms/{{realm.realm}}">Settings</a></li>
|
path[2] == 'keys-settings' || path[2] == 'smtp-settings' || path[2] == 'ldap-settings') && path[3] != 'applications') && 'active'"><a href="#/realms/{{realm.realm}}">Settings</a></li>
|
||||||
<li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users">Users</a>
|
<li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users">Users</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-ng-show="access.viewApplications" data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
<li data-ng-show="access.viewApplications" data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
||||||
|
|
|
@ -7,4 +7,5 @@
|
||||||
<li ng-class="{active: path[2] == 'token-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/token-settings">Token</a></li>
|
<li ng-class="{active: path[2] == 'token-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/token-settings">Token</a></li>
|
||||||
<li ng-class="{active: path[2] == 'keys-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/keys-settings">Keys</a></li>
|
<li ng-class="{active: path[2] == 'keys-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/keys-settings">Keys</a></li>
|
||||||
<li ng-class="{active: path[2] == 'smtp-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/smtp-settings">Email</a></li>
|
<li ng-class="{active: path[2] == 'smtp-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/smtp-settings">Email</a></li>
|
||||||
|
<li ng-class="{active: path[2] == 'ldap-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/ldap-settings">Ldap</a></li>
|
||||||
</ul>
|
</ul>
|
|
@ -1,39 +0,0 @@
|
||||||
package org.keycloak.representations.idm;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class AuthenticationMappingRepresentation {
|
|
||||||
|
|
||||||
protected String self; // link
|
|
||||||
protected String username;
|
|
||||||
protected List<AuthenticationLinkRepresentation> authenticationLinks;
|
|
||||||
|
|
||||||
public String getSelf() {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelf(String self) {
|
|
||||||
this.self = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsername(String username) {
|
|
||||||
this.username = username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<AuthenticationLinkRepresentation> getAuthenticationLinks() {
|
|
||||||
return authenticationLinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAuthenticationLinks(List<AuthenticationLinkRepresentation> authenticationLinks) {
|
|
||||||
this.authenticationLinks = authenticationLinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -39,7 +39,6 @@ public class RealmRepresentation {
|
||||||
protected Map<String, List<UserRoleMappingRepresentation>> applicationRoleMappings;
|
protected Map<String, List<UserRoleMappingRepresentation>> applicationRoleMappings;
|
||||||
protected Map<String, List<ScopeMappingRepresentation>> applicationScopeMappings;
|
protected Map<String, List<ScopeMappingRepresentation>> applicationScopeMappings;
|
||||||
protected List<SocialMappingRepresentation> socialMappings;
|
protected List<SocialMappingRepresentation> socialMappings;
|
||||||
protected List<AuthenticationMappingRepresentation> authenticationMappings;
|
|
||||||
protected List<ApplicationRepresentation> applications;
|
protected List<ApplicationRepresentation> applications;
|
||||||
protected List<OAuthClientRepresentation> oauthClients;
|
protected List<OAuthClientRepresentation> oauthClients;
|
||||||
protected Map<String, String> socialProviders;
|
protected Map<String, String> socialProviders;
|
||||||
|
@ -181,18 +180,6 @@ public class RealmRepresentation {
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<AuthenticationMappingRepresentation> getAuthenticationMappings() {
|
|
||||||
return authenticationMappings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthenticationMappingRepresentation authenticationMapping(String username) {
|
|
||||||
AuthenticationMappingRepresentation mapping = new AuthenticationMappingRepresentation();
|
|
||||||
mapping.setUsername(username);
|
|
||||||
if (authenticationMappings == null) authenticationMappings = new ArrayList<AuthenticationMappingRepresentation>();
|
|
||||||
authenticationMappings.add(mapping);
|
|
||||||
return mapping;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getRequiredCredentials() {
|
public Set<String> getRequiredCredentials() {
|
||||||
return requiredCredentials;
|
return requiredCredentials;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ public class UserRepresentation {
|
||||||
protected String firstName;
|
protected String firstName;
|
||||||
protected String lastName;
|
protected String lastName;
|
||||||
protected String email;
|
protected String email;
|
||||||
|
protected AuthenticationLinkRepresentation authenticationLink;
|
||||||
protected Map<String, String> attributes;
|
protected Map<String, String> attributes;
|
||||||
protected List<CredentialRepresentation> credentials;
|
protected List<CredentialRepresentation> credentials;
|
||||||
protected List<String> requiredActions;
|
protected List<String> requiredActions;
|
||||||
|
@ -96,6 +97,14 @@ public class UserRepresentation {
|
||||||
this.emailVerified = emailVerified;
|
this.emailVerified = emailVerified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthenticationLinkRepresentation getAuthenticationLink() {
|
||||||
|
return authenticationLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationLink(AuthenticationLinkRepresentation authenticationLink) {
|
||||||
|
this.authenticationLink = authenticationLink;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getAttributes() {
|
public Map<String, String> getAttributes() {
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,6 +8,8 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class AuthenticationProviderModel {
|
public class AuthenticationProviderModel {
|
||||||
|
|
||||||
|
public static final AuthenticationProviderModel DEFAULT_PROVIDER = new AuthenticationProviderModel("model", true, Collections.EMPTY_MAP);
|
||||||
|
|
||||||
private String providerName;
|
private String providerName;
|
||||||
private boolean passwordUpdateSupported = true;
|
private boolean passwordUpdateSupported = true;
|
||||||
private Map<String, String> config;
|
private Map<String, String> config;
|
||||||
|
|
|
@ -134,11 +134,9 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
|
||||||
|
|
||||||
boolean removeSocialLink(UserModel user, String socialProvider);
|
boolean removeSocialLink(UserModel user, String socialProvider);
|
||||||
|
|
||||||
UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink);
|
AuthenticationLinkModel getAuthenticationLink(UserModel user);
|
||||||
|
|
||||||
Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user);
|
void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink);
|
||||||
|
|
||||||
void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink);
|
|
||||||
|
|
||||||
boolean isSocial();
|
boolean isSocial();
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -393,7 +395,9 @@ public class RealmAdapter implements RealmModel {
|
||||||
private void removeUser(UserEntity user) {
|
private void removeUser(UserEntity user) {
|
||||||
em.createQuery("delete from " + UserRoleMappingEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
em.createQuery("delete from " + UserRoleMappingEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
||||||
em.createQuery("delete from " + SocialLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
em.createQuery("delete from " + SocialLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
||||||
em.createQuery("delete from " + AuthenticationLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
if (user.getAuthenticationLink() != null) {
|
||||||
|
em.remove(user.getAuthenticationLink());
|
||||||
|
}
|
||||||
em.remove(user);
|
em.remove(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -612,43 +616,22 @@ public class RealmAdapter implements RealmModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
|
public AuthenticationLinkModel getAuthenticationLink(UserModel user) {
|
||||||
TypedQuery<UserEntity> query = em.createNamedQuery("findUserByAuthLinkAndRealm", UserEntity.class);
|
UserEntity userEntity = ((UserAdapter) user).getUser();
|
||||||
query.setParameter("realm", realm);
|
AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink();
|
||||||
query.setParameter("authProvider", authenticationLink.getAuthProvider());
|
return authLinkEntity == null ? null : new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
|
||||||
query.setParameter("authUserId", authenticationLink.getAuthUserId());
|
|
||||||
List<UserEntity> results = query.getResultList();
|
|
||||||
if (results.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
} else if (results.size() > 1) {
|
|
||||||
throw new IllegalStateException("More results found for authenticationProvider=" + authenticationLink.getAuthProvider() +
|
|
||||||
", authUserId=" + authenticationLink.getAuthUserId() + ", results=" + results);
|
|
||||||
} else {
|
|
||||||
UserEntity user = results.get(0);
|
|
||||||
return new UserAdapter(user);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
|
public void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
|
||||||
TypedQuery<AuthenticationLinkEntity> query = em.createNamedQuery("findAuthLinkByUser", AuthenticationLinkEntity.class);
|
|
||||||
query.setParameter("user", ((UserAdapter) user).getUser());
|
|
||||||
List<AuthenticationLinkEntity> results = query.getResultList();
|
|
||||||
Set<AuthenticationLinkModel> set = new HashSet<AuthenticationLinkModel>();
|
|
||||||
for (AuthenticationLinkEntity entity : results) {
|
|
||||||
set.add(new AuthenticationLinkModel(entity.getAuthProvider(), entity.getAuthUserId()));
|
|
||||||
}
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
|
|
||||||
AuthenticationLinkEntity entity = new AuthenticationLinkEntity();
|
AuthenticationLinkEntity entity = new AuthenticationLinkEntity();
|
||||||
entity.setRealm(realm);
|
|
||||||
entity.setAuthProvider(authenticationLink.getAuthProvider());
|
entity.setAuthProvider(authenticationLink.getAuthProvider());
|
||||||
entity.setAuthUserId(authenticationLink.getAuthUserId());
|
entity.setAuthUserId(authenticationLink.getAuthUserId());
|
||||||
entity.setUser(((UserAdapter) user).getUser());
|
|
||||||
|
UserEntity userEntity = ((UserAdapter) user).getUser();
|
||||||
|
userEntity.setAuthenticationLink(entity);
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
|
em.persist(userEntity);
|
||||||
em.flush();
|
em.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -814,7 +797,15 @@ public class RealmAdapter implements RealmModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AuthenticationProviderModel> getAuthenticationProviders() {
|
public List<AuthenticationProviderModel> getAuthenticationProviders() {
|
||||||
Collection<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
|
List<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
|
||||||
|
Collections.sort(entities, new Comparator<AuthenticationProviderEntity>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(AuthenticationProviderEntity o1, AuthenticationProviderEntity o2) {
|
||||||
|
return o1.getPriority() - o2.getPriority();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
|
List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
|
||||||
for (AuthenticationProviderEntity entity : entities) {
|
for (AuthenticationProviderEntity entity : entities) {
|
||||||
result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig()));
|
result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig()));
|
||||||
|
@ -826,11 +817,13 @@ public class RealmAdapter implements RealmModel {
|
||||||
@Override
|
@Override
|
||||||
public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
|
public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
|
||||||
List<AuthenticationProviderEntity> newEntities = new ArrayList<AuthenticationProviderEntity>();
|
List<AuthenticationProviderEntity> newEntities = new ArrayList<AuthenticationProviderEntity>();
|
||||||
|
int counter = 1;
|
||||||
for (AuthenticationProviderModel model : authenticationProviders) {
|
for (AuthenticationProviderModel model : authenticationProviders) {
|
||||||
AuthenticationProviderEntity entity = new AuthenticationProviderEntity();
|
AuthenticationProviderEntity entity = new AuthenticationProviderEntity();
|
||||||
entity.setProviderName(model.getProviderName());
|
entity.setProviderName(model.getProviderName());
|
||||||
entity.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
|
entity.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
|
||||||
entity.setConfig(model.getConfig());
|
entity.setConfig(model.getConfig());
|
||||||
|
entity.setPriority(counter++);
|
||||||
newEntities.add(entity);
|
newEntities.add(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,16 +6,13 @@ import javax.persistence.Id;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.NamedQueries;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
|
import javax.persistence.OneToOne;
|
||||||
|
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
@NamedQueries({
|
|
||||||
@NamedQuery(name="findAuthLinkByUser", query="select link from AuthenticationLinkEntity link where link.user = :user"),
|
|
||||||
@NamedQuery(name="findUserByAuthLinkAndRealm", query="select link.user from AuthenticationLinkEntity link where link.realm = :realm and link.authProvider = :authProvider and link.authUserId = :authUserId")
|
|
||||||
})
|
|
||||||
@Entity
|
@Entity
|
||||||
public class AuthenticationLinkEntity {
|
public class AuthenticationLinkEntity {
|
||||||
|
|
||||||
|
@ -24,12 +21,6 @@ public class AuthenticationLinkEntity {
|
||||||
@GeneratedValue(generator = "keycloak_generator")
|
@GeneratedValue(generator = "keycloak_generator")
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
private UserEntity user;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
protected RealmEntity realm;
|
|
||||||
|
|
||||||
protected String authProvider;
|
protected String authProvider;
|
||||||
protected String authUserId;
|
protected String authUserId;
|
||||||
|
|
||||||
|
@ -41,22 +32,6 @@ public class AuthenticationLinkEntity {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserEntity getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUser(UserEntity user) {
|
|
||||||
this.user = user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RealmEntity getRealm() {
|
|
||||||
return realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealm(RealmEntity realm) {
|
|
||||||
this.realm = realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthProvider() {
|
public String getAuthProvider() {
|
||||||
return authProvider;
|
return authProvider;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ public class AuthenticationProviderEntity {
|
||||||
|
|
||||||
private String providerName;
|
private String providerName;
|
||||||
private boolean passwordUpdateSupported;
|
private boolean passwordUpdateSupported;
|
||||||
|
private int priority;
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
@MapKeyColumn(name="name")
|
@MapKeyColumn(name="name")
|
||||||
|
@ -56,6 +57,14 @@ public class AuthenticationProviderEntity {
|
||||||
this.passwordUpdateSupported = passwordUpdateSupported;
|
this.passwordUpdateSupported = passwordUpdateSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(int priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getConfig() {
|
public Map<String, String> getConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -67,7 +68,7 @@ public class RealmEntity {
|
||||||
|
|
||||||
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
||||||
@JoinTable(name="AuthProviders")
|
@JoinTable(name="AuthProviders")
|
||||||
Collection<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
|
List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||||
Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
|
Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
|
||||||
|
@ -244,11 +245,11 @@ public class RealmEntity {
|
||||||
this.requiredCredentials = requiredCredentials;
|
this.requiredCredentials = requiredCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<AuthenticationProviderEntity> getAuthenticationProviders() {
|
public List<AuthenticationProviderEntity> getAuthenticationProviders() {
|
||||||
return authenticationProviders;
|
return authenticationProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticationProviders(Collection<AuthenticationProviderEntity> authenticationProviders) {
|
public void setAuthenticationProviders(List<AuthenticationProviderEntity> authenticationProviders) {
|
||||||
this.authenticationProviders = authenticationProviders;
|
this.authenticationProviders = authenticationProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ import javax.persistence.MapKeyColumn;
|
||||||
import javax.persistence.NamedQueries;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.OneToOne;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -64,6 +66,9 @@ public class UserEntity {
|
||||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
|
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
|
||||||
protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
||||||
|
|
||||||
|
@OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
|
||||||
|
protected AuthenticationLinkEntity authenticationLink;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -160,6 +165,14 @@ public class UserEntity {
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthenticationLinkEntity getAuthenticationLink() {
|
||||||
|
return authenticationLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationLink(AuthenticationLinkEntity authenticationLink) {
|
||||||
|
this.authenticationLink = authenticationLink;
|
||||||
|
}
|
||||||
|
|
||||||
public int getNotBefore() {
|
public int getNotBefore() {
|
||||||
return notBefore;
|
return notBefore;
|
||||||
}
|
}
|
||||||
|
|
|
@ -927,41 +927,26 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
|
public AuthenticationLinkModel getAuthenticationLink(UserModel user) {
|
||||||
DBObject query = new QueryBuilder()
|
|
||||||
.and("authenticationLinks.authProvider").is(authenticationLink.getAuthProvider())
|
|
||||||
.and("authenticationLinks.authUserId").is(authenticationLink.getAuthUserId())
|
|
||||||
.and("realmId").is(getId())
|
|
||||||
.get();
|
|
||||||
UserEntity userEntity = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext);
|
|
||||||
return userEntity==null ? null : new UserAdapter(userEntity, invocationContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
|
|
||||||
UserEntity userEntity = ((UserAdapter)user).getUser();
|
UserEntity userEntity = ((UserAdapter)user).getUser();
|
||||||
List<AuthenticationLinkEntity> linkEntities = userEntity.getAuthenticationLinks();
|
AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink();
|
||||||
|
|
||||||
if (linkEntities == null) {
|
if (authLinkEntity == null) {
|
||||||
return Collections.EMPTY_SET;
|
return null;
|
||||||
|
} else {
|
||||||
|
return new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<AuthenticationLinkModel> result = new HashSet<AuthenticationLinkModel>();
|
|
||||||
for (AuthenticationLinkEntity authLinkEntity : linkEntities) {
|
|
||||||
AuthenticationLinkModel model = new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
|
|
||||||
result.add(model);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
|
public void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
|
||||||
UserEntity userEntity = ((UserAdapter)user).getUser();
|
UserEntity userEntity = ((UserAdapter)user).getUser();
|
||||||
AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity();
|
AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity();
|
||||||
authLinkEntity.setAuthProvider(authenticationLink.getAuthProvider());
|
authLinkEntity.setAuthProvider(authenticationLink.getAuthProvider());
|
||||||
authLinkEntity.setAuthUserId(authenticationLink.getAuthUserId());
|
authLinkEntity.setAuthUserId(authenticationLink.getAuthUserId());
|
||||||
|
userEntity.setAuthenticationLink(authLinkEntity);
|
||||||
|
|
||||||
getMongoStore().pushItemToList(userEntity, "authenticationLinks", authLinkEntity, true, invocationContext);
|
getMongoStore().updateEntity(userEntity, invocationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateRealm() {
|
protected void updateRealm() {
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
|
||||||
private List<UserModel.RequiredAction> requiredActions;
|
private List<UserModel.RequiredAction> requiredActions;
|
||||||
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
||||||
private List<SocialLinkEntity> socialLinks;
|
private List<SocialLinkEntity> socialLinks;
|
||||||
private List<AuthenticationLinkEntity> authenticationLinks;
|
private AuthenticationLinkEntity authenticationLink;
|
||||||
|
|
||||||
@MongoField
|
@MongoField
|
||||||
public String getLoginName() {
|
public String getLoginName() {
|
||||||
|
@ -163,11 +163,11 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
|
||||||
}
|
}
|
||||||
|
|
||||||
@MongoField
|
@MongoField
|
||||||
public List<AuthenticationLinkEntity> getAuthenticationLinks() {
|
public AuthenticationLinkEntity getAuthenticationLink() {
|
||||||
return authenticationLinks;
|
return authenticationLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticationLinks(List<AuthenticationLinkEntity> authenticationLinks) {
|
public void setAuthenticationLink(AuthenticationLinkEntity authenticationLink) {
|
||||||
this.authenticationLinks = authenticationLinks;
|
this.authenticationLink = authenticationLink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,8 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
|
||||||
realm2 = realmManager.createRealm("realm2");
|
realm2 = realmManager.createRealm("realm2");
|
||||||
realm1.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
realm1.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||||
realm2.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
realm2.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||||
|
realm1.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
|
||||||
|
realm2.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
|
||||||
|
|
||||||
UserModel john = realm1.addUser("john");
|
UserModel john = realm1.addUser("john");
|
||||||
john.setEnabled(true);
|
john.setEnabled(true);
|
||||||
|
@ -93,10 +95,10 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
|
||||||
Assert.assertEquals("john@email.org", john2.getEmail());
|
Assert.assertEquals("john@email.org", john2.getEmail());
|
||||||
|
|
||||||
// Verify link exists
|
// Verify link exists
|
||||||
Set<AuthenticationLinkModel> authLinks = realm2.getAuthenticationLinks(john2);
|
AuthenticationLinkModel authLink = realm2.getAuthenticationLink(john2);
|
||||||
Assert.assertEquals(1, authLinks.size());
|
Assert.assertNotNull(authLink);
|
||||||
AuthenticationLinkModel authLink = authLinks.iterator().next();
|
|
||||||
Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL);
|
Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL);
|
||||||
|
Assert.assertEquals(authLink.getAuthUserId(), realm1.getUser("john").getId());
|
||||||
} finally {
|
} finally {
|
||||||
ResteasyProviderFactory.clearContextData();
|
ResteasyProviderFactory.clearContextData();
|
||||||
}
|
}
|
||||||
|
@ -108,14 +110,19 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
|
||||||
// Add externalModel authenticationProvider into realm2 and point to realm1
|
// Add externalModel authenticationProvider into realm2 and point to realm1
|
||||||
setupAuthenticationProviders();
|
setupAuthenticationProviders();
|
||||||
|
|
||||||
|
// Add john to realm2 and set authentication link
|
||||||
|
UserModel john = realm2.addUser("john");
|
||||||
|
john.setEnabled(true);
|
||||||
|
realm2.setAuthenticationLink(john, new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, realm1.getUser("john").getId()));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// this is needed for externalModel provider
|
// this is needed for externalModel provider
|
||||||
ResteasyProviderFactory.pushContext(KeycloakSession.class, identitySession);
|
ResteasyProviderFactory.pushContext(KeycloakSession.class, identitySession);
|
||||||
|
|
||||||
// Change credential via realm2 and validate that they are changed in both realms
|
// Change credential via realm2 and validate that they are changed also in realm1
|
||||||
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm2);
|
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm2);
|
||||||
try {
|
try {
|
||||||
authProviderManager.updatePassword("john", "password-updated");
|
Assert.assertTrue(authProviderManager.updatePassword(john, "password-updated"));
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
ape.printStackTrace();
|
ape.printStackTrace();
|
||||||
Assert.fail("Error not expected");
|
Assert.fail("Error not expected");
|
||||||
|
@ -130,14 +137,14 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
|
||||||
|
|
||||||
// Change credential and validate that password is updated just for realm2
|
// Change credential and validate that password is updated just for realm2
|
||||||
try {
|
try {
|
||||||
authProviderManager.updatePassword("john", "password-updated2");
|
Assert.assertFalse(authProviderManager.updatePassword(john, "password-updated2"));
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
ape.printStackTrace();
|
ape.printStackTrace();
|
||||||
Assert.fail("Error not expected");
|
Assert.fail("Error not expected");
|
||||||
}
|
}
|
||||||
formData = createFormData("john", "password-updated2");
|
formData = createFormData("john", "password-updated2");
|
||||||
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm1, formData));
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm1, formData));
|
||||||
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm2, formData));
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm2, formData));
|
||||||
|
|
||||||
|
|
||||||
// Allow passwordUpdate propagation again
|
// Allow passwordUpdate propagation again
|
||||||
|
@ -146,7 +153,7 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
|
||||||
// Set passwordPolicy for realm1 and verify that password update fail
|
// Set passwordPolicy for realm1 and verify that password update fail
|
||||||
realm1.setPasswordPolicy(new PasswordPolicy("length(8)"));
|
realm1.setPasswordPolicy(new PasswordPolicy("length(8)"));
|
||||||
try {
|
try {
|
||||||
authProviderManager.updatePassword("john", "passw");
|
authProviderManager.updatePassword(john, "passw");
|
||||||
Assert.fail("Update not expected to pass");
|
Assert.fail("Update not expected to pass");
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
|
||||||
|
|
|
@ -95,9 +95,8 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
|
||||||
Assert.assertEquals("john@email.org", john.getEmail());
|
Assert.assertEquals("john@email.org", john.getEmail());
|
||||||
|
|
||||||
// Verify link exists
|
// Verify link exists
|
||||||
Set<AuthenticationLinkModel> authLinks = realm.getAuthenticationLinks(john);
|
AuthenticationLinkModel authLink = realm.getAuthenticationLink(john);
|
||||||
Assert.assertEquals(1, authLinks.size());
|
Assert.assertNotNull(authLink);
|
||||||
AuthenticationLinkModel authLink = authLinks.iterator().next();
|
|
||||||
Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_PICKETLINK);
|
Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_PICKETLINK);
|
||||||
} finally {
|
} finally {
|
||||||
ResteasyProviderFactory.clearContextData();
|
ResteasyProviderFactory.clearContextData();
|
||||||
|
@ -114,6 +113,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
|
||||||
|
|
||||||
// Add some user and password to realm
|
// Add some user and password to realm
|
||||||
UserModel realmUser = realm.addUser("realmUser");
|
UserModel realmUser = realm.addUser("realmUser");
|
||||||
|
realmUser.setEnabled(true);
|
||||||
UserCredentialModel credential = new UserCredentialModel();
|
UserCredentialModel credential = new UserCredentialModel();
|
||||||
credential.setType(CredentialRepresentation.PASSWORD);
|
credential.setType(CredentialRepresentation.PASSWORD);
|
||||||
credential.setValue("pass");
|
credential.setValue("pass");
|
||||||
|
@ -135,6 +135,11 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
|
||||||
realmUser.setEnabled(false);
|
realmUser.setEnabled(false);
|
||||||
formData = AuthProvidersExternalModelTest.createFormData("realmUser", "pass");
|
formData = AuthProvidersExternalModelTest.createFormData("realmUser", "pass");
|
||||||
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.ACCOUNT_DISABLED, am.authenticateForm(realm, formData));
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.ACCOUNT_DISABLED, am.authenticateForm(realm, formData));
|
||||||
|
|
||||||
|
// Successful authentication
|
||||||
|
realmUser.setEnabled(true);
|
||||||
|
formData = AuthProvidersExternalModelTest.createFormData("realmUser", "pass");
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
|
||||||
} finally {
|
} finally {
|
||||||
ResteasyProviderFactory.clearContextData();
|
ResteasyProviderFactory.clearContextData();
|
||||||
}
|
}
|
||||||
|
@ -149,26 +154,34 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
|
||||||
// this is needed for ldap provider
|
// this is needed for ldap provider
|
||||||
ResteasyProviderFactory.pushContext(KeycloakRegistry.class, new KeycloakRegistry());
|
ResteasyProviderFactory.pushContext(KeycloakRegistry.class, new KeycloakRegistry());
|
||||||
|
|
||||||
|
LdapTestUtils.setLdapPassword(realm, "john", "password");
|
||||||
|
|
||||||
|
// First authenticate successfully to sync john into realm
|
||||||
|
MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password");
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
|
||||||
|
|
||||||
// Change credential and validate that user can authenticate
|
// Change credential and validate that user can authenticate
|
||||||
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
|
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
|
||||||
|
|
||||||
|
UserModel john = realm.getUser("john");
|
||||||
try {
|
try {
|
||||||
authProviderManager.updatePassword("john", "password-updated");
|
Assert.assertTrue(authProviderManager.updatePassword(john, "password-updated"));
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
ape.printStackTrace();
|
ape.printStackTrace();
|
||||||
Assert.fail("Error not expected");
|
Assert.fail("Error not expected");
|
||||||
}
|
}
|
||||||
MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated");
|
formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated");
|
||||||
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
|
||||||
|
|
||||||
// Password updated just in LDAP, so validating directly in realm should fail
|
// Password updated just in LDAP, so validating directly in realm should fail
|
||||||
Assert.assertFalse(realm.validatePassword(realm.getUser("john"), "password-updated"));
|
Assert.assertFalse(realm.validatePassword(john, "password-updated"));
|
||||||
|
|
||||||
// Switch to not allow updating passwords in ldap
|
// Switch to not allow updating passwords in ldap
|
||||||
AuthProvidersExternalModelTest.setPasswordUpdateForProvider(false, AuthProviderConstants.PROVIDER_NAME_PICKETLINK, realm);
|
AuthProvidersExternalModelTest.setPasswordUpdateForProvider(false, AuthProviderConstants.PROVIDER_NAME_PICKETLINK, realm);
|
||||||
|
|
||||||
// Change credential and validate that password is not updated
|
// Change credential and validate that password is not updated
|
||||||
try {
|
try {
|
||||||
authProviderManager.updatePassword("john", "password-updated2");
|
Assert.assertFalse(authProviderManager.updatePassword(john, "password-updated2"));
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
ape.printStackTrace();
|
ape.printStackTrace();
|
||||||
Assert.fail("Error not expected");
|
Assert.fail("Error not expected");
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.model.test;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -14,6 +15,8 @@ import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedHashMap;
|
import javax.ws.rs.core.MultivaluedHashMap;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class AuthenticationManagerTest extends AbstractModelTest {
|
public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
|
@ -138,6 +141,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
realm.setPublicKeyPem("0234234");
|
realm.setPublicKeyPem("0234234");
|
||||||
realm.setAccessTokenLifespan(1000);
|
realm.setAccessTokenLifespan(1000);
|
||||||
realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||||
|
realm.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
|
||||||
|
|
||||||
am = new AuthenticationManager();
|
am = new AuthenticationManager();
|
||||||
|
|
||||||
|
|
|
@ -208,24 +208,9 @@ public class ImportTest extends AbstractModelTest {
|
||||||
Assert.assertTrue(authProv3.isPasswordUpdateSupported());
|
Assert.assertTrue(authProv3.isPasswordUpdateSupported());
|
||||||
|
|
||||||
// Test authentication linking
|
// Test authentication linking
|
||||||
Set<AuthenticationLinkModel> authLinks = realm.getAuthenticationLinks(socialUser);
|
AuthenticationLinkModel authLink = realm.getAuthenticationLink(socialUser);
|
||||||
Assert.assertEquals(2, authLinks.size());
|
Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, authLink.getAuthProvider());
|
||||||
boolean plFound = false;
|
Assert.assertEquals("myUser1", authLink.getAuthUserId());
|
||||||
boolean extFound = false;
|
|
||||||
for (AuthenticationLinkModel authLinkModel : authLinks) {
|
|
||||||
if (AuthProviderConstants.PROVIDER_NAME_PICKETLINK.equals(authLinkModel.getAuthProvider())) {
|
|
||||||
plFound = true;
|
|
||||||
Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser1");
|
|
||||||
} else if (AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL.equals(authLinkModel.getAuthProvider())) {
|
|
||||||
extFound = true;
|
|
||||||
Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser11");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assert.assertTrue(plFound && extFound);
|
|
||||||
|
|
||||||
UserModel foundAuthUser = realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "myUser1"));
|
|
||||||
Assert.assertEquals(foundAuthUser.getLoginName(), socialUser.getLoginName());
|
|
||||||
Assert.assertNull(realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "not-existing")));
|
|
||||||
|
|
||||||
commit();
|
commit();
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"username": "mySocialUser",
|
"username": "mySocialUser",
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"authenticationLink": {
|
||||||
|
"authProvider": "picketlink",
|
||||||
|
"authUserId": "myUser1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"socialMappings": [
|
"socialMappings": [
|
||||||
|
@ -100,21 +104,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"authenticationMappings": [
|
|
||||||
{
|
|
||||||
"username": "mySocialUser",
|
|
||||||
"authenticationLinks": [
|
|
||||||
{
|
|
||||||
"authProvider": "picketlink",
|
|
||||||
"authUserId": "myUser1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"authProvider": "externalModel",
|
|
||||||
"authUserId": "myUser11"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"applications": [
|
"applications": [
|
||||||
{
|
{
|
||||||
"name": "Application",
|
"name": "Application",
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.jboss.resteasy.logging.Logger;
|
import org.jboss.resteasy.logging.Logger;
|
||||||
import org.keycloak.models.AdminRoles;
|
import org.keycloak.models.AdminRoles;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
import org.keycloak.models.Config;
|
import org.keycloak.models.Config;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -58,6 +61,7 @@ public class ApplianceBootstrap {
|
||||||
realm.setSslNotRequired(true);
|
realm.setSslNotRequired(true);
|
||||||
realm.setRegistrationAllowed(false);
|
realm.setRegistrationAllowed(false);
|
||||||
manager.generateRealmKeys(realm);
|
manager.generateRealmKeys(realm);
|
||||||
|
realm.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
|
||||||
|
|
||||||
ApplicationModel adminConsole = new ApplicationManager(manager).createApplication(realm, Constants.ADMIN_CONSOLE_APPLICATION);
|
ApplicationModel adminConsole = new ApplicationManager(manager).createApplication(realm, Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
adminConsole.setBaseUrl("/auth/admin/index.html");
|
adminConsole.setBaseUrl("/auth/admin/index.html");
|
||||||
|
|
|
@ -16,8 +16,7 @@ import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.spi.authentication.AuthProviderStatus;
|
import org.keycloak.spi.authentication.AuthProviderStatus;
|
||||||
import org.keycloak.spi.authentication.AuthResult;
|
import org.keycloak.spi.authentication.AuthUser;
|
||||||
import org.keycloak.spi.authentication.AuthenticatedUser;
|
|
||||||
import org.keycloak.spi.authentication.AuthenticationProviderManager;
|
import org.keycloak.spi.authentication.AuthenticationProviderManager;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
|
@ -188,6 +187,25 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
||||||
|
if (user == null) {
|
||||||
|
AuthUser authUser = AuthenticationProviderManager.getManager(realm).getUser(username);
|
||||||
|
if (authUser != null) {
|
||||||
|
// Create new user and link him with authentication provider
|
||||||
|
user = realm.addUser(authUser.getUsername());
|
||||||
|
user.setEnabled(true);
|
||||||
|
user.setFirstName(authUser.getFirstName());
|
||||||
|
user.setLastName(authUser.getLastName());
|
||||||
|
user.setEmail(authUser.getEmail());
|
||||||
|
realm.setAuthenticationLink(user, new AuthenticationLinkModel(authUser.getProviderName(), authUser.getId()));
|
||||||
|
} else {
|
||||||
|
logger.warn("User " + username + " not found");
|
||||||
|
return AuthenticationStatus.INVALID_USER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkEnabled(user)) {
|
||||||
|
return AuthenticationStatus.ACCOUNT_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
Set<String> types = new HashSet<String>();
|
Set<String> types = new HashSet<String>();
|
||||||
|
|
||||||
|
@ -202,20 +220,13 @@ public class AuthenticationManager {
|
||||||
return AuthenticationStatus.MISSING_PASSWORD;
|
return AuthenticationStatus.MISSING_PASSWORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user == null && types.contains(CredentialRepresentation.TOTP)) {
|
if (user.isTotp()) {
|
||||||
logger.warn("User doesn't exists and TOTP is required for the realm");
|
|
||||||
return AuthenticationStatus.INVALID_USER;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user != null && user.isTotp()) {
|
|
||||||
String token = formData.getFirst(CredentialRepresentation.TOTP);
|
String token = formData.getFirst(CredentialRepresentation.TOTP);
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
logger.warn("TOTP token not provided");
|
logger.warn("TOTP token not provided");
|
||||||
return AuthenticationStatus.MISSING_TOTP;
|
return AuthenticationStatus.MISSING_TOTP;
|
||||||
}
|
}
|
||||||
if (!checkEnabled(user)) {
|
|
||||||
return AuthenticationStatus.ACCOUNT_DISABLED;
|
|
||||||
}
|
|
||||||
logger.debug("validating TOTP");
|
logger.debug("validating TOTP");
|
||||||
if (!realm.validateTOTP(user, password, token)) {
|
if (!realm.validateTOTP(user, password, token)) {
|
||||||
return AuthenticationStatus.INVALID_CREDENTIALS;
|
return AuthenticationStatus.INVALID_CREDENTIALS;
|
||||||
|
@ -223,58 +234,12 @@ public class AuthenticationManager {
|
||||||
} else {
|
} else {
|
||||||
logger.debug("validating password for user: " + username);
|
logger.debug("validating password for user: " + username);
|
||||||
|
|
||||||
AuthResult authResult = AuthenticationProviderManager.getManager(realm).validatePassword(username, password);
|
AuthProviderStatus authStatus = AuthenticationProviderManager.getManager(realm).validatePassword(user, password);
|
||||||
if (authResult.getAuthProviderStatus() == AuthProviderStatus.INVALID_CREDENTIALS) {
|
if (authStatus == AuthProviderStatus.INVALID_CREDENTIALS) {
|
||||||
logger.debug("invalid password for user: " + username);
|
logger.debug("invalid password for user: " + username);
|
||||||
return AuthenticationStatus.INVALID_CREDENTIALS;
|
return AuthenticationStatus.INVALID_CREDENTIALS;
|
||||||
} else if (authResult.getAuthProviderStatus() == AuthProviderStatus.USER_NOT_FOUND) {
|
} else if (authStatus == AuthProviderStatus.FAILED) {
|
||||||
logger.debug("User " + username + " not found in any Authentication provider");
|
return AuthenticationStatus.FAILED;
|
||||||
return AuthenticationStatus.INVALID_USER;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authResult.getAuthenticatedUser() != null) {
|
|
||||||
AuthenticatedUser authUser = authResult.getAuthenticatedUser();
|
|
||||||
AuthenticationLinkModel authLink = new AuthenticationLinkModel(authResult.getProviderName(), authUser.getId());
|
|
||||||
user = realm.getUserByAuthenticationLink(authLink);
|
|
||||||
if (user == null) {
|
|
||||||
user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
|
||||||
if (user != null) {
|
|
||||||
// Case when we already have user with the same username like authenticated, but he is not yet linked to current provider.
|
|
||||||
// TODO: Revisit if it's ok to link if we allow to change username. Maybe ask user?
|
|
||||||
// TODO: Update of existing account?
|
|
||||||
realm.addAuthenticationLink(user, authLink);
|
|
||||||
logger.info("User " + authUser.getUsername() + " successfully authenticated and linked with provider " + authResult.getProviderName());
|
|
||||||
} else {
|
|
||||||
// Create new user, which has been successfully authenticated and link him with authentication provider
|
|
||||||
user = realm.addUser(authUser.getUsername());
|
|
||||||
user.setEnabled(true);
|
|
||||||
user.setFirstName(authUser.getFirstName());
|
|
||||||
user.setLastName(authUser.getLastName());
|
|
||||||
user.setEmail(authUser.getEmail());
|
|
||||||
|
|
||||||
realm.addAuthenticationLink(user, authLink);
|
|
||||||
logger.info("User " + username + " successfully authenticated and created based on provider " + authResult.getProviderName());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Existing and linked user has been authenticated TODO: Update of existing account?
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticated username could be different from the "form" username. In this case, we will change it
|
|
||||||
if (!username.equals(user.getLoginName())) {
|
|
||||||
formData.putSingle(FORM_USERNAME, user.getLoginName());
|
|
||||||
logger.debug("Existing user " + user.getLoginName() + " successfully authenticated");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Authentication provider didn't send AuthenticatedUser. Using already retrieved user based on username from "form"
|
|
||||||
if (user == null) {
|
|
||||||
logger.warn("User '" + username + "' successfully authenticated, but he doesn't exists and don't know how to create him");
|
|
||||||
return AuthenticationStatus.INVALID_USER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkEnabled(user)) {
|
|
||||||
return AuthenticationStatus.ACCOUNT_DISABLED;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import org.keycloak.models.UserModel.RequiredAction;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthenticationLinkRepresentation;
|
import org.keycloak.representations.idm.AuthenticationLinkRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthenticationMappingRepresentation;
|
|
||||||
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
|
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||||
|
@ -37,6 +36,7 @@ import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -387,15 +387,6 @@ public class RealmManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rep.getAuthenticationMappings() != null) {
|
|
||||||
for (AuthenticationMappingRepresentation authMapping : rep.getAuthenticationMappings()) {
|
|
||||||
UserModel user = userMap.get(authMapping.getUsername());
|
|
||||||
for (AuthenticationLinkRepresentation link : authMapping.getAuthenticationLinks()) {
|
|
||||||
AuthenticationLinkModel mappingModel = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId());
|
|
||||||
newRealm.addAuthenticationLink(user, mappingModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rep.getSmtpServer() != null) {
|
if (rep.getSmtpServer() != null) {
|
||||||
newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
|
newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
|
||||||
|
@ -411,6 +402,9 @@ public class RealmManager {
|
||||||
if (rep.getAuthenticationProviders() != null) {
|
if (rep.getAuthenticationProviders() != null) {
|
||||||
List<AuthenticationProviderModel> authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders());
|
List<AuthenticationProviderModel> authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders());
|
||||||
newRealm.setAuthenticationProviders(authProviderModels);
|
newRealm.setAuthenticationProviders(authProviderModels);
|
||||||
|
} else {
|
||||||
|
List<AuthenticationProviderModel> authProviderModels = Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER);
|
||||||
|
newRealm.setAuthenticationProviders(authProviderModels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,6 +468,11 @@ public class RealmManager {
|
||||||
newRealm.updateCredential(user, credential);
|
newRealm.updateCredential(user, credential);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (userRep.getAuthenticationLink() != null) {
|
||||||
|
AuthenticationLinkRepresentation link = userRep.getAuthenticationLink();
|
||||||
|
AuthenticationLinkModel authLink = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId());
|
||||||
|
newRealm.setAuthenticationLink(user, authLink);
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -264,13 +264,15 @@ public class AccountService {
|
||||||
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
|
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
|
||||||
if (Validation.isEmpty(password)) {
|
if (Validation.isEmpty(password)) {
|
||||||
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
|
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
|
||||||
// TODO: This may not work in some cases. For example if ldap username is "foo" but actual loginName of user is "bar", which could theoretically happen...
|
} else if (authProviderManager.validatePassword(user, password) != AuthProviderStatus.SUCCESS) {
|
||||||
} else if (authProviderManager.validatePassword(user.getLoginName(), password).getAuthProviderStatus() != AuthProviderStatus.SUCCESS) {
|
|
||||||
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
|
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
authProviderManager.updatePassword(user.getLoginName(), passwordNew);
|
boolean passwordUpdateSuccess = authProviderManager.updatePassword(user, passwordNew);
|
||||||
|
if (!passwordUpdateSuccess) {
|
||||||
|
return account.setError("Password update failed").createResponse(AccountPages.PASSWORD);
|
||||||
|
}
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
|
return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,10 @@ public class RequiredActionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AuthenticationProviderManager.getManager(realm).updatePassword(user.getLoginName(), passwordNew);
|
boolean updateSuccessful = AuthenticationProviderManager.getManager(realm).updatePassword(user, passwordNew);
|
||||||
|
if (!updateSuccessful) {
|
||||||
|
return loginForms.setError("Password update failed").createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||||
|
}
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
return loginForms.setError(ape.getMessage()).createResponse(RequiredAction.UPDATE_PASSWORD);
|
return loginForms.setError(ape.getMessage()).createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
|
@ -397,12 +397,21 @@ public class TokenService {
|
||||||
UserCredentialModel credentials = new UserCredentialModel();
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
credentials.setType(CredentialRepresentation.PASSWORD);
|
credentials.setType(CredentialRepresentation.PASSWORD);
|
||||||
credentials.setValue(formData.getFirst("password"));
|
credentials.setValue(formData.getFirst("password"));
|
||||||
|
|
||||||
|
boolean passwordUpdateSuccessful;
|
||||||
|
String passwordUpdateError = null;
|
||||||
try {
|
try {
|
||||||
AuthenticationProviderManager.getManager(realm).updatePassword(username, formData.getFirst("password"));
|
passwordUpdateSuccessful = AuthenticationProviderManager.getManager(realm).updatePassword(user, formData.getFirst("password"));
|
||||||
|
passwordUpdateError = "Password update failed";
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
passwordUpdateSuccessful = false;
|
||||||
|
passwordUpdateError = ape.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
// User already registered, but force him to update password
|
// User already registered, but force him to update password
|
||||||
|
if (!passwordUpdateSuccessful) {
|
||||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
return Flows.forms(realm, request, uriInfo).setError(ape.getMessage()).createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
return Flows.forms(realm, request, uriInfo).setError(passwordUpdateError).createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,7 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.spi.authentication.AuthProviderStatus;
|
import org.keycloak.spi.authentication.AuthProviderStatus;
|
||||||
import org.keycloak.spi.authentication.AuthResult;
|
import org.keycloak.spi.authentication.AuthUser;
|
||||||
import org.keycloak.spi.authentication.AuthenticatedUser;
|
|
||||||
import org.keycloak.spi.authentication.AuthenticationProvider;
|
import org.keycloak.spi.authentication.AuthenticationProvider;
|
||||||
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
|
||||||
|
@ -24,22 +23,19 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
|
||||||
private static final Logger logger = Logger.getLogger(AbstractModelAuthenticationProvider.class);
|
private static final Logger logger = Logger.getLogger(AbstractModelAuthenticationProvider.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthResult validatePassword(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
|
public AuthUser getUser(RealmModel currentRealm, Map<String, String> config, String username) throws AuthenticationProviderException {
|
||||||
RealmModel realm = getRealm(currentRealm, config);
|
RealmModel realm = getRealm(currentRealm, config);
|
||||||
|
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
||||||
|
return user == null ? null : createAuthenticatedUserInstance(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthProviderStatus validatePassword(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
|
||||||
|
RealmModel realm = getRealm(currentRealm, config);
|
||||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
||||||
|
|
||||||
if (user == null) {
|
|
||||||
return new AuthResult(AuthProviderStatus.USER_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean result = realm.validatePassword(user, password);
|
boolean result = realm.validatePassword(user, password);
|
||||||
if (!result) {
|
return result ? AuthProviderStatus.SUCCESS : AuthProviderStatus.INVALID_CREDENTIALS;
|
||||||
return new AuthResult(AuthProviderStatus.INVALID_CREDENTIALS);
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthenticatedUser authUser = createAuthenticatedUserInstance(user);
|
|
||||||
return new AuthResult(AuthProviderStatus.SUCCESS).setProviderName(getName()).setUser(authUser);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -54,7 +50,7 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
|
||||||
|
|
||||||
UserModel user = realm.getUser(username);
|
UserModel user = realm.getUser(username);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
logger.debugf("User '%s' doesn't exists. Skip password update", username);
|
logger.warnf("User '%s' doesn't exists. Skip password update", username);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,5 +64,9 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
|
||||||
|
|
||||||
protected abstract RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) throws AuthenticationProviderException;
|
protected abstract RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) throws AuthenticationProviderException;
|
||||||
|
|
||||||
protected abstract AuthenticatedUser createAuthenticatedUserInstance(UserModel user);
|
protected AuthUser createAuthenticatedUserInstance(UserModel user) {
|
||||||
|
return new AuthUser(user.getId(), user.getLoginName(), getName())
|
||||||
|
.setName(user.getFirstName(), user.getLastName())
|
||||||
|
.setEmail(user.getEmail());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.spi.authentication.AuthProviderConstants;
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
import org.keycloak.spi.authentication.AuthenticatedUser;
|
import org.keycloak.spi.authentication.AuthUser;
|
||||||
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,11 +40,4 @@ public class ExternalModelAuthenticationProvider extends AbstractModelAuthentica
|
||||||
}
|
}
|
||||||
return realm;
|
return realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
|
|
||||||
return new AuthenticatedUser(user.getId(), user.getLoginName())
|
|
||||||
.setName(user.getFirstName(), user.getLastName())
|
|
||||||
.setEmail(user.getEmail());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import java.util.Map;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.spi.authentication.AuthProviderConstants;
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
import org.keycloak.spi.authentication.AuthenticatedUser;
|
import org.keycloak.spi.authentication.AuthUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AbstractModelAuthenticationProvider, which uses current realm to call operations on
|
* AbstractModelAuthenticationProvider, which uses current realm to call operations on
|
||||||
|
@ -23,10 +23,4 @@ public class ModelAuthenticationProvider extends AbstractModelAuthenticationProv
|
||||||
protected RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) {
|
protected RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) {
|
||||||
return currentRealm;
|
return currentRealm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
|
|
||||||
// We don't want AuthenticatedUser instance. Auto-registration won't never happen with this provider
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,8 @@ import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.spi.authentication.AuthProviderStatus;
|
import org.keycloak.spi.authentication.AuthProviderStatus;
|
||||||
import org.keycloak.spi.authentication.AuthResult;
|
|
||||||
import org.keycloak.spi.authentication.AuthProviderConstants;
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
import org.keycloak.spi.authentication.AuthenticatedUser;
|
import org.keycloak.spi.authentication.AuthUser;
|
||||||
import org.keycloak.spi.authentication.AuthenticationProvider;
|
import org.keycloak.spi.authentication.AuthenticationProvider;
|
||||||
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
import org.keycloak.spi.picketlink.PartitionManagerProvider;
|
import org.keycloak.spi.picketlink.PartitionManagerProvider;
|
||||||
|
@ -36,28 +35,27 @@ public class PicketlinkAuthenticationProvider implements AuthenticationProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthResult validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
|
public AuthUser getUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException {
|
||||||
IdentityManager identityManager = getIdentityManager(realm);
|
IdentityManager identityManager = getIdentityManager(realm);
|
||||||
|
|
||||||
User picketlinkUser = BasicModel.getUser(identityManager, username);
|
User picketlinkUser = BasicModel.getUser(identityManager, username);
|
||||||
if (picketlinkUser == null) {
|
return picketlinkUser == null ? null : new AuthUser(picketlinkUser.getId(), picketlinkUser.getLoginName(), getName())
|
||||||
return new AuthResult(AuthProviderStatus.USER_NOT_FOUND);
|
.setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
|
||||||
|
.setEmail(picketlinkUser.getEmail())
|
||||||
|
.setProviderName(getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthProviderStatus validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
|
||||||
|
IdentityManager identityManager = getIdentityManager(realm);
|
||||||
|
|
||||||
UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
|
UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
|
||||||
credential.setUsername(username);
|
credential.setUsername(username);
|
||||||
credential.setPassword(new Password(password.toCharArray()));
|
credential.setPassword(new Password(password.toCharArray()));
|
||||||
identityManager.validateCredentials(credential);
|
identityManager.validateCredentials(credential);
|
||||||
if (credential.getStatus() == Credentials.Status.VALID) {
|
if (credential.getStatus() == Credentials.Status.VALID) {
|
||||||
AuthResult result = new AuthResult(AuthProviderStatus.SUCCESS);
|
return AuthProviderStatus.SUCCESS;
|
||||||
|
|
||||||
AuthenticatedUser authenticatedUser = new AuthenticatedUser(picketlinkUser.getId(), picketlinkUser.getLoginName())
|
|
||||||
.setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
|
|
||||||
.setEmail(picketlinkUser.getEmail());
|
|
||||||
result.setUser(authenticatedUser).setProviderName(getName());
|
|
||||||
return result;
|
|
||||||
} else {
|
} else {
|
||||||
return new AuthResult(AuthProviderStatus.INVALID_CREDENTIALS);
|
return AuthProviderStatus.INVALID_CREDENTIALS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,11 @@ package org.keycloak.spi.authentication;
|
||||||
*/
|
*/
|
||||||
public class AuthProviderConstants {
|
public class AuthProviderConstants {
|
||||||
|
|
||||||
|
// Model is default provider. See AuthenticationProviderModel.DEFAULT_PROVIDER
|
||||||
public static final String PROVIDER_NAME_MODEL = "model";
|
public static final String PROVIDER_NAME_MODEL = "model";
|
||||||
public static final String PROVIDER_NAME_EXTERNAL_MODEL = "externalModel";
|
public static final String PROVIDER_NAME_EXTERNAL_MODEL = "externalModel";
|
||||||
public static final String PROVIDER_NAME_PICKETLINK = "picketlink";
|
public static final String PROVIDER_NAME_PICKETLINK = "picketlink";
|
||||||
|
|
||||||
public static final String DEFAULT_PROVIDER = PROVIDER_NAME_MODEL;
|
|
||||||
|
|
||||||
// Used in external-model provider
|
// Used in external-model provider
|
||||||
public static final String EXTERNAL_REALM_ID = "externalRealmId";
|
public static final String EXTERNAL_REALM_ID = "externalRealmId";
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,6 @@ package org.keycloak.spi.authentication;
|
||||||
*/
|
*/
|
||||||
public enum AuthProviderStatus {
|
public enum AuthProviderStatus {
|
||||||
|
|
||||||
SUCCESS, INVALID_CREDENTIALS, USER_NOT_FOUND
|
SUCCESS, INVALID_CREDENTIALS, FAILED
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
package org.keycloak.spi.authentication;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class AuthResult {
|
|
||||||
|
|
||||||
// Status of authentication
|
|
||||||
private final AuthProviderStatus authProviderStatus;
|
|
||||||
|
|
||||||
// Provider, which authenticated user
|
|
||||||
private String providerName;
|
|
||||||
|
|
||||||
// filled usually only in case of successful authentication and just with some Authentication providers
|
|
||||||
private AuthenticatedUser authenticatedUser;
|
|
||||||
|
|
||||||
public AuthResult(AuthProviderStatus authProviderStatus) {
|
|
||||||
this.authProviderStatus = authProviderStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthResult setProviderName(String providerName) {
|
|
||||||
this.providerName = providerName;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthResult setUser(AuthenticatedUser user) {
|
|
||||||
this.authenticatedUser = user;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthProviderStatus getAuthProviderStatus() {
|
|
||||||
return authProviderStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProviderName() {
|
|
||||||
return providerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthenticatedUser getAuthenticatedUser() {
|
|
||||||
return authenticatedUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@ package org.keycloak.spi.authentication;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class AuthenticatedUser {
|
public class AuthUser {
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private String username;
|
private String username;
|
||||||
|
@ -11,16 +11,19 @@ public class AuthenticatedUser {
|
||||||
private String lastName;
|
private String lastName;
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
public AuthenticatedUser(String id, String username) {
|
private String providerName;
|
||||||
|
|
||||||
|
public AuthUser(String id, String username, String providerName) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
this.providerName = providerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticatedUser setId(String id) {
|
public AuthUser setId(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +32,7 @@ public class AuthenticatedUser {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticatedUser setUsername(String username) {
|
public AuthUser setUsername(String username) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -38,7 +41,7 @@ public class AuthenticatedUser {
|
||||||
return firstName;
|
return firstName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticatedUser setName(String name) {
|
public AuthUser setName(String name) {
|
||||||
int i = name.lastIndexOf(' ');
|
int i = name.lastIndexOf(' ');
|
||||||
if (i != -1) {
|
if (i != -1) {
|
||||||
firstName = name.substring(0, i);
|
firstName = name.substring(0, i);
|
||||||
|
@ -50,7 +53,7 @@ public class AuthenticatedUser {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticatedUser setName(String firstName, String lastName) {
|
public AuthUser setName(String firstName, String lastName) {
|
||||||
this.firstName = firstName;
|
this.firstName = firstName;
|
||||||
this.lastName = lastName;
|
this.lastName = lastName;
|
||||||
return this;
|
return this;
|
||||||
|
@ -64,8 +67,17 @@ public class AuthenticatedUser {
|
||||||
return email;
|
return email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticatedUser setEmail(String email) {
|
public AuthUser setEmail(String email) {
|
||||||
this.email = email;
|
this.email = email;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getProviderName() {
|
||||||
|
return providerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthUser setProviderName(String providerName) {
|
||||||
|
this.providerName = providerName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -11,6 +11,17 @@ public interface AuthenticationProvider {
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user by given username or email. Return user instance or null if user doesn't exists in this authentication provider
|
||||||
|
*
|
||||||
|
* @param realm
|
||||||
|
* @param configuration
|
||||||
|
* @param username or email
|
||||||
|
* @return found user or null if user with given username doesn't exists
|
||||||
|
* @throws AuthenticationProviderException
|
||||||
|
*/
|
||||||
|
AuthUser getUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard Authentication flow
|
* Standard Authentication flow
|
||||||
*
|
*
|
||||||
|
@ -18,7 +29,7 @@ public interface AuthenticationProvider {
|
||||||
* @param password
|
* @param password
|
||||||
* @return result of authentication, which might eventually encapsulate info about authenticated user and provider which successfully authenticated
|
* @return result of authentication, which might eventually encapsulate info about authenticated user and provider which successfully authenticated
|
||||||
*/
|
*/
|
||||||
AuthResult validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
|
AuthProviderStatus validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package org.keycloak.spi.authentication;
|
package org.keycloak.spi.authentication;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.AuthenticationLinkModel;
|
||||||
import org.keycloak.models.AuthenticationProviderModel;
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.util.ProviderLoader;
|
import org.keycloak.util.ProviderLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,7 +23,6 @@ import org.keycloak.util.ProviderLoader;
|
||||||
public class AuthenticationProviderManager {
|
public class AuthenticationProviderManager {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(AuthenticationProviderManager.class);
|
private static final Logger logger = Logger.getLogger(AuthenticationProviderManager.class);
|
||||||
private static final AuthenticationProviderModel DEFAULT_PROVIDER = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, true, Collections.EMPTY_MAP);
|
|
||||||
|
|
||||||
private final RealmModel realm;
|
private final RealmModel realm;
|
||||||
private final Map<String, AuthenticationProvider> delegates;
|
private final Map<String, AuthenticationProvider> delegates;
|
||||||
|
@ -47,56 +47,86 @@ public class AuthenticationProviderManager {
|
||||||
this.delegates = delegates;
|
this.delegates = delegates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthResult validatePassword(String username, String password) {
|
public AuthUser getUser(String username) {
|
||||||
List<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
|
List<AuthenticationProviderModel> authProviderModels = getConfiguredProviderModels(realm);
|
||||||
boolean userExists = false;
|
for (AuthenticationProviderModel providerModel : authProviderModels) {
|
||||||
|
AuthenticationProvider delegate = getProvider(providerModel.getProviderName());
|
||||||
for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
|
|
||||||
String providerName = authProviderConfig.getProviderName();
|
|
||||||
|
|
||||||
AuthenticationProvider delegate = getDelegate(providerName);
|
|
||||||
if (delegate == null) {
|
if (delegate == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AuthResult currentResult = delegate.validatePassword(realm, authProviderConfig.getConfig(), username, password);
|
AuthUser authUser = delegate.getUser(realm, providerModel.getConfig(), username);
|
||||||
logger.debugf("Authentication provider '%s' finished with '%s' for authentication of '%s'", delegate.getName(), currentResult.getAuthProviderStatus().toString(), username);
|
if (authUser != null) {
|
||||||
|
logger.debugf("User '%s' found with provider '%s'", username, providerModel.getProviderName());
|
||||||
if (currentResult.getAuthProviderStatus() == AuthProviderStatus.SUCCESS) {
|
return authUser;
|
||||||
return currentResult;
|
|
||||||
} else if (currentResult.getAuthProviderStatus() == AuthProviderStatus.INVALID_CREDENTIALS) {
|
|
||||||
userExists = true;
|
|
||||||
}
|
}
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
logger.warn(ape.getMessage(), ape);
|
logger.warn(ape.getMessage(), ape);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthProviderStatus status = userExists ? AuthProviderStatus.INVALID_CREDENTIALS : AuthProviderStatus.USER_NOT_FOUND;
|
logger.debugf("User '%s' not found with any provider", username);
|
||||||
logger.debugf("Not able to authenticate '%s' with any authentication provider. Status: '%s'", username, status.toString());
|
return null;
|
||||||
|
|
||||||
return new AuthResult(status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updatePassword(String username, String password) throws AuthenticationProviderException {
|
public AuthProviderStatus validatePassword(UserModel user, String password) {
|
||||||
List<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
|
AuthenticationLinkModel authLink = realm.getAuthenticationLink(user);
|
||||||
|
if (authLink == null) {
|
||||||
|
authLink = new AuthenticationLinkModel(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName(), user.getId());
|
||||||
|
}
|
||||||
|
|
||||||
for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
|
String providerName = authLink.getAuthProvider();
|
||||||
|
|
||||||
// Update just those, which support password update
|
AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
|
||||||
if (authProviderConfig.isPasswordUpdateSupported()) {
|
AuthenticationProvider delegate = getProvider(providerName);
|
||||||
String providerName = authProviderConfig.getProviderName();
|
if (delegate == null || providerModel == null) {
|
||||||
AuthenticationProvider delegate = getDelegate(providerName);
|
return AuthProviderStatus.FAILED;
|
||||||
if (delegate == null) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (delegate.updateCredential(realm, authProviderConfig.getConfig(), username, password)) {
|
checkCorrectAuthLink(delegate, providerModel, authLink, user.getLoginName());
|
||||||
logger.debugf("Updated password in authentication provider '%s' for user '%s'", delegate.getName(), username);
|
|
||||||
|
AuthProviderStatus currentResult = delegate.validatePassword(realm, providerModel.getConfig(), user.getLoginName(), password);
|
||||||
|
logger.debugf("Authentication provider '%s' finished with '%s' for authentication of '%s'", delegate.getName(), currentResult.toString(), user.getLoginName());
|
||||||
|
return currentResult;
|
||||||
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
logger.warn(ape.getMessage(), ape);
|
||||||
|
return AuthProviderStatus.FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean updatePassword(UserModel user, String password) throws AuthenticationProviderException {
|
||||||
|
AuthenticationLinkModel authLink = realm.getAuthenticationLink(user);
|
||||||
|
if (authLink == null) {
|
||||||
|
authLink = new AuthenticationLinkModel(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName(), user.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
String providerName = authLink.getAuthProvider();
|
||||||
|
|
||||||
|
AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
|
||||||
|
if (providerModel == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String username = user.getLoginName();
|
||||||
|
|
||||||
|
// Update just those, which support password update
|
||||||
|
if (providerModel.isPasswordUpdateSupported()) {
|
||||||
|
try {
|
||||||
|
AuthenticationProvider delegate = getProvider(providerName);
|
||||||
|
if (delegate == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCorrectAuthLink(delegate, providerModel, authLink, username);
|
||||||
|
|
||||||
|
if (delegate.updateCredential(realm,providerModel.getConfig(), user.getLoginName(), password)) {
|
||||||
|
logger.debugf("Updated password in authentication provider '%s' for user '%s'", providerName, username);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
logger.debugf("Password not updated in authentication provider '%s' for user '%s'", delegate.getName(), username);
|
logger.warnf("Password not updated in authentication provider '%s' for user '%s'", providerName, username);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} catch (AuthenticationProviderException ape) {
|
} catch (AuthenticationProviderException ape) {
|
||||||
// Rethrow it to upper layer
|
// Rethrow it to upper layer
|
||||||
|
@ -104,28 +134,51 @@ public class AuthenticationProviderManager {
|
||||||
throw ape;
|
throw ape;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debugf("Skip password update for authentication provider '%s' for user '%s'", authProviderConfig.getProviderName(), username);
|
logger.warnf("Skip password update for authentication provider '%s' for user '%s'", providerName, username);
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthenticationProvider getDelegate(String providerName) {
|
private AuthenticationProvider getProvider(String providerName) {
|
||||||
AuthenticationProvider delegate = delegates.get(providerName);
|
AuthenticationProvider delegate = delegates.get(providerName);
|
||||||
if (delegate == null) {
|
if (delegate == null) {
|
||||||
logger.warnf("Configured provider with name '%s' not found", providerName);
|
logger.warnf("Provider '%s' not available on classpath", providerName);
|
||||||
}
|
}
|
||||||
return delegate;
|
return delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AuthenticationProviderModel> getConfiguredProviders(RealmModel realm) {
|
private List<AuthenticationProviderModel> getConfiguredProviderModels(RealmModel realm) {
|
||||||
List<AuthenticationProviderModel> configuredProviders = realm.getAuthenticationProviders();
|
List<AuthenticationProviderModel> configuredProviders = realm.getAuthenticationProviders();
|
||||||
|
|
||||||
// Use model based authentication of current realm by default
|
// Use model based authentication of current realm by default
|
||||||
if (configuredProviders == null || configuredProviders.isEmpty()) {
|
if (configuredProviders == null || configuredProviders.isEmpty()) {
|
||||||
configuredProviders = new ArrayList<AuthenticationProviderModel>();
|
configuredProviders = Collections.EMPTY_LIST;
|
||||||
configuredProviders.add(DEFAULT_PROVIDER);
|
logger.warnf("No authentication providers found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuredProviders;
|
return configuredProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AuthenticationProviderModel getConfiguredProviderModel(RealmModel realm, String providerName) {
|
||||||
|
List<AuthenticationProviderModel> providers = getConfiguredProviderModels(realm);
|
||||||
|
for (AuthenticationProviderModel provider : providers) {
|
||||||
|
if (providerName.equals(provider.getProviderName())) {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warnf("Provider '%s' not configured in realm", providerName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ID of linked AuthUser is same as expected ID from authenticationLink . It should catch the case when for example user "john" was deleted in LDAP
|
||||||
|
// and then user "john" has been created again, but it's actually different user with different ID
|
||||||
|
private void checkCorrectAuthLink(AuthenticationProvider authProvider, AuthenticationProviderModel providerModel,
|
||||||
|
AuthenticationLinkModel authLinkModel, String username) throws AuthenticationProviderException {
|
||||||
|
AuthUser authUser = authProvider.getUser(realm, providerModel.getConfig(), username);
|
||||||
|
String userExternalId = authUser.getId();
|
||||||
|
if (!userExternalId.equals(authLinkModel.getAuthUserId())) {
|
||||||
|
throw new AuthenticationProviderException("ID did not match! ID from provider: " + userExternalId + ", ID from authentication link: " + authLinkModel.getAuthUserId());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
@ -44,6 +45,7 @@ import org.keycloak.testsuite.rule.WebRule;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -66,6 +68,7 @@ public class CompositeRoleTest {
|
||||||
realm.setSslNotRequired(true);
|
realm.setSslNotRequired(true);
|
||||||
realm.setEnabled(true);
|
realm.setEnabled(true);
|
||||||
realm.addRequiredCredential(UserCredentialModel.PASSWORD);
|
realm.addRequiredCredential(UserCredentialModel.PASSWORD);
|
||||||
|
realm.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
|
||||||
final RoleModel realmRole1 = realm.addRole("REALM_ROLE_1");
|
final RoleModel realmRole1 = realm.addRole("REALM_ROLE_1");
|
||||||
final RoleModel realmRole2 = realm.addRole("REALM_ROLE_2");
|
final RoleModel realmRole2 = realm.addRole("REALM_ROLE_2");
|
||||||
final RoleModel realmRole3 = realm.addRole("REALM_ROLE_3");
|
final RoleModel realmRole3 = realm.addRole("REALM_ROLE_3");
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class AuthProvidersIntegrationTest {
|
||||||
@Override
|
@Override
|
||||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
addUser(appRealm, "mary", "mary@test.com", "password-app");
|
addUser(appRealm, "mary", "mary@test.com", "password-app");
|
||||||
addUser(adminstrationRealm, "mary", "mary@admin.com", "password-admin");
|
addUser(adminstrationRealm, "mary-admin", "mary@admin.com", "password-admin");
|
||||||
|
|
||||||
AuthenticationProviderModel modelProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, false, Collections.EMPTY_MAP);
|
AuthenticationProviderModel modelProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, false, Collections.EMPTY_MAP);
|
||||||
AuthenticationProviderModel picketlinkProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
|
AuthenticationProviderModel picketlinkProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
|
||||||
|
@ -116,7 +116,7 @@ public class AuthProvidersIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void loginExternalModel() {
|
public void loginExternalModel() {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.login("mary", "password-admin");
|
loginPage.login("mary-admin", "password-admin");
|
||||||
|
|
||||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||||
|
@ -148,18 +148,18 @@ public class AuthProvidersIntegrationTest {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
changePasswordPage.open();
|
changePasswordPage.open();
|
||||||
loginPage.login("mary", "password-admin");
|
loginPage.login("mary-admin", "password-admin");
|
||||||
|
|
||||||
// Can't update to "pass" due to passwordPolicy
|
// Can't update to "pass" due to passwordPolicy
|
||||||
changePasswordPage.changePassword("password-admin", "pass", "pass");
|
changePasswordPage.changePassword("password-admin", "pass", "pass");
|
||||||
Assert.assertEquals("Invalid password: minimum length 6", profilePage.getError());
|
Assert.assertEquals("Invalid password: minimum length 6", profilePage.getError());
|
||||||
|
|
||||||
changePasswordPage.changePassword("password-app", "password-updated", "password-updated");
|
changePasswordPage.changePassword("password-admin", "password-updated", "password-updated");
|
||||||
Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
|
Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
|
||||||
changePasswordPage.logout();
|
changePasswordPage.logout();
|
||||||
|
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.login("mary", "password-updated");
|
loginPage.login("mary-admin", "password-updated");
|
||||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
|
Loading…
Reference in a new issue