Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
bc39bb32d4
64 changed files with 1268 additions and 371 deletions
|
@ -110,7 +110,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
|||
|
||||
String value = getJsonValue(mapperModel, context);
|
||||
if (value != null) {
|
||||
user.setAttribute(attribute, value);
|
||||
user.setSingleAttribute(attribute, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,16 +3,14 @@ package org.keycloak.broker.oidc.mappers;
|
|||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -76,7 +74,7 @@ public class UserAttributeMapper extends AbstractClaimMapper {
|
|||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||
Object value = getClaimValue(mapperModel, context);
|
||||
if (value != null) {
|
||||
user.setAttribute(attribute, value.toString());
|
||||
user.setSingleAttribute(attribute, value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,9 +82,9 @@ public class UserAttributeMapper extends AbstractClaimMapper {
|
|||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||
Object value = getClaimValue(mapperModel, context);
|
||||
String current = user.getAttribute(attribute);
|
||||
String current = user.getFirstAttribute(attribute);
|
||||
if (value != null && !value.equals(current)) {
|
||||
user.setAttribute(attribute, value.toString());
|
||||
user.setSingleAttribute(attribute, value.toString());
|
||||
} else if (value == null) {
|
||||
user.removeAttribute(attribute);
|
||||
}
|
||||
|
|
|
@ -2,17 +2,14 @@ package org.keycloak.broker.saml.mappers;
|
|||
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.broker.saml.SAMLEndpoint;
|
||||
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
|
||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
||||
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
|
@ -87,7 +84,7 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
|
|||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||
Object value = getAttribute(mapperModel, context);
|
||||
if (value != null) {
|
||||
user.setAttribute(attribute, value.toString());
|
||||
user.setSingleAttribute(attribute, value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,9 +112,9 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
|
|||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||
Object value = getAttribute(mapperModel, context);
|
||||
String current = user.getAttribute(attribute);
|
||||
String current = user.getFirstAttribute(attribute);
|
||||
if (value != null && !value.equals(current)) {
|
||||
user.setAttribute(attribute, value.toString());
|
||||
user.setSingleAttribute(attribute, value.toString());
|
||||
} else if (value == null) {
|
||||
user.removeAttribute(attribute);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
<changeSet author="bburke@redhat.com" id="1.4.0">
|
||||
<delete tableName="CLIENT_SESSION_AUTH_STATUS"/>
|
||||
<delete tableName="CLIENT_SESSION_ROLE"/>
|
||||
<delete tableName="CLIENT_SESSION_PROT_MAPPER"/>
|
||||
<delete tableName="CLIENT_SESSION_NOTE"/>
|
||||
<delete tableName="CLIENT_SESSION"/>
|
||||
|
@ -22,6 +23,12 @@
|
|||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
<addColumn tableName="USER_ATTRIBUTE">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
|
||||
<dropColumn tableName="AUTHENTICATOR" columnName="PROVIDER_ID"/>
|
||||
<renameTable oldTableName="AUTHENTICATOR_CONFIG" newTableName="AUTHENTICATOR_CONFIG_ENTRY"/>
|
||||
<renameTable oldTableName="AUTHENTICATOR" newTableName="AUTHENTICATOR_CONFIG"/>
|
||||
|
@ -110,6 +117,8 @@
|
|||
</column>
|
||||
</createTable>
|
||||
|
||||
<dropPrimaryKey constraintName="CONSTRAINT_6" tableName="USER_ATTRIBUTE"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_USER_ATTRIBUTE_PK" tableName="USER_ATTRIBUTE"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_REQ_ACT_PRV_PK" tableName="REQUIRED_ACTION_PROVIDER"/>
|
||||
<addPrimaryKey columnNames="REQUIRED_ACTION_ID, NAME" constraintName="CONSTRAINT_REQ_ACT_CFG_PK" tableName="REQUIRED_ACTION_CONFIG"/>
|
||||
<addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTR_CL_USR_SES_NOTE" tableName="CLIENT_USER_SESSION_NOTE"/>
|
||||
|
|
|
@ -46,7 +46,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
|
||||
"org.keycloak.models.entities.AuthenticationExecutionEntity",
|
||||
"org.keycloak.models.entities.AuthenticationFlowEntity",
|
||||
"org.keycloak.models.entities.AuthenticatorEntity",
|
||||
"org.keycloak.models.entities.AuthenticatorConfigEntity",
|
||||
"org.keycloak.models.entities.RequiredActionProviderEntity",
|
||||
};
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.connections.mongo.api.types;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -14,9 +15,9 @@ public class MapperContext<T, S> {
|
|||
private final Class<? extends S> expectedReturnType;
|
||||
|
||||
// in case that expected return type is generic type (like "List<String>"), then genericTypes could contain list of expected generic arguments
|
||||
private final List<Class<?>> genericTypes;
|
||||
private final List<Type> genericTypes;
|
||||
|
||||
public MapperContext(T objectToConvert, Class<? extends S> expectedReturnType, List<Class<?>> genericTypes) {
|
||||
public MapperContext(T objectToConvert, Class<? extends S> expectedReturnType, List<Type> genericTypes) {
|
||||
this.objectToConvert = objectToConvert;
|
||||
this.expectedReturnType = expectedReturnType;
|
||||
this.genericTypes = genericTypes;
|
||||
|
@ -30,7 +31,7 @@ public class MapperContext<T, S> {
|
|||
return expectedReturnType;
|
||||
}
|
||||
|
||||
public List<Class<?>> getGenericTypes() {
|
||||
public List<Type> getGenericTypes() {
|
||||
return genericTypes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public class BasicDBListMapper implements Mapper<BasicDBList, List> {
|
|||
public List convertObject(MapperContext<BasicDBList, List> context) {
|
||||
BasicDBList dbList = context.getObjectToConvert();
|
||||
ArrayList<Object> appObjects = new ArrayList<Object>();
|
||||
Class<?> expectedListElementType = context.getGenericTypes().get(0);
|
||||
Class<?> expectedListElementType = (Class<?>) context.getGenericTypes().get(0);
|
||||
|
||||
for (Object dbObject : dbList) {
|
||||
MapperContext<Object, Object> newContext = new MapperContext<Object, Object>(dbObject, expectedListElementType, null);
|
||||
|
|
|
@ -23,7 +23,7 @@ public class BasicDBListToSetMapper implements Mapper<BasicDBList, Set> {
|
|||
public Set convertObject(MapperContext<BasicDBList, Set> context) {
|
||||
BasicDBList dbList = context.getObjectToConvert();
|
||||
Set<Object> appObjects = new HashSet<Object>();
|
||||
Class<?> expectedListElementType = context.getGenericTypes().get(0);
|
||||
Class<?> expectedListElementType = (Class<?>) context.getGenericTypes().get(0);
|
||||
|
||||
for (Object dbObject : dbList) {
|
||||
MapperContext<Object, Object> newContext = new MapperContext<Object, Object>(dbObject, expectedListElementType, null);
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.keycloak.util.reflections.Types;
|
|||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -87,10 +88,14 @@ public class BasicDBObjectMapper<S> implements Mapper<BasicDBObject, S> {
|
|||
ParameterizedType parameterized = (ParameterizedType) type;
|
||||
Type[] genericTypeArguments = parameterized.getActualTypeArguments();
|
||||
|
||||
List<Class<?>> genericTypes = new ArrayList<Class<?>>();
|
||||
for (Type genericType : genericTypeArguments) {
|
||||
genericTypes.add((Class<?>)genericType);
|
||||
}
|
||||
List<Type> genericTypes = Arrays.asList(genericTypeArguments);
|
||||
/*for (Type genericType : genericTypeArguments) {
|
||||
if (genericType instanceof Class) {
|
||||
genericTypes.add((Class<?>) genericType);
|
||||
} else {
|
||||
System.out.println("foo");
|
||||
}
|
||||
}*/
|
||||
|
||||
Class<?> expectedReturnType = (Class<?>)parameterized.getRawType();
|
||||
context = new MapperContext<Object, Object>(valueFromDB, expectedReturnType, genericTypes);
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||
import org.keycloak.util.MultivaluedHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -21,7 +25,9 @@ public class UserRepresentation {
|
|||
protected String lastName;
|
||||
protected String email;
|
||||
protected String federationLink;
|
||||
protected Map<String, String> attributes;
|
||||
|
||||
// Currently there is Map<String, List<String>> but for backwards compatibility, we also need to support Map<String, String>
|
||||
protected Map<String, Object> attributes;
|
||||
protected List<CredentialRepresentation> credentials;
|
||||
protected List<String> requiredActions;
|
||||
protected List<FederatedIdentityRepresentation> federatedIdentities;
|
||||
|
@ -106,17 +112,23 @@ public class UserRepresentation {
|
|||
this.emailVerified = emailVerified;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
// This method can be removed once we can remove backwards compatibility with Keycloak 1.3 (then getAttributes() can be changed to return Map<String, List<String>> )
|
||||
@JsonIgnore
|
||||
public Map<String, List<String>> getAttributesAsListValues() {
|
||||
return (Map) attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, Object> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public UserRepresentation attribute(String name, String value) {
|
||||
if (this.attributes == null) attributes = new HashMap<String, String>();
|
||||
attributes.put(name, value);
|
||||
public UserRepresentation singleAttribute(String name, String value) {
|
||||
if (this.attributes == null) attributes = new HashMap<>();
|
||||
attributes.put(name, Arrays.asList(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
|||
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
|
||||
|
||||
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
|
||||
return kerberosPrincipal.equals(local.getAttribute(KERBEROS_PRINCIPAL));
|
||||
return kerberosPrincipal.equals(local.getFirstAttribute(KERBEROS_PRINCIPAL));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -229,7 +229,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
|||
return proxied;
|
||||
} else {
|
||||
logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
|
||||
"] but kerberos principal is not correct. Kerberos principal on user is: " + user.getAttribute(KERBEROS_PRINCIPAL));
|
||||
"] but kerberos principal is not correct. Kerberos principal on user is: " + user.getFirstAttribute(KERBEROS_PRINCIPAL));
|
||||
logger.warn("Will re-create user");
|
||||
session.userStorage().removeUser(realm, user);
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
|||
user.setEnabled(true);
|
||||
user.setEmail(email);
|
||||
user.setFederationLink(model.getId());
|
||||
user.setAttribute(KERBEROS_PRINCIPAL, username + "@" + kerberosConfig.getKerberosRealm());
|
||||
user.setSingleAttribute(KERBEROS_PRINCIPAL, username + "@" + kerberosConfig.getKerberosRealm());
|
||||
|
||||
if (kerberosConfig.isUpdateProfileFirstLogin()) {
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
|
||||
|
|
|
@ -6,7 +6,7 @@ import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
|||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
||||
|
@ -51,7 +51,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
protected EditMode editMode;
|
||||
protected LDAPProviderKerberosConfig kerberosConfig;
|
||||
|
||||
protected final Set<String> supportedCredentialTypes = new HashSet<String>();
|
||||
protected final Set<String> supportedCredentialTypes = new HashSet<>();
|
||||
|
||||
public LDAPFederationProvider(LDAPFederationProviderFactory factory, KeycloakSession session, UserFederationProviderModel model, LDAPIdentityStore ldapIdentityStore) {
|
||||
this.factory = factory;
|
||||
|
@ -145,8 +145,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server");
|
||||
|
||||
LDAPObject ldapObject = LDAPUtils.addUserToLDAP(this, realm, user);
|
||||
user.setAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
|
||||
user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
|
||||
user.setSingleAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
|
||||
user.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
|
||||
|
||||
return proxy(realm, user, ldapObject);
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
}
|
||||
|
||||
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
|
||||
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
// Mapper should replace parameter with correct LDAP mapped attributes
|
||||
|
@ -229,10 +229,10 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
if (ldapUser == null) {
|
||||
return null;
|
||||
}
|
||||
if (ldapUser.getUuid().equals(local.getAttribute(LDAPConstants.LDAP_ID))) {
|
||||
if (ldapUser.getUuid().equals(local.getFirstAttribute(LDAPConstants.LDAP_ID))) {
|
||||
return ldapUser;
|
||||
} else {
|
||||
logger.warnf("LDAP User invalid. ID doesn't match. ID from LDAP [%s], ID from local DB: [%s]", ldapUser.getUuid(), local.getAttribute(LDAPConstants.LDAP_ID));
|
||||
logger.warnf("LDAP User invalid. ID doesn't match. ID from LDAP [%s], LDAP ID from local DB: [%s]", ldapUser.getUuid(), local.getFirstAttribute(LDAPConstants.LDAP_ID));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -271,8 +271,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
|
||||
String userDN = ldapUser.getDn().toString();
|
||||
imported.setFederationLink(model.getId());
|
||||
imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
|
||||
imported.setAttribute(LDAPConstants.LDAP_ENTRY_DN, userDN);
|
||||
imported.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
|
||||
imported.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, userDN);
|
||||
|
||||
logger.debugf("Imported new user from LDAP to Keycloak DB. Username: [%s], Email: [%s], LDAP_ID: [%s], LDAP Entry DN: [%s]", imported.getUsername(), imported.getEmail(),
|
||||
ldapUser.getUuid(), userDN);
|
||||
|
@ -280,7 +280,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
}
|
||||
|
||||
protected LDAPObject queryByEmail(RealmModel realm, String email) {
|
||||
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
// Mapper should replace "email" in parameter name with correct LDAP mapped attribute
|
||||
|
@ -395,7 +395,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
importUserFromLDAP(realm, ldapUser);
|
||||
syncResult.increaseAdded();
|
||||
} else {
|
||||
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) {
|
||||
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getFirstAttribute(LDAPConstants.LDAP_ID)))) {
|
||||
|
||||
// Update keycloak user
|
||||
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
|
||||
|
@ -435,7 +435,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
return proxy(realm, user, ldapObject);
|
||||
} else {
|
||||
logger.warnf("User with username [%s] aready exists and is linked to provider [%s] but is not valid. Stale LDAP_ID on local user is: %s",
|
||||
username, model.getDisplayName(), user.getAttribute(LDAPConstants.LDAP_ID));
|
||||
username, model.getDisplayName(), user.getFirstAttribute(LDAPConstants.LDAP_ID));
|
||||
logger.warn("Will re-create user");
|
||||
session.userStorage().removeUser(realm, user);
|
||||
}
|
||||
|
@ -448,7 +448,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
}
|
||||
|
||||
public LDAPObject loadLDAPUserByUsername(RealmModel realm, String username) {
|
||||
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
|
||||
|
|
|
@ -9,7 +9,7 @@ import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
|||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
|
||||
|
@ -84,14 +84,17 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
|
||||
boolean activeDirectory = ldapConfig.isActiveDirectory();
|
||||
UserFederationProvider.EditMode editMode = ldapConfig.getEditMode();
|
||||
String readOnly = String.valueOf(editMode==UserFederationProvider.EditMode.READ_ONLY || editMode== UserFederationProvider.EditMode.UNSYNCED);
|
||||
String readOnly = String.valueOf(editMode == UserFederationProvider.EditMode.READ_ONLY || editMode == UserFederationProvider.EditMode.UNSYNCED);
|
||||
String usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute();
|
||||
|
||||
String alwaysReadValueFromLDAP = String.valueOf(editMode==UserFederationProvider.EditMode.READ_ONLY || editMode== UserFederationProvider.EditMode.WRITABLE);
|
||||
|
||||
UserFederationMapperModel mapperModel;
|
||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
// CN is typically used as RDN for Active Directory deployments
|
||||
|
@ -103,7 +106,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
} else {
|
||||
|
@ -113,13 +117,15 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
} else {
|
||||
|
||||
|
@ -134,20 +140,23 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
}
|
||||
|
||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("last name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
String createTimestampLdapAttrName = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
|
||||
|
@ -157,14 +166,16 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creation date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "true");
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
// map modifyTimeStamp as read-only
|
||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modify date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "true");
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
}
|
||||
|
||||
|
@ -173,7 +184,7 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
|
||||
logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getDisplayName());
|
||||
|
||||
LDAPIdentityQuery userQuery = createQuery(sessionFactory, realmId, model);
|
||||
LDAPQuery userQuery = createQuery(sessionFactory, realmId, model);
|
||||
UserFederationSyncResult syncResult = syncImpl(sessionFactory, userQuery, realmId, model);
|
||||
|
||||
// TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync?
|
||||
|
@ -192,7 +203,7 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
Condition modifyCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.MODIFY_TIMESTAMP), lastSync);
|
||||
Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition);
|
||||
|
||||
LDAPIdentityQuery userQuery = createQuery(sessionFactory, realmId, model);
|
||||
LDAPQuery userQuery = createQuery(sessionFactory, realmId, model);
|
||||
userQuery.where(orCondition);
|
||||
UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model);
|
||||
|
||||
|
@ -200,7 +211,7 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
return result;
|
||||
}
|
||||
|
||||
protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, LDAPIdentityQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) {
|
||||
protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, LDAPQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) {
|
||||
|
||||
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
|
||||
|
||||
|
@ -243,9 +254,9 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
return syncResult;
|
||||
}
|
||||
|
||||
private LDAPIdentityQuery createQuery(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
|
||||
private LDAPQuery createQuery(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
|
||||
class QueryHolder {
|
||||
LDAPIdentityQuery query;
|
||||
LDAPQuery query;
|
||||
}
|
||||
|
||||
final QueryHolder queryHolder = new QueryHolder();
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package org.keycloak.federation.ldap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.federation.ldap.idm.model.LDAPDn;
|
||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
||||
import org.keycloak.models.ModelException;
|
||||
|
@ -45,8 +44,8 @@ public class LDAPUtils {
|
|||
return ldapUser;
|
||||
}
|
||||
|
||||
public static LDAPIdentityQuery createQueryForUserSearch(LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||
LDAPIdentityQuery ldapQuery = new LDAPIdentityQuery(ldapProvider);
|
||||
public static LDAPQuery createQueryForUserSearch(LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||
LDAPQuery ldapQuery = new LDAPQuery(ldapProvider);
|
||||
LDAPConfig config = ldapProvider.getLdapIdentityStore().getConfig();
|
||||
ldapQuery.setSearchScope(config.getSearchScope());
|
||||
ldapQuery.setSearchDn(config.getUsersDn());
|
||||
|
|
|
@ -2,22 +2,36 @@ package org.keycloak.federation.ldap.idm.model;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class LDAPObject {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(LDAPObject.class);
|
||||
|
||||
private String uuid;
|
||||
private LDAPDn dn;
|
||||
private String rdnAttributeName;
|
||||
|
||||
private final List<String> objectClasses = new LinkedList<String>();
|
||||
private final List<String> readOnlyAttributeNames = new LinkedList<String>();
|
||||
private final Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
private final List<String> objectClasses = new LinkedList<>();
|
||||
|
||||
// NOTE: names of read-only attributes are lower-cased to avoid case sensitivity issues
|
||||
private final List<String> readOnlyAttributeNames = new LinkedList<>();
|
||||
|
||||
private final Map<String, Set<String>> attributes = new HashMap<>();
|
||||
|
||||
// Copy of "attributes" containing lower-cased keys
|
||||
private final Map<String, Set<String>> lowerCasedAttributes = new HashMap<>();
|
||||
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
|
@ -49,7 +63,7 @@ public class LDAPObject {
|
|||
}
|
||||
|
||||
public void addReadOnlyAttributeName(String readOnlyAttribute) {
|
||||
readOnlyAttributeNames.add(readOnlyAttribute);
|
||||
readOnlyAttributeNames.add(readOnlyAttribute.toLowerCase());
|
||||
}
|
||||
|
||||
public String getRdnAttributeName() {
|
||||
|
@ -60,30 +74,37 @@ public class LDAPObject {
|
|||
this.rdnAttributeName = rdnAttributeName;
|
||||
}
|
||||
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
public void setSingleAttribute(String attributeName, String attributeValue) {
|
||||
Set<String> asSet = new LinkedHashSet<>();
|
||||
asSet.add(attributeValue);
|
||||
setAttribute(attributeName, asSet);
|
||||
}
|
||||
|
||||
public void setAttribute(String attributeName, Set<String> attributeValue) {
|
||||
attributes.put(attributeName, attributeValue);
|
||||
lowerCasedAttributes.put(attributeName.toLowerCase(), attributeValue);
|
||||
}
|
||||
|
||||
public void removeAttribute(String name) {
|
||||
attributes.remove(name);
|
||||
}
|
||||
|
||||
|
||||
public Object getAttribute(String name) {
|
||||
return attributes.get(name);
|
||||
}
|
||||
|
||||
// Case-insensitive
|
||||
public String getAttributeAsString(String name) {
|
||||
Object attrValue = attributes.get(name);
|
||||
if (attrValue != null && !(attrValue instanceof String)) {
|
||||
throw new IllegalStateException("Expected String but attribute was " + attrValue + " of type " + attrValue.getClass().getName());
|
||||
Set<String> attrValue = lowerCasedAttributes.get(name.toLowerCase());
|
||||
if (attrValue == null || attrValue.size() == 0) {
|
||||
return null;
|
||||
} else if (attrValue.size() > 1) {
|
||||
logger.warnf("Expected String but attribute '%s' has more values '%s' on object '%s' . Returning just first value", name, attrValue, dn);
|
||||
}
|
||||
|
||||
return (String) attrValue;
|
||||
return attrValue.iterator().next();
|
||||
}
|
||||
|
||||
// Case-insensitive. Return null if there is not value of attribute with given name or set with all values otherwise
|
||||
public Set<String> getAttributeAsSet(String name) {
|
||||
Set<String> values = lowerCasedAttributes.get(name.toLowerCase());
|
||||
return (values == null) ? null : new LinkedHashSet<>(values);
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Object> getAttributes() {
|
||||
public Map<String, Set<String>> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import static java.util.Collections.unmodifiableSet;
|
|||
*
|
||||
* @author Shane Bryzak
|
||||
*/
|
||||
public class LDAPIdentityQuery {
|
||||
public class LDAPQuery {
|
||||
|
||||
private final LDAPFederationProvider ldapFedProvider;
|
||||
|
||||
|
@ -40,6 +40,7 @@ public class LDAPIdentityQuery {
|
|||
private final Set<String> returningLdapAttributes = new LinkedHashSet<String>();
|
||||
|
||||
// Contains just those returningLdapAttributes, which are read-only. They will be marked as read-only in returned LDAPObject instances as well
|
||||
// NOTE: names of attributes are lower-cased to avoid case sensitivity issues (LDAP searching is usually case-insensitive, so we want to be as well)
|
||||
private final Set<String> returningReadOnlyLdapAttributes = new LinkedHashSet<String>();
|
||||
private final Set<String> objectClasses = new LinkedHashSet<String>();
|
||||
|
||||
|
@ -47,46 +48,46 @@ public class LDAPIdentityQuery {
|
|||
|
||||
private int searchScope = SearchControls.SUBTREE_SCOPE;
|
||||
|
||||
public LDAPIdentityQuery(LDAPFederationProvider ldapProvider) {
|
||||
public LDAPQuery(LDAPFederationProvider ldapProvider) {
|
||||
this.ldapFedProvider = ldapProvider;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery where(Condition... condition) {
|
||||
public LDAPQuery where(Condition... condition) {
|
||||
this.conditions.addAll(Arrays.asList(condition));
|
||||
return this;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery sortBy(Sort... sorts) {
|
||||
public LDAPQuery sortBy(Sort... sorts) {
|
||||
this.ordering.addAll(Arrays.asList(sorts));
|
||||
return this;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery setSearchDn(String searchDn) {
|
||||
public LDAPQuery setSearchDn(String searchDn) {
|
||||
this.searchDn = searchDn;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery addObjectClasses(Collection<String> objectClasses) {
|
||||
public LDAPQuery addObjectClasses(Collection<String> objectClasses) {
|
||||
this.objectClasses.addAll(objectClasses);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery addReturningLdapAttribute(String ldapAttributeName) {
|
||||
public LDAPQuery addReturningLdapAttribute(String ldapAttributeName) {
|
||||
this.returningLdapAttributes.add(ldapAttributeName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery addReturningReadOnlyLdapAttribute(String ldapAttributeName) {
|
||||
this.returningReadOnlyLdapAttributes.add(ldapAttributeName);
|
||||
public LDAPQuery addReturningReadOnlyLdapAttribute(String ldapAttributeName) {
|
||||
this.returningReadOnlyLdapAttributes.add(ldapAttributeName.toLowerCase());
|
||||
return this;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery addMappers(Collection<UserFederationMapperModel> mappers) {
|
||||
public LDAPQuery addMappers(Collection<UserFederationMapperModel> mappers) {
|
||||
this.mappers.addAll(mappers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery setSearchScope(int searchScope) {
|
||||
public LDAPQuery setSearchScope(int searchScope) {
|
||||
this.searchScope = searchScope;
|
||||
return this;
|
||||
}
|
||||
|
@ -169,17 +170,17 @@ public class LDAPIdentityQuery {
|
|||
return ldapFedProvider.getLdapIdentityStore().countQueryResults(this);
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery setOffset(int offset) {
|
||||
public LDAPQuery setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery setLimit(int limit) {
|
||||
public LDAPQuery setLimit(int limit) {
|
||||
this.limit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery setPaginationContext(byte[] paginationContext) {
|
||||
public LDAPQuery setPaginationContext(byte[] paginationContext) {
|
||||
this.paginationContext = paginationContext;
|
||||
return this;
|
||||
}
|
|
@ -4,7 +4,7 @@ import java.util.List;
|
|||
|
||||
import org.keycloak.federation.ldap.LDAPConfig;
|
||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
|
||||
/**
|
||||
* IdentityStore representation providing minimal SPI
|
||||
|
@ -48,9 +48,9 @@ public interface IdentityStore {
|
|||
|
||||
// Identity query
|
||||
|
||||
List<LDAPObject> fetchQueryResults(LDAPIdentityQuery LDAPIdentityQuery);
|
||||
List<LDAPObject> fetchQueryResults(LDAPQuery LDAPQuery);
|
||||
|
||||
int countQueryResults(LDAPIdentityQuery LDAPIdentityQuery);
|
||||
int countQueryResults(LDAPQuery LDAPQuery);
|
||||
|
||||
// // Relationship query
|
||||
//
|
||||
|
|
|
@ -4,11 +4,11 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
|
@ -28,7 +28,7 @@ import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
|||
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.BetweenCondition;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.InCondition;
|
||||
|
@ -108,7 +108,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
|
||||
|
||||
@Override
|
||||
public List<LDAPObject> fetchQueryResults(LDAPIdentityQuery identityQuery) {
|
||||
public List<LDAPObject> fetchQueryResults(LDAPQuery identityQuery) {
|
||||
if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
|
||||
throw new ModelException("LDAP Identity Store does not yet support sorted queries.");
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int countQueryResults(LDAPIdentityQuery identityQuery) {
|
||||
public int countQueryResults(LDAPQuery identityQuery) {
|
||||
int limit = identityQuery.getLimit();
|
||||
int offset = identityQuery.getOffset();
|
||||
|
||||
|
@ -247,7 +247,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
|
||||
// ************ END CREDENTIALS AND USER SPECIFIC STUFF
|
||||
|
||||
protected StringBuilder createIdentityTypeSearchFilter(final LDAPIdentityQuery identityQuery) {
|
||||
protected StringBuilder createIdentityTypeSearchFilter(final LDAPQuery identityQuery) {
|
||||
StringBuilder filter = new StringBuilder();
|
||||
|
||||
for (Condition condition : identityQuery.getConditions()) {
|
||||
|
@ -382,12 +382,6 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
|
||||
NamingEnumeration<? extends Attribute> ldapAttributes = attributes.getAll();
|
||||
|
||||
// Exact name of attributes might be different
|
||||
List<String> uppercasedReadOnlyAttrNames = new ArrayList<>();
|
||||
for (String readonlyAttr : readOnlyAttrNames) {
|
||||
uppercasedReadOnlyAttrNames.add(readonlyAttr.toUpperCase());
|
||||
}
|
||||
|
||||
while (ldapAttributes.hasMore()) {
|
||||
Attribute ldapAttribute = ldapAttributes.next();
|
||||
|
||||
|
@ -403,23 +397,20 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
Object uuidValue = ldapAttribute.get();
|
||||
ldapObject.setUuid(this.operationManager.decodeEntryUUID(uuidValue));
|
||||
} else {
|
||||
Set<String> attrValues = new TreeSet<>();
|
||||
Set<String> attrValues = new LinkedHashSet<>();
|
||||
NamingEnumeration<?> enumm = ldapAttribute.getAll();
|
||||
while (enumm.hasMoreElements()) {
|
||||
String attrVal = enumm.next().toString();
|
||||
String attrVal = enumm.next().toString().trim();
|
||||
attrValues.add(attrVal);
|
||||
}
|
||||
|
||||
if (ldapAttributeName.equalsIgnoreCase(LDAPConstants.OBJECT_CLASS)) {
|
||||
ldapObject.setObjectClasses(attrValues);
|
||||
} else {
|
||||
if (attrValues.size() == 1) {
|
||||
ldapObject.setAttribute(ldapAttributeName, attrValues.iterator().next());
|
||||
} else {
|
||||
ldapObject.setAttribute(ldapAttributeName, attrValues);
|
||||
}
|
||||
ldapObject.setAttribute(ldapAttributeName, attrValues);
|
||||
|
||||
if (uppercasedReadOnlyAttrNames.contains(ldapAttributeName.toUpperCase())) {
|
||||
// readOnlyAttrNames are lower-cased
|
||||
if (readOnlyAttrNames.contains(ldapAttributeName.toLowerCase())) {
|
||||
ldapObject.addReadOnlyAttributeName(ldapAttributeName);
|
||||
}
|
||||
}
|
||||
|
@ -440,28 +431,25 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
protected BasicAttributes extractAttributes(LDAPObject ldapObject, boolean isCreate) {
|
||||
BasicAttributes entryAttributes = new BasicAttributes();
|
||||
|
||||
for (Map.Entry<String, Object> attrEntry : ldapObject.getAttributes().entrySet()) {
|
||||
for (Map.Entry<String, Set<String>> attrEntry : ldapObject.getAttributes().entrySet()) {
|
||||
String attrName = attrEntry.getKey();
|
||||
Object attrValue = attrEntry.getValue();
|
||||
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName) && (isCreate || !ldapObject.getRdnAttributeName().equalsIgnoreCase(attrName))) {
|
||||
Set<String> attrValue = attrEntry.getValue();
|
||||
|
||||
if (String.class.isInstance(attrValue)) {
|
||||
if (attrValue.toString().trim().length() == 0) {
|
||||
attrValue = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
|
||||
}
|
||||
entryAttributes.put(attrName, attrValue);
|
||||
} else if (Collection.class.isInstance(attrValue)) {
|
||||
BasicAttribute attr = new BasicAttribute(attrName);
|
||||
Collection<String> valueCollection = (Collection<String>) attrValue;
|
||||
for (String val : valueCollection) {
|
||||
// ldapObject.getReadOnlyAttributeNames() are lower-cased
|
||||
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName.toLowerCase()) && (isCreate || !ldapObject.getRdnAttributeName().equalsIgnoreCase(attrName))) {
|
||||
BasicAttribute attr = new BasicAttribute(attrName);
|
||||
if (attrValue == null) {
|
||||
// Adding empty value as we don't know if attribute is mandatory in LDAP
|
||||
attr.add(LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
|
||||
} else {
|
||||
for (String val : attrValue) {
|
||||
if (val == null || val.toString().trim().length() == 0) {
|
||||
val = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
|
||||
}
|
||||
attr.add(val);
|
||||
}
|
||||
entryAttributes.put(attr);
|
||||
} else if (attrValue == null || attrValue.toString().trim().length() == 0) {
|
||||
entryAttributes.put(attrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
|
||||
} else {
|
||||
throw new ModelException("Unexpected type of value of argument " + attrName + ". Value is " + attrValue);
|
||||
}
|
||||
entryAttributes.put(attr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import javax.naming.ldap.PagedResultsResponseControl;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.federation.ldap.LDAPConfig;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.ModelException;
|
||||
|
||||
|
@ -165,7 +165,7 @@ public class LDAPOperationManager {
|
|||
}
|
||||
}
|
||||
|
||||
public List<SearchResult> searchPaginated(final String baseDN, final String filter, final LDAPIdentityQuery identityQuery) throws NamingException {
|
||||
public List<SearchResult> searchPaginated(final String baseDN, final String filter, final LDAPQuery identityQuery) throws NamingException {
|
||||
final List<SearchResult> result = new ArrayList<SearchResult>();
|
||||
final SearchControls cons = getSearchControls(identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
|||
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
|
@ -29,8 +29,12 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
|
||||
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
||||
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
|
||||
if (fullName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fullName = fullName.trim();
|
||||
if (fullName != null && !fullName.trim().isEmpty()) {
|
||||
if (!fullName.isEmpty()) {
|
||||
int lastSpaceIndex = fullName.lastIndexOf(" ");
|
||||
if (lastSpaceIndex == -1) {
|
||||
user.setLastName(fullName);
|
||||
|
@ -45,7 +49,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
|
||||
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
||||
String fullName = getFullName(localUser.getFirstName(), localUser.getLastName());
|
||||
ldapUser.setAttribute(ldapFullNameAttrName, fullName);
|
||||
ldapUser.setSingleAttribute(ldapFullNameAttrName, fullName);
|
||||
|
||||
if (isReadOnly(mapperModel)) {
|
||||
ldapUser.addReadOnlyAttributeName(ldapFullNameAttrName);
|
||||
|
@ -80,7 +84,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
ensureTransactionStarted();
|
||||
|
||||
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
||||
ldapUser.setAttribute(ldapFullNameAttrName, fullName);
|
||||
ldapUser.setSingleAttribute(ldapFullNameAttrName, fullName);
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -92,7 +96,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
|
||||
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
|
||||
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
||||
query.addReturningLdapAttribute(ldapFullNameAttrName);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.keycloak.federation.ldap.mappers;
|
|||
|
||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.mappers.UserFederationMapper;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
|
@ -58,5 +58,5 @@ public interface LDAPFederationMapper extends UserFederationMapper {
|
|||
* @param mapperModel
|
||||
* @param query
|
||||
*/
|
||||
void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query);
|
||||
void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import org.keycloak.federation.ldap.idm.model.LDAPDn;
|
|||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
|
@ -58,7 +58,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
|
||||
// List of IDs of UserFederationMapperModels where syncRolesFromLDAP was already called in this KeycloakSession. This is to improve performance
|
||||
// TODO: Rather address this with caching at LDAPIdentityStore level?
|
||||
private Set<String> rolesSyncedModels = new TreeSet<String>();
|
||||
private Set<String> rolesSyncedModels = new TreeSet<>();
|
||||
|
||||
@Override
|
||||
public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
|
||||
|
@ -95,7 +95,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
if (!rolesSyncedModels.contains(mapperModel.getId())) {
|
||||
logger.debugf("Syncing roles from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
|
||||
|
||||
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
|
||||
// Send query
|
||||
List<LDAPObject> ldapRoles = ldapQuery.getResultList();
|
||||
|
@ -115,8 +115,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
}
|
||||
}
|
||||
|
||||
public LDAPIdentityQuery createRoleQuery(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
|
||||
LDAPIdentityQuery ldapQuery = new LDAPIdentityQuery(ldapProvider);
|
||||
public LDAPQuery createRoleQuery(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
|
||||
LDAPQuery ldapQuery = new LDAPQuery(ldapProvider);
|
||||
|
||||
// For now, use same search scope, which is configured "globally" and used for user's search.
|
||||
ldapQuery.setSearchScope(ldapProvider.getLdapIdentityStore().getConfig().getSearchScope());
|
||||
|
@ -178,7 +178,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
}
|
||||
String[] objClasses = objectClasses.split(",");
|
||||
|
||||
Set<String> trimmed = new HashSet<String>();
|
||||
Set<String> trimmed = new HashSet<>();
|
||||
for (String objectClass : objClasses) {
|
||||
objectClass = objectClass.trim();
|
||||
if (objectClass.length() > 0) {
|
||||
|
@ -202,7 +202,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
String roleNameAttribute = getRoleNameLdapAttribute(mapperModel);
|
||||
ldapObject.setRdnAttributeName(roleNameAttribute);
|
||||
ldapObject.setObjectClasses(getRoleObjectClasses(mapperModel, ldapProvider));
|
||||
ldapObject.setAttribute(roleNameAttribute, roleName);
|
||||
ldapObject.setSingleAttribute(roleNameAttribute, roleName);
|
||||
|
||||
LDAPDn roleDn = LDAPDn.fromString(getRolesDn(mapperModel));
|
||||
roleDn.addFirst(roleNameAttribute, roleName);
|
||||
|
@ -220,6 +220,15 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
}
|
||||
|
||||
Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
|
||||
|
||||
// Remove membership placeholder if present
|
||||
for (String membership : memberships) {
|
||||
if (membership.trim().length() == 0) {
|
||||
memberships.remove(membership);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
memberships.add(ldapUser.getDn().toString());
|
||||
ldapRole.setAttribute(getMembershipLdapAttribute(mapperModel), memberships);
|
||||
|
||||
|
@ -240,7 +249,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
}
|
||||
|
||||
public LDAPObject loadLDAPRoleByName(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, String roleName) {
|
||||
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
Condition roleNameCondition = new LDAPQueryConditionsBuilder().equal(new QueryParameter(getRoleNameLdapAttribute(mapperModel)), roleName);
|
||||
ldapQuery.where(roleNameCondition);
|
||||
return ldapQuery.getFirstResult();
|
||||
|
@ -248,29 +257,15 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
|
||||
protected Set<String> getExistingMemberships(UserFederationMapperModel mapperModel, LDAPObject ldapRole) {
|
||||
String memberAttrName = getMembershipLdapAttribute(mapperModel);
|
||||
Set<String> memberships = new TreeSet<String>();
|
||||
Object existingMemberships = ldapRole.getAttribute(memberAttrName);
|
||||
|
||||
if (existingMemberships != null) {
|
||||
if (existingMemberships instanceof String) {
|
||||
String existingMembership = existingMemberships.toString().trim();
|
||||
if (existingMemberships != null && existingMembership.length() > 0) {
|
||||
memberships.add(existingMembership);
|
||||
}
|
||||
} else if (existingMemberships instanceof Collection) {
|
||||
Collection<String> exMemberships = (Collection<String>) existingMemberships;
|
||||
for (String membership : exMemberships) {
|
||||
if (membership.trim().length() > 0) {
|
||||
memberships.add(membership);
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<String> memberships = ldapRole.getAttributeAsSet(memberAttrName);
|
||||
if (memberships == null) {
|
||||
memberships = new HashSet<>();
|
||||
}
|
||||
return memberships;
|
||||
}
|
||||
|
||||
protected List<LDAPObject> getLDAPRoleMappings(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
|
||||
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
String membershipAttr = getMembershipLdapAttribute(mapperModel);
|
||||
Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(new QueryParameter(membershipAttr), ldapUser.getDn().toString());
|
||||
ldapQuery.where(membershipCondition);
|
||||
|
@ -290,7 +285,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
|
||||
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
|
||||
}
|
||||
|
||||
|
||||
|
@ -389,7 +384,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
|
||||
if (mode == Mode.LDAP_ONLY) {
|
||||
// For LDAP-only we want to retrieve role mappings of target container just from LDAP
|
||||
Set<RoleModel> modelRolesCopy = new HashSet<RoleModel>(modelRoleMappings);
|
||||
Set<RoleModel> modelRolesCopy = new HashSet<>(modelRoleMappings);
|
||||
for (RoleModel role : modelRolesCopy) {
|
||||
if (role.getContainer().equals(targetRoleContainer)) {
|
||||
modelRoleMappings.remove(role);
|
||||
|
@ -408,7 +403,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
|
||||
List<LDAPObject> ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
|
||||
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
Set<RoleModel> roles = new HashSet<>();
|
||||
String roleNameLdapAttr = getRoleNameLdapAttribute(mapperModel);
|
||||
for (LDAPObject role : ldapRoles) {
|
||||
String roleName = role.getAttributeAsString(roleNameLdapAttr);
|
||||
|
@ -430,7 +425,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
|
||||
if (role.getContainer().equals(roleContainer)) {
|
||||
|
||||
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(getRoleNameLdapAttribute(mapperModel)), role.getName());
|
||||
Condition membershipCondition = conditionsBuilder.equal(new QueryParameter(getMembershipLdapAttribute(mapperModel)), ldapUser.getDn().toString());
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
package org.keycloak.federation.ldap.mappers;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
import org.keycloak.models.UserFederationProvider;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
import org.keycloak.models.utils.reflection.Property;
|
||||
import org.keycloak.models.utils.reflection.PropertyCriteria;
|
||||
import org.keycloak.models.utils.reflection.PropertyQueries;
|
||||
|
@ -21,10 +29,12 @@ import org.keycloak.models.utils.reflection.PropertyQueries;
|
|||
*/
|
||||
public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(UserAttributeLDAPFederationMapper.class);
|
||||
|
||||
private static final Map<String, Property<Object>> userModelProperties;
|
||||
|
||||
static {
|
||||
userModelProperties = PropertyQueries.createQuery(UserModel.class).addCriteria(new PropertyCriteria() {
|
||||
Map<String, Property<Object>> userModelProps = PropertyQueries.createQuery(UserModel.class).addCriteria(new PropertyCriteria() {
|
||||
|
||||
@Override
|
||||
public boolean methodMatches(Method m) {
|
||||
|
@ -36,11 +46,18 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
}
|
||||
|
||||
}).getResultList();
|
||||
|
||||
// Convert to be keyed by lower-cased attribute names
|
||||
userModelProperties = new HashMap<>();
|
||||
for (Map.Entry<String, Property<Object>> entry : userModelProps.entrySet()) {
|
||||
userModelProperties.put(entry.getKey().toLowerCase(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public static final String USER_MODEL_ATTRIBUTE = "user.model.attribute";
|
||||
public static final String LDAP_ATTRIBUTE = "ldap.attribute";
|
||||
public static final String READ_ONLY = "read.only";
|
||||
public static final String ALWAYS_READ_VALUE_FROM_LDAP = "always.read.value.from.ldap";
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -48,16 +65,21 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||
|
||||
Object ldapAttrValue = ldapUser.getAttribute(ldapAttrName);
|
||||
if (ldapAttrValue != null && !ldapAttrValue.toString().trim().isEmpty()) {
|
||||
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName);
|
||||
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName.toLowerCase());
|
||||
|
||||
if (userModelProperty != null) {
|
||||
// we have java property on UserModel
|
||||
userModelProperty.setValue(user, ldapAttrValue);
|
||||
if (userModelProperty != null) {
|
||||
|
||||
// we have java property on UserModel
|
||||
String ldapAttrValue = ldapUser.getAttributeAsString(ldapAttrName);
|
||||
setPropertyOnUserModel(userModelProperty, user, ldapAttrValue);
|
||||
} else {
|
||||
|
||||
// we don't have java property. Let's set attribute
|
||||
Set<String> ldapAttrValue = ldapUser.getAttributeAsSet(ldapAttrName);
|
||||
if (ldapAttrValue != null) {
|
||||
user.setAttribute(userModelAttrName, new ArrayList<>(ldapAttrValue));
|
||||
} else {
|
||||
// we don't have java property. Let's just setAttribute
|
||||
user.setAttribute(userModelAttrName, (String) ldapAttrValue);
|
||||
user.removeAttribute(userModelAttrName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,36 +89,58 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||
|
||||
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName);
|
||||
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName.toLowerCase());
|
||||
|
||||
Object attrValue;
|
||||
if (userModelProperty != null) {
|
||||
// we have java property on UserModel
|
||||
attrValue = userModelProperty.getValue(localUser);
|
||||
|
||||
// we have java property on UserModel. Assuming we support just properties of simple types
|
||||
Object attrValue = userModelProperty.getValue(localUser);
|
||||
String valueAsString = (attrValue == null) ? null : attrValue.toString();
|
||||
ldapUser.setSingleAttribute(ldapAttrName, valueAsString);
|
||||
} else {
|
||||
// we don't have java property. Let's just setAttribute
|
||||
attrValue = localUser.getAttribute(userModelAttrName);
|
||||
|
||||
// we don't have java property. Let's set attribute
|
||||
List<String> attrValues = localUser.getAttribute(userModelAttrName);
|
||||
|
||||
if (attrValues.size() == 0) {
|
||||
ldapUser.setAttribute(ldapAttrName, null);
|
||||
} else {
|
||||
ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<>(attrValues));
|
||||
}
|
||||
}
|
||||
|
||||
ldapUser.setAttribute(ldapAttrName, attrValue);
|
||||
if (isReadOnly(mapperModel)) {
|
||||
ldapUser.addReadOnlyAttributeName(ldapAttrName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
|
||||
public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, final LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
|
||||
final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||
final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||
boolean isAlwaysReadValueFromLDAP = parseBooleanParameter(mapperModel, ALWAYS_READ_VALUE_FROM_LDAP);
|
||||
|
||||
// For writable mode, we want to propagate writing of attribute to LDAP as well
|
||||
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
|
||||
|
||||
final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||
final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||
|
||||
TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
|
||||
delegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
setLDAPAttribute(name, value);
|
||||
super.setAttribute(name, value);
|
||||
super.setSingleAttribute(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
setLDAPAttribute(name, values);
|
||||
super.setAttribute(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
setLDAPAttribute(name, null);
|
||||
super.removeAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -117,28 +161,108 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
super.setFirstName(firstName);
|
||||
}
|
||||
|
||||
protected void setLDAPAttribute(String modelAttrName, String value) {
|
||||
protected void setLDAPAttribute(String modelAttrName, Object value) {
|
||||
if (modelAttrName.equalsIgnoreCase(userModelAttrName)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Pushing user attribute to LDAP. Model attribute name: %s, LDAP attribute name: %s, Attribute value: %s", modelAttrName, ldapAttrName, value);
|
||||
logger.tracef("Pushing user attribute to LDAP. username: %s, Model attribute name: %s, LDAP attribute name: %s, Attribute value: %s", getUsername(), modelAttrName, ldapAttrName, value);
|
||||
}
|
||||
|
||||
ensureTransactionStarted();
|
||||
|
||||
ldapUser.setAttribute(ldapAttrName, value);
|
||||
if (value == null) {
|
||||
ldapUser.setAttribute(ldapAttrName, null);
|
||||
} else if (value instanceof String) {
|
||||
ldapUser.setSingleAttribute(ldapAttrName, (String) value);
|
||||
} else {
|
||||
List<String> asList = (List<String>) value;
|
||||
ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<>(asList));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return txDelegate;
|
||||
} else {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
// We prefer to read attribute value from LDAP instead of from local Keycloak DB
|
||||
if (isAlwaysReadValueFromLDAP) {
|
||||
|
||||
delegate = new UserModelDelegate(delegate) {
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
if (name.equalsIgnoreCase(userModelAttrName)) {
|
||||
return ldapUser.getAttributeAsString(ldapAttrName);
|
||||
} else {
|
||||
return super.getFirstAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
if (name.equalsIgnoreCase(userModelAttrName)) {
|
||||
Collection<String> ldapAttrValue = ldapUser.getAttributeAsSet(ldapAttrName);
|
||||
if (ldapAttrValue == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new ArrayList<>(ldapAttrValue);
|
||||
}
|
||||
} else {
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
Map<String, List<String>> attrs = new HashMap<>(super.getAttributes());
|
||||
|
||||
// Ignore UserModel properties
|
||||
if (userModelProperties.get(userModelAttrName.toLowerCase()) != null) {
|
||||
return attrs;
|
||||
}
|
||||
|
||||
Set<String> allLdapAttrValues = ldapUser.getAttributeAsSet(ldapAttrName);
|
||||
if (allLdapAttrValues != null) {
|
||||
attrs.put(userModelAttrName, new ArrayList<>(allLdapAttrValues));
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmail() {
|
||||
if (UserModel.EMAIL.equalsIgnoreCase(userModelAttrName)) {
|
||||
return ldapUser.getAttributeAsString(ldapAttrName);
|
||||
} else {
|
||||
return super.getEmail();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastName() {
|
||||
if (UserModel.LAST_NAME.equalsIgnoreCase(userModelAttrName)) {
|
||||
return ldapUser.getAttributeAsString(ldapAttrName);
|
||||
} else {
|
||||
return super.getLastName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
if (UserModel.FIRST_NAME.equalsIgnoreCase(userModelAttrName)) {
|
||||
return ldapUser.getAttributeAsString(ldapAttrName);
|
||||
} else {
|
||||
return super.getFirstName();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
|
||||
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
|
||||
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||
|
||||
|
@ -160,4 +284,22 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
private boolean isReadOnly(UserFederationMapperModel mapperModel) {
|
||||
return parseBooleanParameter(mapperModel, READ_ONLY);
|
||||
}
|
||||
|
||||
|
||||
protected void setPropertyOnUserModel(Property<Object> userModelProperty, UserModel user, String ldapAttrValue) {
|
||||
if (ldapAttrValue == null) {
|
||||
userModelProperty.setValue(user, null);
|
||||
} else {
|
||||
Class<Object> clazz = userModelProperty.getJavaClass();
|
||||
|
||||
if (String.class.equals(clazz)) {
|
||||
userModelProperty.setValue(user, ldapAttrValue);
|
||||
} else if (Boolean.class.equals(clazz) || boolean.class.equals(clazz)) {
|
||||
Boolean boolVal = Boolean.valueOf(ldapAttrValue);
|
||||
userModelProperty.setValue(user, boolVal);
|
||||
} else {
|
||||
logger.warnf("Don't know how to set the property '%s' on user '%s' . Value of LDAP attribute is '%s' ", userModelProperty.getName(), user.getUsername(), ldapAttrValue.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,10 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera
|
|||
ProviderConfigProperty readOnly = createConfigProperty(UserAttributeLDAPFederationMapper.READ_ONLY, "Read Only",
|
||||
"Read-only attribute is imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, "false");
|
||||
configProperties.add(readOnly);
|
||||
|
||||
ProviderConfigProperty alwaysReadValueFromLDAP = createConfigProperty(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "Always read value from LDAP",
|
||||
"If on, then during reading of the user will be value of attribute from LDAP always used instead of the value from Keycloak DB", ProviderConfigProperty.BOOLEAN_TYPE, "false");
|
||||
configProperties.add(alwaysReadValueFromLDAP);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package org.keycloak.account.freemarker.model;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.util.MultivaluedHashMap;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -11,14 +14,29 @@ import java.util.Map;
|
|||
*/
|
||||
public class AccountBean {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AccountBean.class);
|
||||
|
||||
private final UserModel user;
|
||||
private final MultivaluedMap<String, String> profileFormData;
|
||||
|
||||
// TODO: More proper multi-value attribute support
|
||||
private final Map<String, String> attributes = new HashMap<>();
|
||||
|
||||
public AccountBean(UserModel user, MultivaluedMap<String, String> profileFormData) {
|
||||
this.user = user;
|
||||
this.profileFormData = profileFormData;
|
||||
attributes.putAll(user.getAttributes());
|
||||
|
||||
for (Map.Entry<String, List<String>> attr : user.getAttributes().entrySet()) {
|
||||
List<String> attrValue = attr.getValue();
|
||||
if (attrValue.size() > 0) {
|
||||
attributes.put(attr.getKey(), attrValue.get(0));
|
||||
}
|
||||
|
||||
if (attrValue.size() > 1) {
|
||||
logger.warnf("There are more values for attribute '%s' of user '%s' . Will display just first value", attr.getKey(), user.getUsername());
|
||||
}
|
||||
}
|
||||
|
||||
if (profileFormData != null) {
|
||||
for (String key : profileFormData.keySet()) {
|
||||
if (key.startsWith("user.attributes.")) {
|
||||
|
|
|
@ -35,7 +35,7 @@ public class LocaleHelper {
|
|||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
||||
if(locale != null){
|
||||
if(user != null){
|
||||
user.setAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
||||
user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
||||
}
|
||||
return locale;
|
||||
}else{
|
||||
|
@ -48,8 +48,8 @@ public class LocaleHelper {
|
|||
String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
|
||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
||||
if(locale != null){
|
||||
if(user != null && user.getAttribute(UserModel.LOCALE) == null){
|
||||
user.setAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
||||
if(user != null && user.getFirstAttribute(UserModel.LOCALE) == null){
|
||||
user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
||||
}
|
||||
return locale;
|
||||
}else{
|
||||
|
@ -59,7 +59,7 @@ public class LocaleHelper {
|
|||
|
||||
//2. User profile
|
||||
if(user != null && user.getAttributes().containsKey(UserModel.LOCALE)){
|
||||
String localeString = user.getAttribute(UserModel.LOCALE);
|
||||
String localeString = user.getFirstAttribute(UserModel.LOCALE);
|
||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
||||
if(locale != null){
|
||||
|
||||
|
|
|
@ -206,6 +206,8 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede
|
|||
if (!user.attributes) {
|
||||
user.attributes = {}
|
||||
}
|
||||
convertAttributeValuesToString(user);
|
||||
|
||||
$scope.user = angular.copy(user);
|
||||
if(user.federationLink) {
|
||||
console.log("federationLink is not null");
|
||||
|
@ -252,13 +254,15 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede
|
|||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
convertAttributeValuesToLists();
|
||||
|
||||
if ($scope.create) {
|
||||
User.save({
|
||||
realm: realm.realm
|
||||
}, $scope.user, function (data, headers) {
|
||||
$scope.changed = false;
|
||||
convertAttributeValuesToString($scope.user);
|
||||
user = angular.copy($scope.user);
|
||||
|
||||
var l = headers().location;
|
||||
|
||||
console.debug("Location == " + l);
|
||||
|
@ -275,12 +279,33 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede
|
|||
userId: $scope.user.id
|
||||
}, $scope.user, function () {
|
||||
$scope.changed = false;
|
||||
convertAttributeValuesToString($scope.user);
|
||||
user = angular.copy($scope.user);
|
||||
Notifications.success("Your changes have been saved to the user.");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function convertAttributeValuesToLists() {
|
||||
var attrs = $scope.user.attributes;
|
||||
for (var attribute in attrs) {
|
||||
if (typeof attrs[attribute] === "string") {
|
||||
var attrVals = attrs[attribute].split("##");
|
||||
attrs[attribute] = attrVals;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertAttributeValuesToString(user) {
|
||||
var attrs = user.attributes;
|
||||
for (var attribute in attrs) {
|
||||
if (typeof attrs[attribute] === "object") {
|
||||
var attrVals = attrs[attribute].join("##");
|
||||
attrs[attribute] = attrVals;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.user = angular.copy(user);
|
||||
$scope.changed = false;
|
||||
|
|
|
@ -60,6 +60,7 @@ public class LDAPConstants {
|
|||
public static final String SAM_ACCOUNT_NAME = "sAMAccountName";
|
||||
public static final String EMAIL = "mail";
|
||||
public static final String POSTAL_CODE = "postalCode";
|
||||
public static final String STREET = "street";
|
||||
public static final String MEMBER = "member";
|
||||
public static final String MEMBER_OF = "memberOf";
|
||||
public static final String OBJECT_CLASS = "objectclass";
|
||||
|
|
|
@ -27,13 +27,31 @@ public interface UserModel {
|
|||
|
||||
void setEnabled(boolean enabled);
|
||||
|
||||
void setAttribute(String name, String value);
|
||||
/**
|
||||
* Set single value of specified attribute. Remove all other existing values
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
void setSingleAttribute(String name, String value);
|
||||
|
||||
void setAttribute(String name, List<String> values);
|
||||
|
||||
void removeAttribute(String name);
|
||||
|
||||
String getAttribute(String name);
|
||||
/**
|
||||
* @param name
|
||||
* @return null if there is not any value of specified attribute or first value otherwise. Don't throw exception if there are more values of the attribute
|
||||
*/
|
||||
String getFirstAttribute(String name);
|
||||
|
||||
Map<String, String> getAttributes();
|
||||
/**
|
||||
* @param name
|
||||
* @return list of all attribute values or empty list if there are not any values. Never return null
|
||||
*/
|
||||
List<String> getAttribute(String name);
|
||||
|
||||
Map<String, List<String>> getAttributes();
|
||||
|
||||
Set<String> getRequiredActions();
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package org.keycloak.models.entities;
|
||||
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -23,7 +21,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||
|
||||
private List<String> roleIds;
|
||||
|
||||
private Map<String, String> attributes;
|
||||
private Map<String, List<String>> attributes;
|
||||
private List<String> requiredActions;
|
||||
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
||||
private List<FederatedIdentityEntity> federatedIdentities;
|
||||
|
@ -101,11 +99,11 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||
this.roleIds = roleIds;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.keycloak.representations.idm.UserFederationMapperRepresentation;
|
|||
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
import org.keycloak.util.MultivaluedHashMap;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -67,7 +68,7 @@ public class ModelToRepresentation {
|
|||
rep.setRequiredActions(reqActions);
|
||||
|
||||
if (user.getAttributes() != null && !user.getAttributes().isEmpty()) {
|
||||
Map<String, String> attrs = new HashMap<String, String>();
|
||||
Map<String, Object> attrs = new HashMap<>();
|
||||
attrs.putAll(user.getAttributes());
|
||||
rep.setAttributes(attrs);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.keycloak.util.UriUtils;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
|
@ -805,8 +806,17 @@ public class RepresentationToModel {
|
|||
user.setFederationLink(userRep.getFederationLink());
|
||||
user.setTotp(userRep.isTotp());
|
||||
if (userRep.getAttributes() != null) {
|
||||
for (Map.Entry<String, String> entry : userRep.getAttributes().entrySet()) {
|
||||
user.setAttribute(entry.getKey(), entry.getValue());
|
||||
for (Map.Entry<String, Object> entry : userRep.getAttributes().entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (value instanceof Collection) {
|
||||
Collection<String> colVal = (Collection<String>) value;
|
||||
user.setAttribute(entry.getKey(), new ArrayList<>(colVal));
|
||||
} else if (value instanceof String) {
|
||||
// TODO: This is here just for backwards compatibility with KC 1.3 and earlier
|
||||
String stringVal = (String) value;
|
||||
user.setSingleAttribute(entry.getKey(), stringVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (userRep.getRequiredActions() != null) {
|
||||
|
|
|
@ -53,8 +53,13 @@ public class UserModelDelegate implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
delegate.setAttribute(name, value);
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
delegate.setSingleAttribute(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
delegate.setAttribute(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,12 +68,17 @@ public class UserModelDelegate implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
public String getFirstAttribute(String name) {
|
||||
return delegate.getFirstAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
return delegate.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return delegate.getAttributes();
|
||||
}
|
||||
|
||||
|
|
|
@ -158,12 +158,23 @@ public class UserAdapter implements UserModel, Comparable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
if (user.getAttributes() == null) {
|
||||
user.setAttributes(new HashMap<String, String>());
|
||||
user.setAttributes(new HashMap<String, List<String>>());
|
||||
}
|
||||
|
||||
user.getAttributes().put(name, value);
|
||||
List<String> attrValues = new ArrayList<>();
|
||||
attrValues.add(value);
|
||||
user.getAttributes().put(name, attrValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
if (user.getAttributes() == null) {
|
||||
user.setAttributes(new HashMap<String, List<String>>());
|
||||
}
|
||||
|
||||
user.getAttributes().put(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -174,13 +185,23 @@ public class UserAdapter implements UserModel, Comparable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
return user.getAttributes()==null ? null : user.getAttributes().get(name);
|
||||
public String getFirstAttribute(String name) {
|
||||
if (user.getAttributes()==null) return null;
|
||||
|
||||
List<String> attrValues = user.getAttributes().get(name);
|
||||
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return user.getAttributes()==null ? Collections.<String, String>emptyMap() : Collections.unmodifiableMap(user.getAttributes());
|
||||
public List<String> getAttribute(String name) {
|
||||
if (user.getAttributes()==null) return Collections.<String>emptyList();
|
||||
List<String> attrValues = user.getAttributes().get(name);
|
||||
return (attrValues == null) ? Collections.<String>emptyList() : Collections.unmodifiableList(attrValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return user.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map)user.getAttributes());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.keycloak.models.UserCredentialValueModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.cache.entities.CachedUser;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -78,9 +79,15 @@ public class UserAdapter implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
getDelegateForUpdate();
|
||||
updated.setAttribute(name, value);
|
||||
updated.setSingleAttribute(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
getDelegateForUpdate();
|
||||
updated.setAttribute(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -90,13 +97,20 @@ public class UserAdapter implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
if (updated != null) return updated.getAttribute(name);
|
||||
return cached.getAttributes().get(name);
|
||||
public String getFirstAttribute(String name) {
|
||||
if (updated != null) return updated.getFirstAttribute(name);
|
||||
return cached.getAttributes().getFirst(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
public List<String> getAttribute(String name) {
|
||||
if (updated != null) return updated.getAttribute(name);
|
||||
List<String> result = cached.getAttributes().get(name);
|
||||
return (result == null) ? Collections.<String>emptyList() : result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
if (updated != null) return updated.getAttributes();
|
||||
return cached.getAttributes();
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.util.MultivaluedHashMap;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
|
@ -29,7 +30,7 @@ public class CachedUser implements Serializable {
|
|||
private boolean enabled;
|
||||
private boolean totp;
|
||||
private String federationLink;
|
||||
private Map<String, String> attributes = new HashMap<>();
|
||||
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||
private Set<String> requiredActions = new HashSet<>();
|
||||
private Set<String> roleMappings = new HashSet<String>();
|
||||
|
||||
|
@ -93,7 +94,7 @@ public class CachedUser implements Serializable {
|
|||
return totp;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
public MultivaluedHashMap<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
|
|||
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
import org.keycloak.util.MultivaluedHashMap;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
@ -92,14 +93,46 @@ public class UserAdapter implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
boolean found = false;
|
||||
List<UserAttributeEntity> toRemove = new ArrayList<>();
|
||||
for (UserAttributeEntity attr : user.getAttributes()) {
|
||||
if (attr.getName().equals(name)) {
|
||||
attr.setValue(value);
|
||||
return;
|
||||
if (!found) {
|
||||
attr.setValue(value);
|
||||
found = true;
|
||||
} else {
|
||||
toRemove.add(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (UserAttributeEntity attr : toRemove) {
|
||||
em.remove(attr);
|
||||
user.getAttributes().remove(attr);
|
||||
}
|
||||
|
||||
if (found) {
|
||||
return;
|
||||
}
|
||||
|
||||
persistAttributeValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
// Remove all existing
|
||||
removeAttribute(name);
|
||||
|
||||
// Put all new
|
||||
for (String value : values) {
|
||||
persistAttributeValue(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void persistAttributeValue(String name, String value) {
|
||||
UserAttributeEntity attr = new UserAttributeEntity();
|
||||
attr.setId(KeycloakModelUtils.generateId());
|
||||
attr.setName(name);
|
||||
attr.setValue(value);
|
||||
attr.setUser(user);
|
||||
|
@ -120,7 +153,7 @@ public class UserAdapter implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
public String getFirstAttribute(String name) {
|
||||
for (UserAttributeEntity attr : user.getAttributes()) {
|
||||
if (attr.getName().equals(name)) {
|
||||
return attr.getValue();
|
||||
|
@ -130,10 +163,21 @@ public class UserAdapter implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
public List<String> getAttribute(String name) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (UserAttributeEntity attr : user.getAttributes()) {
|
||||
result.put(attr.getName(), attr.getValue());
|
||||
if (attr.getName().equals(name)) {
|
||||
result.add(attr.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
|
||||
for (UserAttributeEntity attr : user.getAttributes()) {
|
||||
result.add(attr.getName(), attr.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
|
@ -11,6 +13,8 @@ import javax.persistence.NamedQueries;
|
|||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -22,20 +26,29 @@ import java.io.Serializable;
|
|||
})
|
||||
@Table(name="USER_ATTRIBUTE")
|
||||
@Entity
|
||||
@IdClass(UserAttributeEntity.Key.class)
|
||||
public class UserAttributeEntity {
|
||||
|
||||
@Id
|
||||
@Column(name="ID", length = 36)
|
||||
protected String id;
|
||||
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name = "USER_ID")
|
||||
protected UserEntity user;
|
||||
|
||||
@Id
|
||||
@Column(name = "NAME")
|
||||
protected String name;
|
||||
@Column(name = "VALUE")
|
||||
protected String value;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -60,47 +73,4 @@ public class UserAttributeEntity {
|
|||
this.user = user;
|
||||
}
|
||||
|
||||
public static class Key implements Serializable {
|
||||
|
||||
protected UserEntity user;
|
||||
|
||||
protected String name;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(UserEntity user, String name) {
|
||||
this.user = user;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public UserEntity getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key key = (Key) o;
|
||||
|
||||
if (name != null ? !name.equals(key.name) : key.name != null) return false;
|
||||
if (user != null ? !user.getId().equals(key.user != null ? key.user.getId() : null) : key.user != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = user != null ? user.getId().hashCode() : 0;
|
||||
result = 31 * result + (name != null ? name.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import org.keycloak.models.UserCredentialValueModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.entities.CredentialEntity;
|
||||
import org.keycloak.models.entities.UserConsentEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
||||
|
@ -127,12 +126,24 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
if (user.getAttributes() == null) {
|
||||
user.setAttributes(new HashMap<String, String>());
|
||||
user.setAttributes(new HashMap<String, List<String>>());
|
||||
}
|
||||
|
||||
user.getAttributes().put(name, value);
|
||||
List<String> attrValues = new ArrayList<>();
|
||||
attrValues.add(value);
|
||||
user.getAttributes().put(name, attrValues);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
if (user.getAttributes() == null) {
|
||||
user.setAttributes(new HashMap<String, List<String>>());
|
||||
}
|
||||
|
||||
user.getAttributes().put(name, values);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
|
@ -145,13 +156,23 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
return user.getAttributes()==null ? null : user.getAttributes().get(name);
|
||||
public String getFirstAttribute(String name) {
|
||||
if (user.getAttributes()==null) return null;
|
||||
|
||||
List<String> attrValues = user.getAttributes().get(name);
|
||||
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return user.getAttributes()==null ? Collections.<String, String>emptyMap() : Collections.unmodifiableMap(user.getAttributes());
|
||||
public List<String> getAttribute(String name) {
|
||||
if (user.getAttributes()==null) return Collections.<String>emptyList();
|
||||
List<String> attrValues = user.getAttributes().get(name);
|
||||
return (attrValues == null) ? Collections.<String>emptyList() : Collections.unmodifiableList(attrValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return user.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map)user.getAttributes());
|
||||
}
|
||||
|
||||
public MongoUserEntity getUser() {
|
||||
|
|
|
@ -238,11 +238,11 @@ public class SamlProtocol implements LoginProtocol {
|
|||
// generate a persistent user id specifically for each client.
|
||||
UserModel user = userSession.getUser();
|
||||
String name = SAML_PERSISTENT_NAME_ID_FOR + "." + clientSession.getClient().getClientId();
|
||||
String samlPersistentId = user.getAttribute(name);
|
||||
String samlPersistentId = user.getFirstAttribute(name);
|
||||
if (samlPersistentId != null) return samlPersistentId;
|
||||
// "G-" stands for "generated"
|
||||
samlPersistentId = "G-" + UUID.randomUUID().toString();
|
||||
user.setAttribute(name, samlPersistentId);
|
||||
user.setSingleAttribute(name, samlPersistentId);
|
||||
return samlPersistentId;
|
||||
} else if(nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())){
|
||||
// TODO: Support for persistent NameID (pseudo-random identifier persisted in user object)
|
||||
|
|
|
@ -13,7 +13,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Mappings UserModel property (the property name of a getter method) to an AttributeStatement.
|
||||
* Mappings UserModel attribute (not property name of a getter method) to an AttributeStatement.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -62,7 +62,7 @@ public class UserAttributeStatementMapper extends AbstractSAMLProtocolMapper imp
|
|||
public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
UserModel user = userSession.getUser();
|
||||
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
|
||||
String attributeValue = user.getAttribute(attributeName);
|
||||
String attributeValue = user.getFirstAttribute(attributeName);
|
||||
if (attributeValue == null) return;
|
||||
AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue);
|
||||
|
||||
|
|
|
@ -15,12 +15,15 @@ import java.util.List;
|
|||
public class ProtocolMapperUtils {
|
||||
public static final String USER_ATTRIBUTE = "user.attribute";
|
||||
public static final String USER_SESSION_NOTE = "user.session.note";
|
||||
public static final String MULTIVALUED = "multivalued";
|
||||
public static final String USER_MODEL_PROPERTY_LABEL = "User Property";
|
||||
public static final String USER_MODEL_PROPERTY_HELP_TEXT = "Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method.";
|
||||
public static final String USER_MODEL_ATTRIBUTE_LABEL = "User Attribute";
|
||||
public static final String USER_MODEL_ATTRIBUTE_HELP_TEXT = "Name of stored user attribute which is the name of an attribute within the UserModel.attribute map.";
|
||||
public static final String USER_SESSION_MODEL_NOTE_LABEL = "User Session Note";
|
||||
public static final String USER_SESSION_MODEL_NOTE_HELP_TEXT = "Name of stored user session note within the UserSessionModel.note map.";
|
||||
public static final String MULTIVALUED_LABEL = "Multivalued";
|
||||
public static final String MULTIVALUED_HELP_TEXT = "Indicates if attribute supports multiple values. If true, then the list of all values of this attribute will be set as claim. If false, then just first value will be set as claim";
|
||||
|
||||
public static String getUserModelValue(UserModel user, String propertyName) {
|
||||
|
||||
|
|
|
@ -118,11 +118,11 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc
|
|||
protected void setClaim(IDToken token, UserSessionModel userSession) {
|
||||
UserModel user = userSession.getUser();
|
||||
AddressClaimSet addressSet = new AddressClaimSet();
|
||||
addressSet.setStreetAddress(user.getAttribute("street"));
|
||||
addressSet.setLocality(user.getAttribute("locality"));
|
||||
addressSet.setRegion(user.getAttribute("region"));
|
||||
addressSet.setPostalCode(user.getAttribute("postal_code"));
|
||||
addressSet.setCountry(user.getAttribute("country"));
|
||||
addressSet.setStreetAddress(user.getFirstAttribute("street"));
|
||||
addressSet.setLocality(user.getFirstAttribute("locality"));
|
||||
addressSet.setRegion(user.getFirstAttribute("region"));
|
||||
addressSet.setPostalCode(user.getFirstAttribute("postal_code"));
|
||||
addressSet.setCountry(user.getFirstAttribute("country"));
|
||||
token.getOtherClaims().put("address", addressSet);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.protocol.oidc.mappers;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.ProtocolMapper;
|
||||
|
@ -19,6 +20,8 @@ import java.util.Map;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OIDCAttributeMapperHelper {
|
||||
private static final Logger logger = Logger.getLogger(OIDCAttributeMapperHelper.class);
|
||||
|
||||
public static final String TOKEN_CLAIM_NAME = "claim.name";
|
||||
public static final String TOKEN_CLAIM_NAME_LABEL = "Token Claim Name";
|
||||
public static final String JSON_TYPE = "Claim JSON Type";
|
||||
|
@ -31,6 +34,26 @@ public class OIDCAttributeMapperHelper {
|
|||
|
||||
public static Object mapAttributeValue(ProtocolMapperModel mappingModel, Object attributeValue) {
|
||||
if (attributeValue == null) return null;
|
||||
|
||||
if (attributeValue instanceof List) {
|
||||
List<Object> valueAsList = (List<Object>) attributeValue;
|
||||
if (valueAsList.size() == 0) return null;
|
||||
|
||||
if (isMultivalued(mappingModel)) {
|
||||
List<Object> result = new ArrayList<>();
|
||||
for (Object valueItem : valueAsList) {
|
||||
result.add(mapAttributeValue(mappingModel, valueItem));
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
if (valueAsList.size() > 1) {
|
||||
logger.warnf("Multiple values found '%s' for protocol mapper '%s' but expected just single value", attributeValue.toString(), mappingModel.getName());
|
||||
}
|
||||
|
||||
attributeValue = valueAsList.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
String type = mappingModel.getConfig().get(JSON_TYPE);
|
||||
if (type == null) return attributeValue;
|
||||
if (type.equals("boolean")) {
|
||||
|
@ -53,8 +76,9 @@ public class OIDCAttributeMapperHelper {
|
|||
}
|
||||
|
||||
public static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Object attributeValue) {
|
||||
if (attributeValue == null) return;
|
||||
attributeValue = mapAttributeValue(mappingModel, attributeValue);
|
||||
if (attributeValue == null) return;
|
||||
|
||||
String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME);
|
||||
String[] split = protocolClaim.split("\\.");
|
||||
Map<String, Object> jsonObject = token.getOtherClaims();
|
||||
|
@ -102,6 +126,11 @@ public class OIDCAttributeMapperHelper {
|
|||
return "true".equals(mappingModel.getConfig().get(INCLUDE_IN_ACCESS_TOKEN));
|
||||
}
|
||||
|
||||
|
||||
public static boolean isMultivalued(ProtocolMapperModel mappingModel) {
|
||||
return "true".equals(mappingModel.getConfig().get(ProtocolMapperUtils.MULTIVALUED));
|
||||
}
|
||||
|
||||
public static void addAttributeConfig(List<ProviderConfigProperty> configProperties) {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.keycloak.protocol.oidc.mappers;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||
|
@ -36,6 +36,13 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
|
|||
configProperties.add(property);
|
||||
OIDCAttributeMapperHelper.addAttributeConfig(configProperties);
|
||||
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(ProtocolMapperUtils.MULTIVALUED);
|
||||
property.setLabel(ProtocolMapperUtils.MULTIVALUED_LABEL);
|
||||
property.setHelpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT);
|
||||
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||
configProperties.add(property);
|
||||
|
||||
}
|
||||
|
||||
public static final String PROVIDER_ID = "oidc-usermodel-attribute-mapper";
|
||||
|
@ -77,7 +84,7 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
|
|||
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
|
||||
UserModel user = userSession.getUser();
|
||||
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
|
||||
String attributeValue = user.getAttribute(attributeName);
|
||||
List<String> attributeValue = user.getAttribute(attributeName);
|
||||
if (attributeValue == null) return;
|
||||
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue);
|
||||
}
|
||||
|
@ -93,12 +100,18 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
|
|||
String userAttribute,
|
||||
String tokenClaimName, String claimType,
|
||||
boolean consentRequired, String consentText,
|
||||
boolean accessToken, boolean idToken) {
|
||||
return OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute,
|
||||
boolean accessToken, boolean idToken, boolean multivalued) {
|
||||
ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute,
|
||||
tokenClaimName, claimType,
|
||||
consentRequired, consentText,
|
||||
accessToken, idToken,
|
||||
PROVIDER_ID);
|
||||
|
||||
if (multivalued) {
|
||||
mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, "true");
|
||||
}
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.keycloak.services.resources;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
|
@ -21,8 +24,26 @@ public class AttributeFormDataProcessor {
|
|||
for (String key : formData.keySet()) {
|
||||
if (!key.startsWith("user.attributes.")) continue;
|
||||
String attribute = key.substring("user.attributes.".length());
|
||||
user.setAttribute(attribute, formData.getFirst(key));
|
||||
|
||||
// Need to handle case when attribute has multiple values, but in UI was displayed just first value
|
||||
List<String> modelValue = new ArrayList<>(user.getAttribute(attribute));
|
||||
|
||||
int index = 0;
|
||||
for (String value : formData.get(key)) {
|
||||
addOrSetValue(modelValue, index, value);
|
||||
index++;
|
||||
}
|
||||
|
||||
user.setAttribute(attribute, modelValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void addOrSetValue(List<String> list, int index, String value) {
|
||||
if (list.size() > index) {
|
||||
list.set(index, value);
|
||||
} else {
|
||||
list.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import org.jboss.resteasy.annotations.cache.NoCache;
|
|||
import org.jboss.resteasy.spi.BadRequestException;
|
||||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
|
@ -228,8 +227,8 @@ public class UsersResource {
|
|||
}
|
||||
}
|
||||
|
||||
if (rep.getAttributes() != null) {
|
||||
for (Map.Entry<String, String> attr : rep.getAttributes().entrySet()) {
|
||||
if (rep.getAttributesAsListValues() != null) {
|
||||
for (Map.Entry<String, List<String>> attr : rep.getAttributesAsListValues().entrySet()) {
|
||||
user.setAttribute(attr.getKey(), attr.getValue());
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.apache.http.HttpResponse;
|
|||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
|
@ -51,8 +52,8 @@ public class ProfileTest {
|
|||
UserModel user = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
|
||||
user.setFirstName("First");
|
||||
user.setLastName("Last");
|
||||
user.setAttribute("key1", "value1");
|
||||
user.setAttribute("key2", "value2");
|
||||
user.setSingleAttribute("key1", "value1");
|
||||
user.setSingleAttribute("key2", "value2");
|
||||
|
||||
ClientModel accountApp = appRealm.getClientByClientId(org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
|
||||
|
||||
|
@ -114,8 +115,12 @@ public class ProfileTest {
|
|||
assertEquals("Last", profile.getString("lastName"));
|
||||
|
||||
JSONObject attributes = profile.getJSONObject("attributes");
|
||||
assertEquals("value1", attributes.getString("key1"));
|
||||
assertEquals("value2", attributes.getString("key2"));
|
||||
JSONArray attrValue = attributes.getJSONArray("key1");
|
||||
assertEquals(1, attrValue.length());
|
||||
assertEquals("value1", attrValue.get(0));
|
||||
attrValue = attributes.getJSONArray("key2");
|
||||
assertEquals(1, attrValue.length());
|
||||
assertEquals("value2", attrValue.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -12,6 +12,8 @@ import org.keycloak.representations.idm.UserRepresentation;
|
|||
|
||||
import javax.ws.rs.ClientErrorException;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -271,8 +273,8 @@ public class UserTest extends AbstractClientTest {
|
|||
public void attributes() {
|
||||
UserRepresentation user1 = new UserRepresentation();
|
||||
user1.setUsername("user1");
|
||||
user1.attribute("attr1", "value1user1");
|
||||
user1.attribute("attr2", "value2user1");
|
||||
user1.singleAttribute("attr1", "value1user1");
|
||||
user1.singleAttribute("attr2", "value2user1");
|
||||
|
||||
Response response = realm.users().create(user1);
|
||||
String user1Id = ApiUtil.getCreatedId(response);
|
||||
|
@ -280,40 +282,45 @@ public class UserTest extends AbstractClientTest {
|
|||
|
||||
UserRepresentation user2 = new UserRepresentation();
|
||||
user2.setUsername("user2");
|
||||
user2.attribute("attr1", "value1user2");
|
||||
user2.attribute("attr2", "value2user2");
|
||||
user2.singleAttribute("attr1", "value1user2");
|
||||
List<String> vals = new ArrayList<>();
|
||||
vals.add("value2user2");
|
||||
vals.add("value2user2_2");
|
||||
user2.getAttributesAsListValues().put("attr2", vals);
|
||||
|
||||
response = realm.users().create(user2);
|
||||
String user2Id = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
user1 = realm.users().get(user1Id).toRepresentation();
|
||||
assertEquals(2, user1.getAttributes().size());
|
||||
assertEquals("value1user1", user1.getAttributes().get("attr1"));
|
||||
assertEquals("value2user1", user1.getAttributes().get("attr2"));
|
||||
assertEquals(2, user1.getAttributesAsListValues().size());
|
||||
assertAttributeValue("value1user1", user1.getAttributesAsListValues().get("attr1"));
|
||||
assertAttributeValue("value2user1", user1.getAttributesAsListValues().get("attr2"));
|
||||
|
||||
user2 = realm.users().get(user2Id).toRepresentation();
|
||||
assertEquals(2, user2.getAttributes().size());
|
||||
assertEquals("value1user2", user2.getAttributes().get("attr1"));
|
||||
assertEquals("value2user2", user2.getAttributes().get("attr2"));
|
||||
assertEquals(2, user2.getAttributesAsListValues().size());
|
||||
assertAttributeValue("value1user2", user2.getAttributesAsListValues().get("attr1"));
|
||||
vals = user2.getAttributesAsListValues().get("attr2");
|
||||
assertEquals(2, vals.size());
|
||||
assertTrue(vals.contains("value2user2") && vals.contains("value2user2_2"));
|
||||
|
||||
user1.attribute("attr1", "value3user1");
|
||||
user1.attribute("attr3", "value4user1");
|
||||
user1.singleAttribute("attr1", "value3user1");
|
||||
user1.singleAttribute("attr3", "value4user1");
|
||||
|
||||
realm.users().get(user1Id).update(user1);
|
||||
|
||||
user1 = realm.users().get(user1Id).toRepresentation();
|
||||
assertEquals(3, user1.getAttributes().size());
|
||||
assertEquals("value3user1", user1.getAttributes().get("attr1"));
|
||||
assertEquals("value2user1", user1.getAttributes().get("attr2"));
|
||||
assertEquals("value4user1", user1.getAttributes().get("attr3"));
|
||||
assertEquals(3, user1.getAttributesAsListValues().size());
|
||||
assertAttributeValue("value3user1", user1.getAttributesAsListValues().get("attr1"));
|
||||
assertAttributeValue("value2user1", user1.getAttributesAsListValues().get("attr2"));
|
||||
assertAttributeValue("value4user1", user1.getAttributesAsListValues().get("attr3"));
|
||||
|
||||
user1.getAttributes().remove("attr1");
|
||||
realm.users().get(user1Id).update(user1);
|
||||
|
||||
user1 = realm.users().get(user1Id).toRepresentation();
|
||||
assertEquals(2, user1.getAttributes().size());
|
||||
assertEquals("value2user1", user1.getAttributes().get("attr2"));
|
||||
assertEquals("value4user1", user1.getAttributes().get("attr3"));
|
||||
assertEquals(2, user1.getAttributesAsListValues().size());
|
||||
assertAttributeValue("value2user1", user1.getAttributesAsListValues().get("attr2"));
|
||||
assertAttributeValue("value4user1", user1.getAttributesAsListValues().get("attr3"));
|
||||
|
||||
user1.getAttributes().clear();
|
||||
realm.users().get(user1Id).update(user1);
|
||||
|
@ -322,6 +329,11 @@ public class UserTest extends AbstractClientTest {
|
|||
assertNull(user1.getAttributes());
|
||||
}
|
||||
|
||||
private void assertAttributeValue(String expectedValue, List<String> attrValues) {
|
||||
assertEquals(1, attrValues.size());
|
||||
assertEquals(expectedValue, attrValues.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendResetPasswordEmail() {
|
||||
UserRepresentation userRep = new UserRepresentation();
|
||||
|
|
|
@ -141,7 +141,7 @@ public abstract class AbstractIdentityProviderTest {
|
|||
identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON);
|
||||
|
||||
UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true);
|
||||
Assert.assertEquals("617-666-7777", user.getAttribute("mobile"));
|
||||
Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -304,7 +304,7 @@ public abstract class AbstractIdentityProviderTest {
|
|||
identityProviderModel.setTrustEmail(true);
|
||||
|
||||
UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", true);
|
||||
Assert.assertEquals("617-666-7777", user.getAttribute("mobile"));
|
||||
Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
|
||||
} finally {
|
||||
identityProviderModel.setTrustEmail(false);
|
||||
getRealm().setVerifyEmail(false);
|
||||
|
|
|
@ -262,6 +262,101 @@ public class FederationProvidersIntegrationTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaseSensitiveAttributeName() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
|
||||
try {
|
||||
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
LDAPObject johnZip = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnzip", "John", "Zip", "johnzip@email.org", "12398");
|
||||
|
||||
// Remove default zipcode mapper and add the mapper for "POstalCode" to test case sensitivity
|
||||
UserFederationMapperModel currentZipMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "zipCodeMapper");
|
||||
appRealm.removeUserFederationMapper(currentZipMapper);
|
||||
FederationTestUtils.addUserAttributeMapper(appRealm, ldapModel, "zipCodeMapper-cs", "postal_code", "POstalCode");
|
||||
|
||||
// Fetch user from LDAP and check that postalCode is filled
|
||||
UserModel user = session.users().getUserByUsername("johnzip", appRealm);
|
||||
String postalCode = user.getFirstAttribute("postal_code");
|
||||
Assert.assertEquals("12398", postalCode);
|
||||
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectLDAPUpdate() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
|
||||
try {
|
||||
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
LDAPObject johnDirect = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", "12399");
|
||||
|
||||
// Fetch user from LDAP and check that postalCode is filled
|
||||
UserModel user = session.users().getUserByUsername("johndirect", appRealm);
|
||||
String postalCode = user.getFirstAttribute("postal_code");
|
||||
Assert.assertEquals("12399", postalCode);
|
||||
|
||||
// Directly update user in LDAP
|
||||
johnDirect.setSingleAttribute(LDAPConstants.POSTAL_CODE, "12400");
|
||||
johnDirect.setSingleAttribute(LDAPConstants.SN, "DirectLDAPUpdated");
|
||||
ldapFedProvider.getLdapIdentityStore().update(johnDirect);
|
||||
|
||||
// Verify that postalCode is still the same as we read it's value from Keycloak DB
|
||||
user = session.users().getUserByUsername("johndirect", appRealm);
|
||||
postalCode = user.getFirstAttribute("postal_code");
|
||||
Assert.assertEquals("12399", postalCode);
|
||||
|
||||
// Check user.getAttributes()
|
||||
postalCode = user.getAttributes().get("postal_code").get(0);
|
||||
Assert.assertEquals("12399", postalCode);
|
||||
|
||||
// LastName is new as lastName mapper will read the value from LDAP
|
||||
String lastName = user.getLastName();
|
||||
Assert.assertEquals("DirectLDAPUpdated", lastName);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||
|
||||
// Update postalCode mapper to always read the value from LDAP
|
||||
UserFederationMapperModel zipMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "zipCodeMapper");
|
||||
zipMapper.getConfig().put(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true");
|
||||
appRealm.updateUserFederationMapper(zipMapper);
|
||||
|
||||
// Update lastName mapper to read the value from Keycloak DB
|
||||
UserFederationMapperModel lastNameMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "last name");
|
||||
lastNameMapper.getConfig().put(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||
appRealm.updateUserFederationMapper(lastNameMapper);
|
||||
|
||||
// Verify that postalCode is read from LDAP now
|
||||
UserModel user = session.users().getUserByUsername("johndirect", appRealm);
|
||||
String postalCode = user.getFirstAttribute("postal_code");
|
||||
Assert.assertEquals("12400", postalCode);
|
||||
|
||||
// Check user.getAttributes()
|
||||
postalCode = user.getAttributes().get("postal_code").get(0);
|
||||
Assert.assertEquals("12400", postalCode);
|
||||
|
||||
Assert.assertFalse(user.getAttributes().containsKey(UserModel.LAST_NAME));
|
||||
|
||||
// lastName is read from Keycloak DB now
|
||||
String lastName = user.getLastName();
|
||||
Assert.assertEquals("Direct", lastName);
|
||||
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullNameMapper() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.testsuite.federation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
@ -7,7 +9,7 @@ import org.keycloak.federation.ldap.LDAPFederationProvider;
|
|||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||
import org.keycloak.federation.ldap.LDAPUtils;
|
||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapper;
|
||||
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapperFactory;
|
||||
|
@ -69,11 +71,11 @@ class FederationTestUtils {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
public List<String> getAttribute(String name) {
|
||||
if ("postal_code".equals(name)) {
|
||||
return postalCode;
|
||||
return Arrays.asList(postalCode);
|
||||
} else {
|
||||
return null;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -91,18 +93,19 @@ class FederationTestUtils {
|
|||
Assert.assertEquals(expectedFirstName, user.getFirstName());
|
||||
Assert.assertEquals(expectedLastName, user.getLastName());
|
||||
Assert.assertEquals(expectedEmail, user.getEmail());
|
||||
Assert.assertEquals(expectedPostalCode, user.getAttribute("postal_code"));
|
||||
Assert.assertEquals(expectedPostalCode, user.getFirstAttribute("postal_code"));
|
||||
}
|
||||
|
||||
public static void addZipCodeLDAPMapper(RealmModel realm, UserFederationProviderModel providerModel) {
|
||||
addUserAttributeMapper(realm, providerModel, "zipCodeMapper", "postal_code", LDAPConstants.POSTAL_CODE);
|
||||
addUserAttributeMapper(realm, providerModel, "zipCodeMapper", "postal_code", LDAPConstants.POSTAL_CODE);
|
||||
}
|
||||
|
||||
public static void addUserAttributeMapper(RealmModel realm, UserFederationProviderModel providerModel, String mapperName, String userModelAttributeName, String ldapAttributeName) {
|
||||
UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel(mapperName, providerModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, userModelAttributeName,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, ldapAttributeName,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "false");
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "false",
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
}
|
||||
|
||||
|
@ -137,7 +140,7 @@ class FederationTestUtils {
|
|||
|
||||
public static void removeAllLDAPUsers(LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||
LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
|
||||
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm);
|
||||
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm);
|
||||
List<LDAPObject> allUsers = ldapQuery.getResultList();
|
||||
|
||||
for (LDAPObject ldapUser : allUsers) {
|
||||
|
@ -148,7 +151,7 @@ class FederationTestUtils {
|
|||
public static void removeAllLDAPRoles(KeycloakSession session, RealmModel appRealm, UserFederationProviderModel ldapModel, String mapperName) {
|
||||
UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), mapperName);
|
||||
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
LDAPIdentityQuery roleQuery = new RoleLDAPFederationMapper().createRoleQuery(mapperModel, ldapProvider);
|
||||
LDAPQuery roleQuery = new RoleLDAPFederationMapper().createRoleQuery(mapperModel, ldapProvider);
|
||||
List<LDAPObject> ldapRoles = roleQuery.getResultList();
|
||||
for (LDAPObject ldapRole : ldapRoles) {
|
||||
ldapProvider.getLdapIdentityStore().remove(ldapRole);
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package org.keycloak.testsuite.federation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.representations.IDToken;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class LDAPExampleServlet extends HttpServlet {
|
||||
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
KeycloakSecurityContext securityContext = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
|
||||
IDToken idToken = securityContext.getIdToken();
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.println("<html><head><title>LDAP Portal</title></head><body>");
|
||||
out.println("<table border><tr><th>Attribute name</th><th>Attribute values</th></tr>");
|
||||
|
||||
out.printf("<tr><td>%s</td><td>%s</td></tr>", "preferred_username", idToken.getPreferredUsername());
|
||||
out.println();
|
||||
out.printf("<tr><td>%s</td><td>%s</td></tr>", "name", idToken.getName());
|
||||
out.println();
|
||||
out.printf("<tr><td>%s</td><td>%s</td></tr>", "email", idToken.getEmail());
|
||||
out.println();
|
||||
|
||||
for (Map.Entry<String, Object> claim : idToken.getOtherClaims().entrySet()) {
|
||||
Object value = claim.getValue();
|
||||
|
||||
if (value instanceof List) {
|
||||
List<String> asList = (List<String>) value;
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (String item : asList) {
|
||||
result.append(item + "<br>");
|
||||
}
|
||||
value = result.toString();
|
||||
}
|
||||
|
||||
out.printf("<tr><td>%s</td><td>%s</td></tr>", claim.getKey(), value);
|
||||
out.println();
|
||||
}
|
||||
|
||||
out.println("</table></body></html>");
|
||||
out.flush();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
package org.keycloak.testsuite.federation;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.RuleChain;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserFederationProvider;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.adapter.AdapterTest;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.LDAPRule;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class LDAPMultipleAttributesTest {
|
||||
|
||||
protected String APP_SERVER_BASE_URL = "http://localhost:8081";
|
||||
protected String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(APP_SERVER_BASE_URL + "/auth")).build("test").toString();
|
||||
|
||||
private static LDAPRule ldapRule = new LDAPRule();
|
||||
|
||||
private static UserFederationProviderModel ldapModel = null;
|
||||
|
||||
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
Map<String,String> ldapConfig = ldapRule.getConfig();
|
||||
ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
|
||||
|
||||
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
|
||||
FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
|
||||
FederationTestUtils.addUserAttributeMapper(appRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET);
|
||||
|
||||
// Create ldap-portal client
|
||||
ClientModel ldapClient = appRealm.addClient("ldap-portal");
|
||||
ldapClient.addRedirectUri("/ldap-portal");
|
||||
ldapClient.addRedirectUri("/ldap-portal/*");
|
||||
ldapClient.setManagementUrl("/ldap-portal");
|
||||
ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("postalCode", "postal_code", "postal_code", "String", true, "", true, true, true));
|
||||
ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("street", "street", "street", "String", true, "", true, true, false));
|
||||
ldapClient.addScopeMapping(appRealm.getRole("user"));
|
||||
ldapClient.setSecret("password");
|
||||
|
||||
// Deploy ldap-portal client
|
||||
URL url = getClass().getResource("/ldap/ldap-app-keycloak.json");
|
||||
keycloakRule.createApplicationDeployment()
|
||||
.name("ldap-portal").contextPath("/ldap-portal")
|
||||
.servletClass(LDAPExampleServlet.class).adapterConfigPath(url.getPath())
|
||||
.role("user").deployApplication();
|
||||
}
|
||||
});
|
||||
|
||||
@ClassRule
|
||||
public static TestRule chain = RuleChain
|
||||
.outerRule(ldapRule)
|
||||
.around(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@WebResource
|
||||
protected WebDriver driver;
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Test
|
||||
public void testModel() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
|
||||
FederationTestUtils.assertUserImported(session.users(), appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", "88441");
|
||||
|
||||
UserModel user = session.users().getUserByUsername("bwilson", appRealm);
|
||||
Assert.assertEquals("bwilson@keycloak.org", user.getEmail());
|
||||
Assert.assertEquals("Bruce", user.getFirstName());
|
||||
|
||||
// There are 2 lastnames in ldif
|
||||
Assert.assertTrue("Wilson".equals(user.getLastName()) || "Schneider".equals(user.getLastName()));
|
||||
|
||||
// Actually there are 2 postalCodes
|
||||
List<String> postalCodes = user.getAttribute("postal_code");
|
||||
assertPostalCodes(postalCodes, "88441", "77332");
|
||||
|
||||
postalCodes.remove("77332");
|
||||
user.setAttribute("postal_code", postalCodes);
|
||||
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername("bwilson", appRealm);
|
||||
List<String> postalCodes = user.getAttribute("postal_code");
|
||||
assertPostalCodes(postalCodes, "88441");
|
||||
|
||||
postalCodes.add("77332");
|
||||
user.setAttribute("postal_code", postalCodes);
|
||||
assertPostalCodes(user.getAttribute("postal_code"), "88441", "77332");
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertPostalCodes(List<String> postalCodes, String... expectedPostalCodes) {
|
||||
if (expectedPostalCodes == null && postalCodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Assert.assertEquals(expectedPostalCodes.length, postalCodes.size());
|
||||
for (String expected : expectedPostalCodes) {
|
||||
if (!postalCodes.contains(expected)) {
|
||||
Assert.fail("postalCode '" + expected + "' not in postalCodes: " + postalCodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ldapPortalEndToEndTest() {
|
||||
// Login as bwilson
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/ldap-portal");
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
|
||||
loginPage.login("bwilson", "password");
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(APP_SERVER_BASE_URL + "/ldap-portal"));
|
||||
String pageSource = driver.getPageSource();
|
||||
System.out.println(pageSource);
|
||||
Assert.assertTrue(pageSource.contains("bwilson") && pageSource.contains("Bruce"));
|
||||
Assert.assertTrue(pageSource.contains("street") && pageSource.contains("Elm 5"));
|
||||
Assert.assertTrue(pageSource.contains("postal_code") && pageSource.contains("88441") && pageSource.contains("77332"));
|
||||
|
||||
// Logout
|
||||
String logoutUri = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(APP_SERVER_BASE_URL + "/auth"))
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, APP_SERVER_BASE_URL + "/ldap-portal").build("test").toString();
|
||||
driver.navigate().to(logoutUri);
|
||||
|
||||
// Login as jbrown
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/ldap-portal");
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
|
||||
loginPage.login("jbrown", "password");
|
||||
Assert.assertTrue(driver.getCurrentUrl().startsWith(APP_SERVER_BASE_URL + "/ldap-portal"));
|
||||
pageSource = driver.getPageSource();
|
||||
System.out.println(pageSource);
|
||||
Assert.assertTrue(pageSource.contains("jbrown") && pageSource.contains("James Brown"));
|
||||
Assert.assertFalse(pageSource.contains("street"));
|
||||
Assert.assertTrue(pageSource.contains("postal_code") && pageSource.contains("88441"));
|
||||
Assert.assertFalse(pageSource.contains("77332"));
|
||||
|
||||
// Logout
|
||||
driver.navigate().to(logoutUri);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
|||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapper;
|
||||
import org.keycloak.models.AccountRoles;
|
||||
|
@ -329,7 +329,7 @@ public class LDAPRoleMappingsTest {
|
|||
}
|
||||
|
||||
private void deleteRoleMappingsInLDAP(UserFederationMapperModel roleMapperModel, RoleLDAPFederationMapper roleMapper, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, String roleName) {
|
||||
LDAPIdentityQuery ldapQuery = roleMapper.createRoleQuery(roleMapperModel, ldapProvider);
|
||||
LDAPQuery ldapQuery = roleMapper.createRoleQuery(roleMapperModel, ldapProvider);
|
||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||
Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(LDAPConstants.CN), roleName);
|
||||
ldapQuery.where(roleNameCondition);
|
||||
|
|
|
@ -126,8 +126,8 @@ public class SyncProvidersTest {
|
|||
FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
|
||||
LDAPObject ldapUser5 = ldapFedProvider.loadLDAPUserByUsername(testRealm, "user5");
|
||||
// NOTE: Changing LDAP attributes directly here
|
||||
ldapUser5.setAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
|
||||
ldapUser5.setAttribute(LDAPConstants.POSTAL_CODE, "521");
|
||||
ldapUser5.setSingleAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
|
||||
ldapUser5.setSingleAttribute(LDAPConstants.POSTAL_CODE, "521");
|
||||
ldapFedProvider.getLdapIdentityStore().update(ldapUser5);
|
||||
|
||||
// Assert still old users in local provider
|
||||
|
|
|
@ -54,7 +54,7 @@ public class EmailTest {
|
|||
UserModel user = manager.getSession().users().addUser(appRealm, "login-test");
|
||||
user.setEmail("login@test.com");
|
||||
user.setEnabled(true);
|
||||
user.setAttribute(UserModel.LOCALE, "de");
|
||||
user.setSingleAttribute(UserModel.LOCALE, "de");
|
||||
|
||||
UserCredentialModel creds = new UserCredentialModel();
|
||||
creds.setType(CredentialRepresentation.PASSWORD);
|
||||
|
@ -91,7 +91,7 @@ public class EmailTest {
|
|||
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
manager.getSession().users().getUserByUsername("login-test", appRealm).setAttribute(UserModel.LOCALE, "en");
|
||||
manager.getSession().users().getUserByUsername("login-test", appRealm).setSingleAttribute(UserModel.LOCALE, "en");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -159,7 +158,7 @@ public class AdapterTest extends AbstractModelTest {
|
|||
test1CreateRealm();
|
||||
|
||||
UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke");
|
||||
user.setAttribute("attr1", "val1");
|
||||
user.setSingleAttribute("attr1", "val1");
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
|
||||
RoleModel testRole = realmModel.addRole("test");
|
||||
|
|
|
@ -140,6 +140,22 @@ public class ImportTest extends AbstractModelTest {
|
|||
Assert.assertEquals(1, appRoles.size());
|
||||
Assert.assertEquals("app-admin", appRoles.iterator().next().getName());
|
||||
|
||||
// Test attributes
|
||||
Map<String, List<String>> attrs = wburke.getAttributes();
|
||||
Assert.assertEquals(1, attrs.size());
|
||||
List<String> attrVals = attrs.get("email");
|
||||
Assert.assertEquals(1, attrVals.size());
|
||||
Assert.assertEquals("bburke@redhat.com", attrVals.get(0));
|
||||
|
||||
attrs = admin.getAttributes();
|
||||
Assert.assertEquals(2, attrs.size());
|
||||
attrVals = attrs.get("key1");
|
||||
Assert.assertEquals(1, attrVals.size());
|
||||
Assert.assertEquals("val1", attrVals.get(0));
|
||||
attrVals = attrs.get("key2");
|
||||
Assert.assertEquals(2, attrVals.size());
|
||||
Assert.assertTrue(attrVals.contains("val21") && attrVals.contains("val22"));
|
||||
|
||||
// Test client
|
||||
ClientModel oauthClient = realm.getClientByClientId("oauthclient");
|
||||
Assert.assertEquals("clientpassword", oauthClient.getSecret());
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -137,6 +138,61 @@ public class UserModelTest extends AbstractModelTest {
|
|||
Assert.assertTrue(user.getRequiredActions().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserMultipleAttributes() throws Exception {
|
||||
RealmModel realm = realmManager.createRealm("original");
|
||||
UserModel user = session.users().addUser(realm, "user");
|
||||
|
||||
user.setSingleAttribute("key1", "value1");
|
||||
List<String> attrVals = new ArrayList<>(Arrays.asList( "val21", "val22" ));
|
||||
user.setAttribute("key2", attrVals);
|
||||
|
||||
commit();
|
||||
|
||||
// Test read attributes
|
||||
realm = realmManager.getRealmByName("original");
|
||||
user = session.users().getUserByUsername("user", realm);
|
||||
|
||||
attrVals = user.getAttribute("key1");
|
||||
Assert.assertEquals(1, attrVals.size());
|
||||
Assert.assertEquals("value1", attrVals.get(0));
|
||||
Assert.assertEquals("value1", user.getFirstAttribute("key1"));
|
||||
|
||||
attrVals = user.getAttribute("key2");
|
||||
Assert.assertEquals(2, attrVals.size());
|
||||
Assert.assertTrue(attrVals.contains("val21"));
|
||||
Assert.assertTrue(attrVals.contains("val22"));
|
||||
|
||||
attrVals = user.getAttribute("key3");
|
||||
Assert.assertTrue(attrVals.isEmpty());
|
||||
Assert.assertNull(user.getFirstAttribute("key3"));
|
||||
|
||||
Map<String, List<String>> allAttrVals = user.getAttributes();
|
||||
Assert.assertEquals(2, allAttrVals.size());
|
||||
Assert.assertEquals(allAttrVals.get("key1"), user.getAttribute("key1"));
|
||||
Assert.assertEquals(allAttrVals.get("key2"), user.getAttribute("key2"));
|
||||
|
||||
// Test searching
|
||||
Map<String, String> attributes = new HashMap<String, String>();
|
||||
attributes.put("key2", "val22");
|
||||
List<UserModel> users = session.users().searchForUserByAttributes(attributes, realm);
|
||||
Assert.assertEquals(1, users.size());
|
||||
Assert.assertEquals(users.get(0), user);
|
||||
|
||||
// Test remove and rewrite attribute
|
||||
user.removeAttribute("key1");
|
||||
user.setSingleAttribute("key2", "val23");
|
||||
|
||||
commit();
|
||||
|
||||
realm = realmManager.getRealmByName("original");
|
||||
user = session.users().getUserByUsername("user", realm);
|
||||
Assert.assertNull(user.getFirstAttribute("key1"));
|
||||
attrVals = user.getAttribute("key2");
|
||||
Assert.assertEquals(1, attrVals.size());
|
||||
Assert.assertEquals("val23", attrVals.get(0));
|
||||
}
|
||||
|
||||
public static void assertEquals(UserModel expected, UserModel actual) {
|
||||
Assert.assertEquals(expected.getUsername(), actual.getUsername());
|
||||
Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
|
||||
|
|
|
@ -69,7 +69,9 @@ import javax.ws.rs.core.Response;
|
|||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
@ -616,19 +618,22 @@ public class AccessTokenTest {
|
|||
KeycloakSession session = keycloakRule.startSession();
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername("test-user@localhost", realm);
|
||||
user.setAttribute("street", "5 Yawkey Way");
|
||||
user.setAttribute("locality", "Boston");
|
||||
user.setAttribute("region", "MA");
|
||||
user.setAttribute("postal_code", "02115");
|
||||
user.setAttribute("country", "USA");
|
||||
user.setAttribute("phone", "617-777-6666");
|
||||
user.setSingleAttribute("street", "5 Yawkey Way");
|
||||
user.setSingleAttribute("locality", "Boston");
|
||||
user.setSingleAttribute("region", "MA");
|
||||
user.setSingleAttribute("postal_code", "02115");
|
||||
user.setSingleAttribute("country", "USA");
|
||||
user.setSingleAttribute("phone", "617-777-6666");
|
||||
List<String> departments = Arrays.asList("finance", "development");
|
||||
user.setAttribute("departments", departments);
|
||||
ClientModel app = realm.getClientByClientId("test-app");
|
||||
ProtocolMapperModel mapper = AddressMapper.createAddressMapper(true, true);
|
||||
app.addProtocolMapper(mapper);
|
||||
app.addProtocolMapper(HardcodedClaim.create("hard", "hard", "coded", "String", false, null, true, true));
|
||||
app.addProtocolMapper(HardcodedClaim.create("hard-nested", "nested.hard", "coded-nested", "String", false, null, true, true));
|
||||
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true));
|
||||
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true));
|
||||
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true, false));
|
||||
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true, false));
|
||||
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("departments", "departments", "department", "String", true, "", true, true, true));
|
||||
app.addProtocolMapper(HardcodedRole.create("hard-realm", "hardcoded"));
|
||||
app.addProtocolMapper(HardcodedRole.create("hard-app", "app.hardcoded"));
|
||||
app.addProtocolMapper(RoleNameMapper.create("rename-app-role", "test-app.customer-user", "realm-user"));
|
||||
|
@ -655,6 +660,9 @@ public class AccessTokenTest {
|
|||
Assert.assertEquals("coded-nested", nested.get("hard"));
|
||||
nested = (Map)idToken.getOtherClaims().get("home");
|
||||
Assert.assertEquals("617-777-6666", nested.get("phone"));
|
||||
List<String> departments = (List<String>)idToken.getOtherClaims().get("department");
|
||||
Assert.assertEquals(2, departments.size());
|
||||
Assert.assertTrue(departments.contains("finance") && departments.contains("development"));
|
||||
|
||||
AccessToken accessToken = getAccessToken(tokenResponse);
|
||||
Assert.assertEquals(accessToken.getName(), "Tom Brady");
|
||||
|
@ -671,6 +679,9 @@ public class AccessTokenTest {
|
|||
Assert.assertEquals("coded-nested", nested.get("hard"));
|
||||
nested = (Map)accessToken.getOtherClaims().get("home");
|
||||
Assert.assertEquals("617-777-6666", nested.get("phone"));
|
||||
departments = (List<String>)idToken.getOtherClaims().get("department");
|
||||
Assert.assertEquals(2, departments.size());
|
||||
Assert.assertTrue(departments.contains("finance") && departments.contains("development"));
|
||||
Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains("hardcoded"));
|
||||
Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains("realm-user"));
|
||||
Assert.assertFalse(accessToken.getResourceAccess("test-app").getRoles().contains("customer-user"));
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"realm": "test",
|
||||
"resource": "ldap-portal",
|
||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url": "http://localhost:8081/auth",
|
||||
"ssl-required" : "external",
|
||||
"credentials": {
|
||||
"secret": "password"
|
||||
}
|
||||
}
|
|
@ -19,4 +19,29 @@ objectclass: top
|
|||
objectclass: organizationalUnit
|
||||
ou: FinanceRoles
|
||||
|
||||
dn: uid=jbrown,ou=People,dc=keycloak,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
uid: jbrown
|
||||
cn: James
|
||||
sn: Brown
|
||||
mail: jbrown@keycloak.org
|
||||
postalCode: 88441
|
||||
userPassword: password
|
||||
|
||||
dn: uid=bwilson,ou=People,dc=keycloak,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
uid: bwilson
|
||||
cn: Bruce
|
||||
sn: Wilson
|
||||
sn: Schneider
|
||||
mail: bwilson@keycloak.org
|
||||
postalCode: 88441
|
||||
postalCode: 77332
|
||||
street: Elm 5
|
||||
userPassword: password
|
||||
|
|
|
@ -82,6 +82,15 @@
|
|||
{
|
||||
"username": "admin",
|
||||
"enabled": true,
|
||||
"attributes": {
|
||||
"key1": [
|
||||
"val1"
|
||||
],
|
||||
"key2": [
|
||||
"val21",
|
||||
"val22"
|
||||
]
|
||||
},
|
||||
"credentials": [
|
||||
{
|
||||
"type": "password",
|
||||
|
|
Loading…
Reference in a new issue