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);
|
String value = getJsonValue(mapperModel, context);
|
||||||
if (value != null) {
|
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.KeycloakOIDCIdentityProviderFactory;
|
||||||
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
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.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,7 +74,7 @@ public class UserAttributeMapper extends AbstractClaimMapper {
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
Object value = getClaimValue(mapperModel, context);
|
Object value = getClaimValue(mapperModel, context);
|
||||||
if (value != null) {
|
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) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
Object value = getClaimValue(mapperModel, context);
|
Object value = getClaimValue(mapperModel, context);
|
||||||
String current = user.getAttribute(attribute);
|
String current = user.getFirstAttribute(attribute);
|
||||||
if (value != null && !value.equals(current)) {
|
if (value != null && !value.equals(current)) {
|
||||||
user.setAttribute(attribute, value.toString());
|
user.setSingleAttribute(attribute, value.toString());
|
||||||
} else if (value == null) {
|
} else if (value == null) {
|
||||||
user.removeAttribute(attribute);
|
user.removeAttribute(attribute);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,14 @@ package org.keycloak.broker.saml.mappers;
|
||||||
|
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
|
||||||
import org.keycloak.broker.saml.SAMLEndpoint;
|
import org.keycloak.broker.saml.SAMLEndpoint;
|
||||||
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
|
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
@ -87,7 +84,7 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
Object value = getAttribute(mapperModel, context);
|
Object value = getAttribute(mapperModel, context);
|
||||||
if (value != null) {
|
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) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
Object value = getAttribute(mapperModel, context);
|
Object value = getAttribute(mapperModel, context);
|
||||||
String current = user.getAttribute(attribute);
|
String current = user.getFirstAttribute(attribute);
|
||||||
if (value != null && !value.equals(current)) {
|
if (value != null && !value.equals(current)) {
|
||||||
user.setAttribute(attribute, value.toString());
|
user.setSingleAttribute(attribute, value.toString());
|
||||||
} else if (value == null) {
|
} else if (value == null) {
|
||||||
user.removeAttribute(attribute);
|
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">
|
<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">
|
<changeSet author="bburke@redhat.com" id="1.4.0">
|
||||||
<delete tableName="CLIENT_SESSION_AUTH_STATUS"/>
|
<delete tableName="CLIENT_SESSION_AUTH_STATUS"/>
|
||||||
|
<delete tableName="CLIENT_SESSION_ROLE"/>
|
||||||
<delete tableName="CLIENT_SESSION_PROT_MAPPER"/>
|
<delete tableName="CLIENT_SESSION_PROT_MAPPER"/>
|
||||||
<delete tableName="CLIENT_SESSION_NOTE"/>
|
<delete tableName="CLIENT_SESSION_NOTE"/>
|
||||||
<delete tableName="CLIENT_SESSION"/>
|
<delete tableName="CLIENT_SESSION"/>
|
||||||
|
@ -22,6 +23,12 @@
|
||||||
<constraints nullable="true"/>
|
<constraints nullable="true"/>
|
||||||
</column>
|
</column>
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
<addColumn tableName="USER_ATTRIBUTE">
|
||||||
|
<column name="ID" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
|
||||||
<dropColumn tableName="AUTHENTICATOR" columnName="PROVIDER_ID"/>
|
<dropColumn tableName="AUTHENTICATOR" columnName="PROVIDER_ID"/>
|
||||||
<renameTable oldTableName="AUTHENTICATOR_CONFIG" newTableName="AUTHENTICATOR_CONFIG_ENTRY"/>
|
<renameTable oldTableName="AUTHENTICATOR_CONFIG" newTableName="AUTHENTICATOR_CONFIG_ENTRY"/>
|
||||||
<renameTable oldTableName="AUTHENTICATOR" newTableName="AUTHENTICATOR_CONFIG"/>
|
<renameTable oldTableName="AUTHENTICATOR" newTableName="AUTHENTICATOR_CONFIG"/>
|
||||||
|
@ -110,6 +117,8 @@
|
||||||
</column>
|
</column>
|
||||||
</createTable>
|
</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="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="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"/>
|
<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.mongo.keycloak.entities.MongoMigrationModelEntity",
|
||||||
"org.keycloak.models.entities.AuthenticationExecutionEntity",
|
"org.keycloak.models.entities.AuthenticationExecutionEntity",
|
||||||
"org.keycloak.models.entities.AuthenticationFlowEntity",
|
"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);
|
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.connections.mongo.api.types;
|
package org.keycloak.connections.mongo.api.types;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,9 +15,9 @@ public class MapperContext<T, S> {
|
||||||
private final Class<? extends S> expectedReturnType;
|
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
|
// 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.objectToConvert = objectToConvert;
|
||||||
this.expectedReturnType = expectedReturnType;
|
this.expectedReturnType = expectedReturnType;
|
||||||
this.genericTypes = genericTypes;
|
this.genericTypes = genericTypes;
|
||||||
|
@ -30,7 +31,7 @@ public class MapperContext<T, S> {
|
||||||
return expectedReturnType;
|
return expectedReturnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Class<?>> getGenericTypes() {
|
public List<Type> getGenericTypes() {
|
||||||
return genericTypes;
|
return genericTypes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ public class BasicDBListMapper implements Mapper<BasicDBList, List> {
|
||||||
public List convertObject(MapperContext<BasicDBList, List> context) {
|
public List convertObject(MapperContext<BasicDBList, List> context) {
|
||||||
BasicDBList dbList = context.getObjectToConvert();
|
BasicDBList dbList = context.getObjectToConvert();
|
||||||
ArrayList<Object> appObjects = new ArrayList<Object>();
|
ArrayList<Object> appObjects = new ArrayList<Object>();
|
||||||
Class<?> expectedListElementType = context.getGenericTypes().get(0);
|
Class<?> expectedListElementType = (Class<?>) context.getGenericTypes().get(0);
|
||||||
|
|
||||||
for (Object dbObject : dbList) {
|
for (Object dbObject : dbList) {
|
||||||
MapperContext<Object, Object> newContext = new MapperContext<Object, Object>(dbObject, expectedListElementType, null);
|
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) {
|
public Set convertObject(MapperContext<BasicDBList, Set> context) {
|
||||||
BasicDBList dbList = context.getObjectToConvert();
|
BasicDBList dbList = context.getObjectToConvert();
|
||||||
Set<Object> appObjects = new HashSet<Object>();
|
Set<Object> appObjects = new HashSet<Object>();
|
||||||
Class<?> expectedListElementType = context.getGenericTypes().get(0);
|
Class<?> expectedListElementType = (Class<?>) context.getGenericTypes().get(0);
|
||||||
|
|
||||||
for (Object dbObject : dbList) {
|
for (Object dbObject : dbList) {
|
||||||
MapperContext<Object, Object> newContext = new MapperContext<Object, Object>(dbObject, expectedListElementType, null);
|
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.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,10 +88,14 @@ public class BasicDBObjectMapper<S> implements Mapper<BasicDBObject, S> {
|
||||||
ParameterizedType parameterized = (ParameterizedType) type;
|
ParameterizedType parameterized = (ParameterizedType) type;
|
||||||
Type[] genericTypeArguments = parameterized.getActualTypeArguments();
|
Type[] genericTypeArguments = parameterized.getActualTypeArguments();
|
||||||
|
|
||||||
List<Class<?>> genericTypes = new ArrayList<Class<?>>();
|
List<Type> genericTypes = Arrays.asList(genericTypeArguments);
|
||||||
for (Type genericType : genericTypeArguments) {
|
/*for (Type genericType : genericTypeArguments) {
|
||||||
|
if (genericType instanceof Class) {
|
||||||
genericTypes.add((Class<?>) genericType);
|
genericTypes.add((Class<?>) genericType);
|
||||||
|
} else {
|
||||||
|
System.out.println("foo");
|
||||||
}
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
Class<?> expectedReturnType = (Class<?>)parameterized.getRawType();
|
Class<?> expectedReturnType = (Class<?>)parameterized.getRawType();
|
||||||
context = new MapperContext<Object, Object>(valueFromDB, expectedReturnType, genericTypes);
|
context = new MapperContext<Object, Object>(valueFromDB, expectedReturnType, genericTypes);
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
package org.keycloak.representations.idm;
|
package org.keycloak.representations.idm;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||||
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
@ -21,7 +25,9 @@ public class UserRepresentation {
|
||||||
protected String lastName;
|
protected String lastName;
|
||||||
protected String email;
|
protected String email;
|
||||||
protected String federationLink;
|
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<CredentialRepresentation> credentials;
|
||||||
protected List<String> requiredActions;
|
protected List<String> requiredActions;
|
||||||
protected List<FederatedIdentityRepresentation> federatedIdentities;
|
protected List<FederatedIdentityRepresentation> federatedIdentities;
|
||||||
|
@ -106,17 +112,23 @@ public class UserRepresentation {
|
||||||
this.emailVerified = emailVerified;
|
this.emailVerified = emailVerified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getAttributes() {
|
public Map<String, Object> getAttributes() {
|
||||||
return attributes;
|
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;
|
this.attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserRepresentation attribute(String name, String value) {
|
public UserRepresentation singleAttribute(String name, String value) {
|
||||||
if (this.attributes == null) attributes = new HashMap<String, String>();
|
if (this.attributes == null) attributes = new HashMap<>();
|
||||||
attributes.put(name, value);
|
attributes.put(name, Arrays.asList(value));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
||||||
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
|
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
|
||||||
|
|
||||||
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
|
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
|
||||||
return kerberosPrincipal.equals(local.getAttribute(KERBEROS_PRINCIPAL));
|
return kerberosPrincipal.equals(local.getFirstAttribute(KERBEROS_PRINCIPAL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -229,7 +229,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
||||||
return proxied;
|
return proxied;
|
||||||
} else {
|
} else {
|
||||||
logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
|
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");
|
logger.warn("Will re-create user");
|
||||||
session.userStorage().removeUser(realm, user);
|
session.userStorage().removeUser(realm, user);
|
||||||
}
|
}
|
||||||
|
@ -249,7 +249,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
user.setEmail(email);
|
user.setEmail(email);
|
||||||
user.setFederationLink(model.getId());
|
user.setFederationLink(model.getId());
|
||||||
user.setAttribute(KERBEROS_PRINCIPAL, username + "@" + kerberosConfig.getKerberosRealm());
|
user.setSingleAttribute(KERBEROS_PRINCIPAL, username + "@" + kerberosConfig.getKerberosRealm());
|
||||||
|
|
||||||
if (kerberosConfig.isUpdateProfileFirstLogin()) {
|
if (kerberosConfig.isUpdateProfileFirstLogin()) {
|
||||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
|
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.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
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.query.internal.LDAPQueryConditionsBuilder;
|
||||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
||||||
|
@ -51,7 +51,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
protected EditMode editMode;
|
protected EditMode editMode;
|
||||||
protected LDAPProviderKerberosConfig kerberosConfig;
|
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) {
|
public LDAPFederationProvider(LDAPFederationProviderFactory factory, KeycloakSession session, UserFederationProviderModel model, LDAPIdentityStore ldapIdentityStore) {
|
||||||
this.factory = factory;
|
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");
|
if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server");
|
||||||
|
|
||||||
LDAPObject ldapObject = LDAPUtils.addUserToLDAP(this, realm, user);
|
LDAPObject ldapObject = LDAPUtils.addUserToLDAP(this, realm, user);
|
||||||
user.setAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
|
user.setSingleAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
|
||||||
user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
|
user.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
|
||||||
|
|
||||||
return proxy(realm, user, ldapObject);
|
return proxy(realm, user, ldapObject);
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
|
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();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
|
||||||
// Mapper should replace parameter with correct LDAP mapped attributes
|
// Mapper should replace parameter with correct LDAP mapped attributes
|
||||||
|
@ -229,10 +229,10 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
if (ldapUser == null) {
|
if (ldapUser == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (ldapUser.getUuid().equals(local.getAttribute(LDAPConstants.LDAP_ID))) {
|
if (ldapUser.getUuid().equals(local.getFirstAttribute(LDAPConstants.LDAP_ID))) {
|
||||||
return ldapUser;
|
return ldapUser;
|
||||||
} else {
|
} 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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,8 +271,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
|
|
||||||
String userDN = ldapUser.getDn().toString();
|
String userDN = ldapUser.getDn().toString();
|
||||||
imported.setFederationLink(model.getId());
|
imported.setFederationLink(model.getId());
|
||||||
imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
|
imported.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
|
||||||
imported.setAttribute(LDAPConstants.LDAP_ENTRY_DN, userDN);
|
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(),
|
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);
|
ldapUser.getUuid(), userDN);
|
||||||
|
@ -280,7 +280,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LDAPObject queryByEmail(RealmModel realm, String email) {
|
protected LDAPObject queryByEmail(RealmModel realm, String email) {
|
||||||
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
|
||||||
// Mapper should replace "email" in parameter name with correct LDAP mapped attribute
|
// Mapper should replace "email" in parameter name with correct LDAP mapped attribute
|
||||||
|
@ -395,7 +395,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
importUserFromLDAP(realm, ldapUser);
|
importUserFromLDAP(realm, ldapUser);
|
||||||
syncResult.increaseAdded();
|
syncResult.increaseAdded();
|
||||||
} else {
|
} 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
|
// Update keycloak user
|
||||||
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
|
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
|
||||||
|
@ -435,7 +435,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
return proxy(realm, user, ldapObject);
|
return proxy(realm, user, ldapObject);
|
||||||
} else {
|
} 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",
|
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");
|
logger.warn("Will re-create user");
|
||||||
session.userStorage().removeUser(realm, user);
|
session.userStorage().removeUser(realm, user);
|
||||||
}
|
}
|
||||||
|
@ -448,7 +448,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPObject loadLDAPUserByUsername(RealmModel realm, String username) {
|
public LDAPObject loadLDAPUserByUsername(RealmModel realm, String username) {
|
||||||
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
|
||||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
|
|
||||||
String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
|
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.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
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.query.internal.LDAPQueryConditionsBuilder;
|
||||||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
|
||||||
|
@ -87,11 +87,14 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
||||||
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 usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute();
|
||||||
|
|
||||||
|
String alwaysReadValueFromLDAP = String.valueOf(editMode==UserFederationProvider.EditMode.READ_ONLY || editMode== UserFederationProvider.EditMode.WRITABLE);
|
||||||
|
|
||||||
UserFederationMapperModel mapperModel;
|
UserFederationMapperModel mapperModel;
|
||||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
|
|
||||||
// CN is typically used as RDN for Active Directory deployments
|
// 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,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,13 +117,15 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
||||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
|
|
||||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -134,20 +140,23 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
||||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("last name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("last name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
|
|
||||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
|
|
||||||
String createTimestampLdapAttrName = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
|
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,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creation date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, "true");
|
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
|
|
||||||
// map modifyTimeStamp as read-only
|
// map modifyTimeStamp as read-only
|
||||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modify date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modify date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, "true");
|
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +184,7 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
||||||
public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
|
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());
|
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);
|
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?
|
// 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 modifyCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.MODIFY_TIMESTAMP), lastSync);
|
||||||
Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition);
|
Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition);
|
||||||
|
|
||||||
LDAPIdentityQuery userQuery = createQuery(sessionFactory, realmId, model);
|
LDAPQuery userQuery = createQuery(sessionFactory, realmId, model);
|
||||||
userQuery.where(orCondition);
|
userQuery.where(orCondition);
|
||||||
UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model);
|
UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model);
|
||||||
|
|
||||||
|
@ -200,7 +211,7 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
||||||
return result;
|
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();
|
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
|
||||||
|
|
||||||
|
@ -243,9 +254,9 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
||||||
return syncResult;
|
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 {
|
class QueryHolder {
|
||||||
LDAPIdentityQuery query;
|
LDAPQuery query;
|
||||||
}
|
}
|
||||||
|
|
||||||
final QueryHolder queryHolder = new QueryHolder();
|
final QueryHolder queryHolder = new QueryHolder();
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package org.keycloak.federation.ldap;
|
package org.keycloak.federation.ldap;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPDn;
|
import org.keycloak.federation.ldap.idm.model.LDAPDn;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
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.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
|
@ -45,8 +44,8 @@ public class LDAPUtils {
|
||||||
return ldapUser;
|
return ldapUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LDAPIdentityQuery createQueryForUserSearch(LDAPFederationProvider ldapProvider, RealmModel realm) {
|
public static LDAPQuery createQueryForUserSearch(LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||||
LDAPIdentityQuery ldapQuery = new LDAPIdentityQuery(ldapProvider);
|
LDAPQuery ldapQuery = new LDAPQuery(ldapProvider);
|
||||||
LDAPConfig config = ldapProvider.getLdapIdentityStore().getConfig();
|
LDAPConfig config = ldapProvider.getLdapIdentityStore().getConfig();
|
||||||
ldapQuery.setSearchScope(config.getSearchScope());
|
ldapQuery.setSearchScope(config.getSearchScope());
|
||||||
ldapQuery.setSearchDn(config.getUsersDn());
|
ldapQuery.setSearchDn(config.getUsersDn());
|
||||||
|
|
|
@ -2,22 +2,36 @@ package org.keycloak.federation.ldap.idm.model;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class LDAPObject {
|
public class LDAPObject {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(LDAPObject.class);
|
||||||
|
|
||||||
private String uuid;
|
private String uuid;
|
||||||
private LDAPDn dn;
|
private LDAPDn dn;
|
||||||
private String rdnAttributeName;
|
private String rdnAttributeName;
|
||||||
|
|
||||||
private final List<String> objectClasses = new LinkedList<String>();
|
private final List<String> objectClasses = new LinkedList<>();
|
||||||
private final List<String> readOnlyAttributeNames = new LinkedList<String>();
|
|
||||||
private final Map<String, Object> attributes = new HashMap<String, Object>();
|
// 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() {
|
public String getUuid() {
|
||||||
return uuid;
|
return uuid;
|
||||||
|
@ -49,7 +63,7 @@ public class LDAPObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addReadOnlyAttributeName(String readOnlyAttribute) {
|
public void addReadOnlyAttributeName(String readOnlyAttribute) {
|
||||||
readOnlyAttributeNames.add(readOnlyAttribute);
|
readOnlyAttributeNames.add(readOnlyAttribute.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRdnAttributeName() {
|
public String getRdnAttributeName() {
|
||||||
|
@ -60,30 +74,37 @@ public class LDAPObject {
|
||||||
this.rdnAttributeName = rdnAttributeName;
|
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);
|
attributes.put(attributeName, attributeValue);
|
||||||
|
lowerCasedAttributes.put(attributeName.toLowerCase(), attributeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAttribute(String name) {
|
// Case-insensitive
|
||||||
attributes.remove(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Object getAttribute(String name) {
|
|
||||||
return attributes.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAttributeAsString(String name) {
|
public String getAttributeAsString(String name) {
|
||||||
Object attrValue = attributes.get(name);
|
Set<String> attrValue = lowerCasedAttributes.get(name.toLowerCase());
|
||||||
if (attrValue != null && !(attrValue instanceof String)) {
|
if (attrValue == null || attrValue.size() == 0) {
|
||||||
throw new IllegalStateException("Expected String but attribute was " + attrValue + " of type " + attrValue.getClass().getName());
|
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;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import static java.util.Collections.unmodifiableSet;
|
||||||
*
|
*
|
||||||
* @author Shane Bryzak
|
* @author Shane Bryzak
|
||||||
*/
|
*/
|
||||||
public class LDAPIdentityQuery {
|
public class LDAPQuery {
|
||||||
|
|
||||||
private final LDAPFederationProvider ldapFedProvider;
|
private final LDAPFederationProvider ldapFedProvider;
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ public class LDAPIdentityQuery {
|
||||||
private final Set<String> returningLdapAttributes = new LinkedHashSet<String>();
|
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
|
// 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> returningReadOnlyLdapAttributes = new LinkedHashSet<String>();
|
||||||
private final Set<String> objectClasses = new LinkedHashSet<String>();
|
private final Set<String> objectClasses = new LinkedHashSet<String>();
|
||||||
|
|
||||||
|
@ -47,46 +48,46 @@ public class LDAPIdentityQuery {
|
||||||
|
|
||||||
private int searchScope = SearchControls.SUBTREE_SCOPE;
|
private int searchScope = SearchControls.SUBTREE_SCOPE;
|
||||||
|
|
||||||
public LDAPIdentityQuery(LDAPFederationProvider ldapProvider) {
|
public LDAPQuery(LDAPFederationProvider ldapProvider) {
|
||||||
this.ldapFedProvider = ldapProvider;
|
this.ldapFedProvider = ldapProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery where(Condition... condition) {
|
public LDAPQuery where(Condition... condition) {
|
||||||
this.conditions.addAll(Arrays.asList(condition));
|
this.conditions.addAll(Arrays.asList(condition));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery sortBy(Sort... sorts) {
|
public LDAPQuery sortBy(Sort... sorts) {
|
||||||
this.ordering.addAll(Arrays.asList(sorts));
|
this.ordering.addAll(Arrays.asList(sorts));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery setSearchDn(String searchDn) {
|
public LDAPQuery setSearchDn(String searchDn) {
|
||||||
this.searchDn = searchDn;
|
this.searchDn = searchDn;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery addObjectClasses(Collection<String> objectClasses) {
|
public LDAPQuery addObjectClasses(Collection<String> objectClasses) {
|
||||||
this.objectClasses.addAll(objectClasses);
|
this.objectClasses.addAll(objectClasses);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery addReturningLdapAttribute(String ldapAttributeName) {
|
public LDAPQuery addReturningLdapAttribute(String ldapAttributeName) {
|
||||||
this.returningLdapAttributes.add(ldapAttributeName);
|
this.returningLdapAttributes.add(ldapAttributeName);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery addReturningReadOnlyLdapAttribute(String ldapAttributeName) {
|
public LDAPQuery addReturningReadOnlyLdapAttribute(String ldapAttributeName) {
|
||||||
this.returningReadOnlyLdapAttributes.add(ldapAttributeName);
|
this.returningReadOnlyLdapAttributes.add(ldapAttributeName.toLowerCase());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery addMappers(Collection<UserFederationMapperModel> mappers) {
|
public LDAPQuery addMappers(Collection<UserFederationMapperModel> mappers) {
|
||||||
this.mappers.addAll(mappers);
|
this.mappers.addAll(mappers);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery setSearchScope(int searchScope) {
|
public LDAPQuery setSearchScope(int searchScope) {
|
||||||
this.searchScope = searchScope;
|
this.searchScope = searchScope;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -169,17 +170,17 @@ public class LDAPIdentityQuery {
|
||||||
return ldapFedProvider.getLdapIdentityStore().countQueryResults(this);
|
return ldapFedProvider.getLdapIdentityStore().countQueryResults(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery setOffset(int offset) {
|
public LDAPQuery setOffset(int offset) {
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery setLimit(int limit) {
|
public LDAPQuery setLimit(int limit) {
|
||||||
this.limit = limit;
|
this.limit = limit;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery setPaginationContext(byte[] paginationContext) {
|
public LDAPQuery setPaginationContext(byte[] paginationContext) {
|
||||||
this.paginationContext = paginationContext;
|
this.paginationContext = paginationContext;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.federation.ldap.LDAPConfig;
|
import org.keycloak.federation.ldap.LDAPConfig;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
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
|
* IdentityStore representation providing minimal SPI
|
||||||
|
@ -48,9 +48,9 @@ public interface IdentityStore {
|
||||||
|
|
||||||
// Identity query
|
// Identity query
|
||||||
|
|
||||||
List<LDAPObject> fetchQueryResults(LDAPIdentityQuery LDAPIdentityQuery);
|
List<LDAPObject> fetchQueryResults(LDAPQuery LDAPQuery);
|
||||||
|
|
||||||
int countQueryResults(LDAPIdentityQuery LDAPIdentityQuery);
|
int countQueryResults(LDAPQuery LDAPQuery);
|
||||||
|
|
||||||
// // Relationship query
|
// // Relationship query
|
||||||
//
|
//
|
||||||
|
|
|
@ -4,11 +4,11 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import javax.naming.NamingEnumeration;
|
import javax.naming.NamingEnumeration;
|
||||||
import javax.naming.NamingException;
|
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.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
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.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.EqualCondition;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition;
|
import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition;
|
||||||
import org.keycloak.federation.ldap.idm.query.internal.InCondition;
|
import org.keycloak.federation.ldap.idm.query.internal.InCondition;
|
||||||
|
@ -108,7 +108,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<LDAPObject> fetchQueryResults(LDAPIdentityQuery identityQuery) {
|
public List<LDAPObject> fetchQueryResults(LDAPQuery identityQuery) {
|
||||||
if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
|
if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
|
||||||
throw new ModelException("LDAP Identity Store does not yet support sorted queries.");
|
throw new ModelException("LDAP Identity Store does not yet support sorted queries.");
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int countQueryResults(LDAPIdentityQuery identityQuery) {
|
public int countQueryResults(LDAPQuery identityQuery) {
|
||||||
int limit = identityQuery.getLimit();
|
int limit = identityQuery.getLimit();
|
||||||
int offset = identityQuery.getOffset();
|
int offset = identityQuery.getOffset();
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
|
|
||||||
// ************ END CREDENTIALS AND USER SPECIFIC STUFF
|
// ************ END CREDENTIALS AND USER SPECIFIC STUFF
|
||||||
|
|
||||||
protected StringBuilder createIdentityTypeSearchFilter(final LDAPIdentityQuery identityQuery) {
|
protected StringBuilder createIdentityTypeSearchFilter(final LDAPQuery identityQuery) {
|
||||||
StringBuilder filter = new StringBuilder();
|
StringBuilder filter = new StringBuilder();
|
||||||
|
|
||||||
for (Condition condition : identityQuery.getConditions()) {
|
for (Condition condition : identityQuery.getConditions()) {
|
||||||
|
@ -382,12 +382,6 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
|
|
||||||
NamingEnumeration<? extends Attribute> ldapAttributes = attributes.getAll();
|
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()) {
|
while (ldapAttributes.hasMore()) {
|
||||||
Attribute ldapAttribute = ldapAttributes.next();
|
Attribute ldapAttribute = ldapAttributes.next();
|
||||||
|
|
||||||
|
@ -403,23 +397,20 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
Object uuidValue = ldapAttribute.get();
|
Object uuidValue = ldapAttribute.get();
|
||||||
ldapObject.setUuid(this.operationManager.decodeEntryUUID(uuidValue));
|
ldapObject.setUuid(this.operationManager.decodeEntryUUID(uuidValue));
|
||||||
} else {
|
} else {
|
||||||
Set<String> attrValues = new TreeSet<>();
|
Set<String> attrValues = new LinkedHashSet<>();
|
||||||
NamingEnumeration<?> enumm = ldapAttribute.getAll();
|
NamingEnumeration<?> enumm = ldapAttribute.getAll();
|
||||||
while (enumm.hasMoreElements()) {
|
while (enumm.hasMoreElements()) {
|
||||||
String attrVal = enumm.next().toString();
|
String attrVal = enumm.next().toString().trim();
|
||||||
attrValues.add(attrVal);
|
attrValues.add(attrVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ldapAttributeName.equalsIgnoreCase(LDAPConstants.OBJECT_CLASS)) {
|
if (ldapAttributeName.equalsIgnoreCase(LDAPConstants.OBJECT_CLASS)) {
|
||||||
ldapObject.setObjectClasses(attrValues);
|
ldapObject.setObjectClasses(attrValues);
|
||||||
} else {
|
|
||||||
if (attrValues.size() == 1) {
|
|
||||||
ldapObject.setAttribute(ldapAttributeName, attrValues.iterator().next());
|
|
||||||
} else {
|
} 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);
|
ldapObject.addReadOnlyAttributeName(ldapAttributeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,28 +431,25 @@ public class LDAPIdentityStore implements IdentityStore {
|
||||||
protected BasicAttributes extractAttributes(LDAPObject ldapObject, boolean isCreate) {
|
protected BasicAttributes extractAttributes(LDAPObject ldapObject, boolean isCreate) {
|
||||||
BasicAttributes entryAttributes = new BasicAttributes();
|
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();
|
String attrName = attrEntry.getKey();
|
||||||
Object attrValue = attrEntry.getValue();
|
Set<String> attrValue = attrEntry.getValue();
|
||||||
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName) && (isCreate || !ldapObject.getRdnAttributeName().equalsIgnoreCase(attrName))) {
|
|
||||||
|
|
||||||
if (String.class.isInstance(attrValue)) {
|
// ldapObject.getReadOnlyAttributeNames() are lower-cased
|
||||||
if (attrValue.toString().trim().length() == 0) {
|
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName.toLowerCase()) && (isCreate || !ldapObject.getRdnAttributeName().equalsIgnoreCase(attrName))) {
|
||||||
attrValue = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
|
|
||||||
}
|
|
||||||
entryAttributes.put(attrName, attrValue);
|
|
||||||
} else if (Collection.class.isInstance(attrValue)) {
|
|
||||||
BasicAttribute attr = new BasicAttribute(attrName);
|
BasicAttribute attr = new BasicAttribute(attrName);
|
||||||
Collection<String> valueCollection = (Collection<String>) attrValue;
|
if (attrValue == null) {
|
||||||
for (String val : valueCollection) {
|
// 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);
|
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.jboss.logging.Logger;
|
||||||
import org.keycloak.federation.ldap.LDAPConfig;
|
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.LDAPConstants;
|
||||||
import org.keycloak.models.ModelException;
|
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 List<SearchResult> result = new ArrayList<SearchResult>();
|
||||||
final SearchControls cons = getSearchControls(identityQuery.getReturningLdapAttributes(), identityQuery.getSearchScope());
|
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.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
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.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.LDAPConstants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
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) {
|
public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
|
||||||
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
||||||
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
|
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
|
||||||
|
if (fullName == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fullName = fullName.trim();
|
fullName = fullName.trim();
|
||||||
if (fullName != null && !fullName.trim().isEmpty()) {
|
if (!fullName.isEmpty()) {
|
||||||
int lastSpaceIndex = fullName.lastIndexOf(" ");
|
int lastSpaceIndex = fullName.lastIndexOf(" ");
|
||||||
if (lastSpaceIndex == -1) {
|
if (lastSpaceIndex == -1) {
|
||||||
user.setLastName(fullName);
|
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) {
|
public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
|
||||||
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
||||||
String fullName = getFullName(localUser.getFirstName(), localUser.getLastName());
|
String fullName = getFullName(localUser.getFirstName(), localUser.getLastName());
|
||||||
ldapUser.setAttribute(ldapFullNameAttrName, fullName);
|
ldapUser.setSingleAttribute(ldapFullNameAttrName, fullName);
|
||||||
|
|
||||||
if (isReadOnly(mapperModel)) {
|
if (isReadOnly(mapperModel)) {
|
||||||
ldapUser.addReadOnlyAttributeName(ldapFullNameAttrName);
|
ldapUser.addReadOnlyAttributeName(ldapFullNameAttrName);
|
||||||
|
@ -80,7 +84,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
ensureTransactionStarted();
|
ensureTransactionStarted();
|
||||||
|
|
||||||
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
||||||
ldapUser.setAttribute(ldapFullNameAttrName, fullName);
|
ldapUser.setSingleAttribute(ldapFullNameAttrName, fullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -92,7 +96,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
|
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
|
||||||
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
|
||||||
query.addReturningLdapAttribute(ldapFullNameAttrName);
|
query.addReturningLdapAttribute(ldapFullNameAttrName);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.keycloak.federation.ldap.mappers;
|
||||||
|
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
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.models.RealmModel;
|
||||||
import org.keycloak.mappers.UserFederationMapper;
|
import org.keycloak.mappers.UserFederationMapper;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
|
@ -58,5 +58,5 @@ public interface LDAPFederationMapper extends UserFederationMapper {
|
||||||
* @param mapperModel
|
* @param mapperModel
|
||||||
* @param query
|
* @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.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
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.query.internal.LDAPQueryConditionsBuilder;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.LDAPConstants;
|
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
|
// 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?
|
// TODO: Rather address this with caching at LDAPIdentityStore level?
|
||||||
private Set<String> rolesSyncedModels = new TreeSet<String>();
|
private Set<String> rolesSyncedModels = new TreeSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
|
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())) {
|
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());
|
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
|
// Send query
|
||||||
List<LDAPObject> ldapRoles = ldapQuery.getResultList();
|
List<LDAPObject> ldapRoles = ldapQuery.getResultList();
|
||||||
|
@ -115,8 +115,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPIdentityQuery createRoleQuery(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
|
public LDAPQuery createRoleQuery(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
|
||||||
LDAPIdentityQuery ldapQuery = new LDAPIdentityQuery(ldapProvider);
|
LDAPQuery ldapQuery = new LDAPQuery(ldapProvider);
|
||||||
|
|
||||||
// For now, use same search scope, which is configured "globally" and used for user's search.
|
// For now, use same search scope, which is configured "globally" and used for user's search.
|
||||||
ldapQuery.setSearchScope(ldapProvider.getLdapIdentityStore().getConfig().getSearchScope());
|
ldapQuery.setSearchScope(ldapProvider.getLdapIdentityStore().getConfig().getSearchScope());
|
||||||
|
@ -178,7 +178,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
}
|
}
|
||||||
String[] objClasses = objectClasses.split(",");
|
String[] objClasses = objectClasses.split(",");
|
||||||
|
|
||||||
Set<String> trimmed = new HashSet<String>();
|
Set<String> trimmed = new HashSet<>();
|
||||||
for (String objectClass : objClasses) {
|
for (String objectClass : objClasses) {
|
||||||
objectClass = objectClass.trim();
|
objectClass = objectClass.trim();
|
||||||
if (objectClass.length() > 0) {
|
if (objectClass.length() > 0) {
|
||||||
|
@ -202,7 +202,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
String roleNameAttribute = getRoleNameLdapAttribute(mapperModel);
|
String roleNameAttribute = getRoleNameLdapAttribute(mapperModel);
|
||||||
ldapObject.setRdnAttributeName(roleNameAttribute);
|
ldapObject.setRdnAttributeName(roleNameAttribute);
|
||||||
ldapObject.setObjectClasses(getRoleObjectClasses(mapperModel, ldapProvider));
|
ldapObject.setObjectClasses(getRoleObjectClasses(mapperModel, ldapProvider));
|
||||||
ldapObject.setAttribute(roleNameAttribute, roleName);
|
ldapObject.setSingleAttribute(roleNameAttribute, roleName);
|
||||||
|
|
||||||
LDAPDn roleDn = LDAPDn.fromString(getRolesDn(mapperModel));
|
LDAPDn roleDn = LDAPDn.fromString(getRolesDn(mapperModel));
|
||||||
roleDn.addFirst(roleNameAttribute, roleName);
|
roleDn.addFirst(roleNameAttribute, roleName);
|
||||||
|
@ -220,6 +220,15 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
|
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());
|
memberships.add(ldapUser.getDn().toString());
|
||||||
ldapRole.setAttribute(getMembershipLdapAttribute(mapperModel), memberships);
|
ldapRole.setAttribute(getMembershipLdapAttribute(mapperModel), memberships);
|
||||||
|
|
||||||
|
@ -240,7 +249,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPObject loadLDAPRoleByName(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, String roleName) {
|
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);
|
Condition roleNameCondition = new LDAPQueryConditionsBuilder().equal(new QueryParameter(getRoleNameLdapAttribute(mapperModel)), roleName);
|
||||||
ldapQuery.where(roleNameCondition);
|
ldapQuery.where(roleNameCondition);
|
||||||
return ldapQuery.getFirstResult();
|
return ldapQuery.getFirstResult();
|
||||||
|
@ -248,29 +257,15 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
|
|
||||||
protected Set<String> getExistingMemberships(UserFederationMapperModel mapperModel, LDAPObject ldapRole) {
|
protected Set<String> getExistingMemberships(UserFederationMapperModel mapperModel, LDAPObject ldapRole) {
|
||||||
String memberAttrName = getMembershipLdapAttribute(mapperModel);
|
String memberAttrName = getMembershipLdapAttribute(mapperModel);
|
||||||
Set<String> memberships = new TreeSet<String>();
|
Set<String> memberships = ldapRole.getAttributeAsSet(memberAttrName);
|
||||||
Object existingMemberships = ldapRole.getAttribute(memberAttrName);
|
if (memberships == null) {
|
||||||
|
memberships = new HashSet<>();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return memberships;
|
return memberships;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<LDAPObject> getLDAPRoleMappings(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
|
protected List<LDAPObject> getLDAPRoleMappings(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
|
||||||
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||||
String membershipAttr = getMembershipLdapAttribute(mapperModel);
|
String membershipAttr = getMembershipLdapAttribute(mapperModel);
|
||||||
Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(new QueryParameter(membershipAttr), ldapUser.getDn().toString());
|
Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(new QueryParameter(membershipAttr), ldapUser.getDn().toString());
|
||||||
ldapQuery.where(membershipCondition);
|
ldapQuery.where(membershipCondition);
|
||||||
|
@ -290,7 +285,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
if (mode == Mode.LDAP_ONLY) {
|
||||||
// For LDAP-only we want to retrieve role mappings of target container just from LDAP
|
// 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) {
|
for (RoleModel role : modelRolesCopy) {
|
||||||
if (role.getContainer().equals(targetRoleContainer)) {
|
if (role.getContainer().equals(targetRoleContainer)) {
|
||||||
modelRoleMappings.remove(role);
|
modelRoleMappings.remove(role);
|
||||||
|
@ -408,7 +403,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
|
|
||||||
List<LDAPObject> ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
|
List<LDAPObject> ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
|
||||||
|
|
||||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
Set<RoleModel> roles = new HashSet<>();
|
||||||
String roleNameLdapAttr = getRoleNameLdapAttribute(mapperModel);
|
String roleNameLdapAttr = getRoleNameLdapAttribute(mapperModel);
|
||||||
for (LDAPObject role : ldapRoles) {
|
for (LDAPObject role : ldapRoles) {
|
||||||
String roleName = role.getAttributeAsString(roleNameLdapAttr);
|
String roleName = role.getAttributeAsString(roleNameLdapAttr);
|
||||||
|
@ -430,7 +425,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
|
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
|
||||||
if (role.getContainer().equals(roleContainer)) {
|
if (role.getContainer().equals(roleContainer)) {
|
||||||
|
|
||||||
LDAPIdentityQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||||
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(getRoleNameLdapAttribute(mapperModel)), role.getName());
|
Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(getRoleNameLdapAttribute(mapperModel)), role.getName());
|
||||||
Condition membershipCondition = conditionsBuilder.equal(new QueryParameter(getMembershipLdapAttribute(mapperModel)), ldapUser.getDn().toString());
|
Condition membershipCondition = conditionsBuilder.equal(new QueryParameter(getMembershipLdapAttribute(mapperModel)), ldapUser.getDn().toString());
|
||||||
|
|
|
@ -1,17 +1,25 @@
|
||||||
package org.keycloak.federation.ldap.mappers;
|
package org.keycloak.federation.ldap.mappers;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
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.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
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.RealmModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProvider;
|
import org.keycloak.models.UserFederationProvider;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.UserModelDelegate;
|
||||||
import org.keycloak.models.utils.reflection.Property;
|
import org.keycloak.models.utils.reflection.Property;
|
||||||
import org.keycloak.models.utils.reflection.PropertyCriteria;
|
import org.keycloak.models.utils.reflection.PropertyCriteria;
|
||||||
import org.keycloak.models.utils.reflection.PropertyQueries;
|
import org.keycloak.models.utils.reflection.PropertyQueries;
|
||||||
|
@ -21,10 +29,12 @@ import org.keycloak.models.utils.reflection.PropertyQueries;
|
||||||
*/
|
*/
|
||||||
public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(UserAttributeLDAPFederationMapper.class);
|
||||||
|
|
||||||
private static final Map<String, Property<Object>> userModelProperties;
|
private static final Map<String, Property<Object>> userModelProperties;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
userModelProperties = PropertyQueries.createQuery(UserModel.class).addCriteria(new PropertyCriteria() {
|
Map<String, Property<Object>> userModelProps = PropertyQueries.createQuery(UserModel.class).addCriteria(new PropertyCriteria() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean methodMatches(Method m) {
|
public boolean methodMatches(Method m) {
|
||||||
|
@ -36,11 +46,18 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
||||||
}
|
}
|
||||||
|
|
||||||
}).getResultList();
|
}).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 USER_MODEL_ATTRIBUTE = "user.model.attribute";
|
||||||
public static final String LDAP_ATTRIBUTE = "ldap.attribute";
|
public static final String LDAP_ATTRIBUTE = "ldap.attribute";
|
||||||
public static final String READ_ONLY = "read.only";
|
public static final String READ_ONLY = "read.only";
|
||||||
|
public static final String ALWAYS_READ_VALUE_FROM_LDAP = "always.read.value.from.ldap";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -48,16 +65,21 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
||||||
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||||
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||||
|
|
||||||
Object ldapAttrValue = ldapUser.getAttribute(ldapAttrName);
|
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName.toLowerCase());
|
||||||
if (ldapAttrValue != null && !ldapAttrValue.toString().trim().isEmpty()) {
|
|
||||||
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName);
|
|
||||||
|
|
||||||
if (userModelProperty != null) {
|
if (userModelProperty != null) {
|
||||||
|
|
||||||
// we have java property on UserModel
|
// we have java property on UserModel
|
||||||
userModelProperty.setValue(user, ldapAttrValue);
|
String ldapAttrValue = ldapUser.getAttributeAsString(ldapAttrName);
|
||||||
|
setPropertyOnUserModel(userModelProperty, user, ldapAttrValue);
|
||||||
} else {
|
} else {
|
||||||
// we don't have java property. Let's just setAttribute
|
|
||||||
user.setAttribute(userModelAttrName, (String) ldapAttrValue);
|
// 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 {
|
||||||
|
user.removeAttribute(userModelAttrName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,36 +89,58 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
||||||
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||||
String ldapAttrName = mapperModel.getConfig().get(LDAP_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) {
|
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 {
|
} 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)) {
|
if (isReadOnly(mapperModel)) {
|
||||||
ldapUser.addReadOnlyAttributeName(ldapAttrName);
|
ldapUser.addReadOnlyAttributeName(ldapAttrName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
||||||
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
|
|
||||||
|
|
||||||
final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||||
final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||||
|
boolean isAlwaysReadValueFromLDAP = parseBooleanParameter(mapperModel, ALWAYS_READ_VALUE_FROM_LDAP);
|
||||||
|
|
||||||
TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
|
// For writable mode, we want to propagate writing of attribute to LDAP as well
|
||||||
|
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
|
||||||
|
|
||||||
|
delegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAttribute(String name, String value) {
|
public void setSingleAttribute(String name, String value) {
|
||||||
setLDAPAttribute(name, 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
|
@Override
|
||||||
|
@ -117,28 +161,108 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
||||||
super.setFirstName(firstName);
|
super.setFirstName(firstName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setLDAPAttribute(String modelAttrName, String value) {
|
protected void setLDAPAttribute(String modelAttrName, Object value) {
|
||||||
if (modelAttrName.equalsIgnoreCase(userModelAttrName)) {
|
if (modelAttrName.equalsIgnoreCase(userModelAttrName)) {
|
||||||
if (logger.isTraceEnabled()) {
|
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();
|
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;
|
}
|
||||||
|
|
||||||
|
// 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 {
|
} else {
|
||||||
return delegate;
|
return super.getFirstAttribute(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
|
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, LDAPQuery query) {
|
||||||
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||||
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||||
|
|
||||||
|
@ -160,4 +284,22 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
||||||
private boolean isReadOnly(UserFederationMapperModel mapperModel) {
|
private boolean isReadOnly(UserFederationMapperModel mapperModel) {
|
||||||
return parseBooleanParameter(mapperModel, READ_ONLY);
|
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",
|
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");
|
"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);
|
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
|
@Override
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package org.keycloak.account.freemarker.model;
|
package org.keycloak.account.freemarker.model;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,14 +14,29 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class AccountBean {
|
public class AccountBean {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(AccountBean.class);
|
||||||
|
|
||||||
private final UserModel user;
|
private final UserModel user;
|
||||||
private final MultivaluedMap<String, String> profileFormData;
|
private final MultivaluedMap<String, String> profileFormData;
|
||||||
|
|
||||||
|
// TODO: More proper multi-value attribute support
|
||||||
private final Map<String, String> attributes = new HashMap<>();
|
private final Map<String, String> attributes = new HashMap<>();
|
||||||
|
|
||||||
public AccountBean(UserModel user, MultivaluedMap<String, String> profileFormData) {
|
public AccountBean(UserModel user, MultivaluedMap<String, String> profileFormData) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.profileFormData = profileFormData;
|
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) {
|
if (profileFormData != null) {
|
||||||
for (String key : profileFormData.keySet()) {
|
for (String key : profileFormData.keySet()) {
|
||||||
if (key.startsWith("user.attributes.")) {
|
if (key.startsWith("user.attributes.")) {
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class LocaleHelper {
|
||||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
||||||
if(locale != null){
|
if(locale != null){
|
||||||
if(user != null){
|
if(user != null){
|
||||||
user.setAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
||||||
}
|
}
|
||||||
return locale;
|
return locale;
|
||||||
}else{
|
}else{
|
||||||
|
@ -48,8 +48,8 @@ public class LocaleHelper {
|
||||||
String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
|
String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
|
||||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
||||||
if(locale != null){
|
if(locale != null){
|
||||||
if(user != null && user.getAttribute(UserModel.LOCALE) == null){
|
if(user != null && user.getFirstAttribute(UserModel.LOCALE) == null){
|
||||||
user.setAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
||||||
}
|
}
|
||||||
return locale;
|
return locale;
|
||||||
}else{
|
}else{
|
||||||
|
@ -59,7 +59,7 @@ public class LocaleHelper {
|
||||||
|
|
||||||
//2. User profile
|
//2. User profile
|
||||||
if(user != null && user.getAttributes().containsKey(UserModel.LOCALE)){
|
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);
|
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
||||||
if(locale != null){
|
if(locale != null){
|
||||||
|
|
||||||
|
|
|
@ -206,6 +206,8 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede
|
||||||
if (!user.attributes) {
|
if (!user.attributes) {
|
||||||
user.attributes = {}
|
user.attributes = {}
|
||||||
}
|
}
|
||||||
|
convertAttributeValuesToString(user);
|
||||||
|
|
||||||
$scope.user = angular.copy(user);
|
$scope.user = angular.copy(user);
|
||||||
if(user.federationLink) {
|
if(user.federationLink) {
|
||||||
console.log("federationLink is not null");
|
console.log("federationLink is not null");
|
||||||
|
@ -252,13 +254,15 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
$scope.save = function() {
|
$scope.save = function() {
|
||||||
|
convertAttributeValuesToLists();
|
||||||
|
|
||||||
if ($scope.create) {
|
if ($scope.create) {
|
||||||
User.save({
|
User.save({
|
||||||
realm: realm.realm
|
realm: realm.realm
|
||||||
}, $scope.user, function (data, headers) {
|
}, $scope.user, function (data, headers) {
|
||||||
$scope.changed = false;
|
$scope.changed = false;
|
||||||
|
convertAttributeValuesToString($scope.user);
|
||||||
user = angular.copy($scope.user);
|
user = angular.copy($scope.user);
|
||||||
|
|
||||||
var l = headers().location;
|
var l = headers().location;
|
||||||
|
|
||||||
console.debug("Location == " + l);
|
console.debug("Location == " + l);
|
||||||
|
@ -275,12 +279,33 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede
|
||||||
userId: $scope.user.id
|
userId: $scope.user.id
|
||||||
}, $scope.user, function () {
|
}, $scope.user, function () {
|
||||||
$scope.changed = false;
|
$scope.changed = false;
|
||||||
|
convertAttributeValuesToString($scope.user);
|
||||||
user = angular.copy($scope.user);
|
user = angular.copy($scope.user);
|
||||||
Notifications.success("Your changes have been saved to the 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.reset = function() {
|
||||||
$scope.user = angular.copy(user);
|
$scope.user = angular.copy(user);
|
||||||
$scope.changed = false;
|
$scope.changed = false;
|
||||||
|
|
|
@ -60,6 +60,7 @@ public class LDAPConstants {
|
||||||
public static final String SAM_ACCOUNT_NAME = "sAMAccountName";
|
public static final String SAM_ACCOUNT_NAME = "sAMAccountName";
|
||||||
public static final String EMAIL = "mail";
|
public static final String EMAIL = "mail";
|
||||||
public static final String POSTAL_CODE = "postalCode";
|
public static final String POSTAL_CODE = "postalCode";
|
||||||
|
public static final String STREET = "street";
|
||||||
public static final String MEMBER = "member";
|
public static final String MEMBER = "member";
|
||||||
public static final String MEMBER_OF = "memberOf";
|
public static final String MEMBER_OF = "memberOf";
|
||||||
public static final String OBJECT_CLASS = "objectclass";
|
public static final String OBJECT_CLASS = "objectclass";
|
||||||
|
|
|
@ -27,13 +27,31 @@ public interface UserModel {
|
||||||
|
|
||||||
void setEnabled(boolean enabled);
|
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);
|
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();
|
Set<String> getRequiredActions();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package org.keycloak.models.entities;
|
package org.keycloak.models.entities;
|
||||||
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -23,7 +21,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
||||||
|
|
||||||
private List<String> roleIds;
|
private List<String> roleIds;
|
||||||
|
|
||||||
private Map<String, String> attributes;
|
private Map<String, List<String>> attributes;
|
||||||
private List<String> requiredActions;
|
private List<String> requiredActions;
|
||||||
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
||||||
private List<FederatedIdentityEntity> federatedIdentities;
|
private List<FederatedIdentityEntity> federatedIdentities;
|
||||||
|
@ -101,11 +99,11 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
||||||
this.roleIds = roleIds;
|
this.roleIds = roleIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getAttributes() {
|
public Map<String, List<String>> getAttributes() {
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAttributes(Map<String, String> attributes) {
|
public void setAttributes(Map<String, List<String>> attributes) {
|
||||||
this.attributes = 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.UserFederationProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||||
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -67,7 +68,7 @@ public class ModelToRepresentation {
|
||||||
rep.setRequiredActions(reqActions);
|
rep.setRequiredActions(reqActions);
|
||||||
|
|
||||||
if (user.getAttributes() != null && !user.getAttributes().isEmpty()) {
|
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());
|
attrs.putAll(user.getAttributes());
|
||||||
rep.setAttributes(attrs);
|
rep.setAttributes(attrs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.keycloak.util.UriUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -805,8 +806,17 @@ public class RepresentationToModel {
|
||||||
user.setFederationLink(userRep.getFederationLink());
|
user.setFederationLink(userRep.getFederationLink());
|
||||||
user.setTotp(userRep.isTotp());
|
user.setTotp(userRep.isTotp());
|
||||||
if (userRep.getAttributes() != null) {
|
if (userRep.getAttributes() != null) {
|
||||||
for (Map.Entry<String, String> entry : userRep.getAttributes().entrySet()) {
|
for (Map.Entry<String, Object> entry : userRep.getAttributes().entrySet()) {
|
||||||
user.setAttribute(entry.getKey(), entry.getValue());
|
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) {
|
if (userRep.getRequiredActions() != null) {
|
||||||
|
|
|
@ -53,8 +53,13 @@ public class UserModelDelegate implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAttribute(String name, String value) {
|
public void setSingleAttribute(String name, String value) {
|
||||||
delegate.setAttribute(name, value);
|
delegate.setSingleAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, List<String> values) {
|
||||||
|
delegate.setAttribute(name, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -63,12 +68,17 @@ public class UserModelDelegate implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
return delegate.getAttribute(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> getAttributes() {
|
public Map<String, List<String>> getAttributes() {
|
||||||
return delegate.getAttributes();
|
return delegate.getAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,12 +158,23 @@ public class UserAdapter implements UserModel, Comparable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAttribute(String name, String value) {
|
public void setSingleAttribute(String name, String value) {
|
||||||
if (user.getAttributes() == null) {
|
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
|
@Override
|
||||||
|
@ -174,13 +185,23 @@ public class UserAdapter implements UserModel, Comparable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttribute(String name) {
|
public String getFirstAttribute(String name) {
|
||||||
return user.getAttributes()==null ? null : user.getAttributes().get(name);
|
if (user.getAttributes()==null) return null;
|
||||||
|
|
||||||
|
List<String> attrValues = user.getAttributes().get(name);
|
||||||
|
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> getAttributes() {
|
public List<String> getAttribute(String name) {
|
||||||
return user.getAttributes()==null ? Collections.<String, String>emptyMap() : Collections.unmodifiableMap(user.getAttributes());
|
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
|
@Override
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.cache.entities.CachedUser;
|
import org.keycloak.models.cache.entities.CachedUser;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -78,9 +79,15 @@ public class UserAdapter implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAttribute(String name, String value) {
|
public void setSingleAttribute(String name, String value) {
|
||||||
getDelegateForUpdate();
|
getDelegateForUpdate();
|
||||||
updated.setAttribute(name, value);
|
updated.setSingleAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, List<String> values) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.setAttribute(name, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -90,13 +97,20 @@ public class UserAdapter implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttribute(String name) {
|
public String getFirstAttribute(String name) {
|
||||||
if (updated != null) return updated.getAttribute(name);
|
if (updated != null) return updated.getFirstAttribute(name);
|
||||||
return cached.getAttributes().get(name);
|
return cached.getAttributes().getFirst(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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();
|
if (updated != null) return updated.getAttributes();
|
||||||
return cached.getAttributes();
|
return cached.getAttributes();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -29,7 +30,7 @@ public class CachedUser implements Serializable {
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean totp;
|
private boolean totp;
|
||||||
private String federationLink;
|
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> requiredActions = new HashSet<>();
|
||||||
private Set<String> roleMappings = new HashSet<String>();
|
private Set<String> roleMappings = new HashSet<String>();
|
||||||
|
|
||||||
|
@ -93,7 +94,7 @@ public class CachedUser implements Serializable {
|
||||||
return totp;
|
return totp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getAttributes() {
|
public MultivaluedHashMap<String, String> getAttributes() {
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
|
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||||
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
@ -92,14 +93,46 @@ public class UserAdapter implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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()) {
|
for (UserAttributeEntity attr : user.getAttributes()) {
|
||||||
if (attr.getName().equals(name)) {
|
if (attr.getName().equals(name)) {
|
||||||
|
if (!found) {
|
||||||
attr.setValue(value);
|
attr.setValue(value);
|
||||||
|
found = true;
|
||||||
|
} else {
|
||||||
|
toRemove.add(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UserAttributeEntity attr : toRemove) {
|
||||||
|
em.remove(attr);
|
||||||
|
user.getAttributes().remove(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
return;
|
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();
|
UserAttributeEntity attr = new UserAttributeEntity();
|
||||||
|
attr.setId(KeycloakModelUtils.generateId());
|
||||||
attr.setName(name);
|
attr.setName(name);
|
||||||
attr.setValue(value);
|
attr.setValue(value);
|
||||||
attr.setUser(user);
|
attr.setUser(user);
|
||||||
|
@ -120,7 +153,7 @@ public class UserAdapter implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttribute(String name) {
|
public String getFirstAttribute(String name) {
|
||||||
for (UserAttributeEntity attr : user.getAttributes()) {
|
for (UserAttributeEntity attr : user.getAttributes()) {
|
||||||
if (attr.getName().equals(name)) {
|
if (attr.getName().equals(name)) {
|
||||||
return attr.getValue();
|
return attr.getValue();
|
||||||
|
@ -130,10 +163,21 @@ public class UserAdapter implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> getAttributes() {
|
public List<String> getAttribute(String name) {
|
||||||
Map<String, String> result = new HashMap<String, String>();
|
List<String> result = new ArrayList<>();
|
||||||
for (UserAttributeEntity attr : user.getAttributes()) {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.keycloak.models.jpa.entities;
|
package org.keycloak.models.jpa.entities;
|
||||||
|
|
||||||
|
import javax.persistence.CollectionTable;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.ElementCollection;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
|
@ -11,6 +13,8 @@ import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -22,20 +26,29 @@ import java.io.Serializable;
|
||||||
})
|
})
|
||||||
@Table(name="USER_ATTRIBUTE")
|
@Table(name="USER_ATTRIBUTE")
|
||||||
@Entity
|
@Entity
|
||||||
@IdClass(UserAttributeEntity.Key.class)
|
|
||||||
public class UserAttributeEntity {
|
public class UserAttributeEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
@Column(name="ID", length = 36)
|
||||||
|
protected String id;
|
||||||
|
|
||||||
@ManyToOne(fetch= FetchType.LAZY)
|
@ManyToOne(fetch= FetchType.LAZY)
|
||||||
@JoinColumn(name = "USER_ID")
|
@JoinColumn(name = "USER_ID")
|
||||||
protected UserEntity user;
|
protected UserEntity user;
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "NAME")
|
@Column(name = "NAME")
|
||||||
protected String name;
|
protected String name;
|
||||||
@Column(name = "VALUE")
|
@Column(name = "VALUE")
|
||||||
protected String value;
|
protected String value;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -60,47 +73,4 @@ public class UserAttributeEntity {
|
||||||
this.user = user;
|
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.UserModel;
|
||||||
import org.keycloak.models.entities.CredentialEntity;
|
import org.keycloak.models.entities.CredentialEntity;
|
||||||
import org.keycloak.models.entities.UserConsentEntity;
|
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.MongoUserConsentEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||||
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
||||||
|
@ -127,12 +126,24 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAttribute(String name, String value) {
|
public void setSingleAttribute(String name, String value) {
|
||||||
if (user.getAttributes() == null) {
|
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();
|
updateUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,13 +156,23 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttribute(String name) {
|
public String getFirstAttribute(String name) {
|
||||||
return user.getAttributes()==null ? null : user.getAttributes().get(name);
|
if (user.getAttributes()==null) return null;
|
||||||
|
|
||||||
|
List<String> attrValues = user.getAttributes().get(name);
|
||||||
|
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> getAttributes() {
|
public List<String> getAttribute(String name) {
|
||||||
return user.getAttributes()==null ? Collections.<String, String>emptyMap() : Collections.unmodifiableMap(user.getAttributes());
|
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() {
|
public MongoUserEntity getUser() {
|
||||||
|
|
|
@ -238,11 +238,11 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
// generate a persistent user id specifically for each client.
|
// generate a persistent user id specifically for each client.
|
||||||
UserModel user = userSession.getUser();
|
UserModel user = userSession.getUser();
|
||||||
String name = SAML_PERSISTENT_NAME_ID_FOR + "." + clientSession.getClient().getClientId();
|
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;
|
if (samlPersistentId != null) return samlPersistentId;
|
||||||
// "G-" stands for "generated"
|
// "G-" stands for "generated"
|
||||||
samlPersistentId = "G-" + UUID.randomUUID().toString();
|
samlPersistentId = "G-" + UUID.randomUUID().toString();
|
||||||
user.setAttribute(name, samlPersistentId);
|
user.setSingleAttribute(name, samlPersistentId);
|
||||||
return samlPersistentId;
|
return samlPersistentId;
|
||||||
} else if(nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())){
|
} else if(nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())){
|
||||||
// TODO: Support for persistent NameID (pseudo-random identifier persisted in user object)
|
// TODO: Support for persistent NameID (pseudo-random identifier persisted in user object)
|
||||||
|
|
|
@ -13,7 +13,7 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @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) {
|
public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
UserModel user = userSession.getUser();
|
UserModel user = userSession.getUser();
|
||||||
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
|
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
|
||||||
String attributeValue = user.getAttribute(attributeName);
|
String attributeValue = user.getFirstAttribute(attributeName);
|
||||||
if (attributeValue == null) return;
|
if (attributeValue == null) return;
|
||||||
AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue);
|
AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue);
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,15 @@ import java.util.List;
|
||||||
public class ProtocolMapperUtils {
|
public class ProtocolMapperUtils {
|
||||||
public static final String USER_ATTRIBUTE = "user.attribute";
|
public static final String USER_ATTRIBUTE = "user.attribute";
|
||||||
public static final String USER_SESSION_NOTE = "user.session.note";
|
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_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_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_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_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_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 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) {
|
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) {
|
protected void setClaim(IDToken token, UserSessionModel userSession) {
|
||||||
UserModel user = userSession.getUser();
|
UserModel user = userSession.getUser();
|
||||||
AddressClaimSet addressSet = new AddressClaimSet();
|
AddressClaimSet addressSet = new AddressClaimSet();
|
||||||
addressSet.setStreetAddress(user.getAttribute("street"));
|
addressSet.setStreetAddress(user.getFirstAttribute("street"));
|
||||||
addressSet.setLocality(user.getAttribute("locality"));
|
addressSet.setLocality(user.getFirstAttribute("locality"));
|
||||||
addressSet.setRegion(user.getAttribute("region"));
|
addressSet.setRegion(user.getFirstAttribute("region"));
|
||||||
addressSet.setPostalCode(user.getAttribute("postal_code"));
|
addressSet.setPostalCode(user.getFirstAttribute("postal_code"));
|
||||||
addressSet.setCountry(user.getAttribute("country"));
|
addressSet.setCountry(user.getFirstAttribute("country"));
|
||||||
token.getOtherClaims().put("address", addressSet);
|
token.getOtherClaims().put("address", addressSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.protocol.oidc.mappers;
|
package org.keycloak.protocol.oidc.mappers;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.ProtocolMapper;
|
import org.keycloak.protocol.ProtocolMapper;
|
||||||
|
@ -19,6 +20,8 @@ import java.util.Map;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class OIDCAttributeMapperHelper {
|
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 = "claim.name";
|
||||||
public static final String TOKEN_CLAIM_NAME_LABEL = "Token Claim Name";
|
public static final String TOKEN_CLAIM_NAME_LABEL = "Token Claim Name";
|
||||||
public static final String JSON_TYPE = "Claim JSON Type";
|
public static final String JSON_TYPE = "Claim JSON Type";
|
||||||
|
@ -31,6 +34,26 @@ public class OIDCAttributeMapperHelper {
|
||||||
|
|
||||||
public static Object mapAttributeValue(ProtocolMapperModel mappingModel, Object attributeValue) {
|
public static Object mapAttributeValue(ProtocolMapperModel mappingModel, Object attributeValue) {
|
||||||
if (attributeValue == null) return null;
|
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);
|
String type = mappingModel.getConfig().get(JSON_TYPE);
|
||||||
if (type == null) return attributeValue;
|
if (type == null) return attributeValue;
|
||||||
if (type.equals("boolean")) {
|
if (type.equals("boolean")) {
|
||||||
|
@ -53,8 +76,9 @@ public class OIDCAttributeMapperHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Object attributeValue) {
|
public static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Object attributeValue) {
|
||||||
if (attributeValue == null) return;
|
|
||||||
attributeValue = mapAttributeValue(mappingModel, attributeValue);
|
attributeValue = mapAttributeValue(mappingModel, attributeValue);
|
||||||
|
if (attributeValue == null) return;
|
||||||
|
|
||||||
String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME);
|
String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME);
|
||||||
String[] split = protocolClaim.split("\\.");
|
String[] split = protocolClaim.split("\\.");
|
||||||
Map<String, Object> jsonObject = token.getOtherClaims();
|
Map<String, Object> jsonObject = token.getOtherClaims();
|
||||||
|
@ -102,6 +126,11 @@ public class OIDCAttributeMapperHelper {
|
||||||
return "true".equals(mappingModel.getConfig().get(INCLUDE_IN_ACCESS_TOKEN));
|
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) {
|
public static void addAttributeConfig(List<ProviderConfigProperty> configProperties) {
|
||||||
ProviderConfigProperty property;
|
ProviderConfigProperty property;
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package org.keycloak.protocol.oidc.mappers;
|
package org.keycloak.protocol.oidc.mappers;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||||
|
@ -36,6 +36,13 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
OIDCAttributeMapperHelper.addAttributeConfig(configProperties);
|
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";
|
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) {
|
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
|
||||||
UserModel user = userSession.getUser();
|
UserModel user = userSession.getUser();
|
||||||
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
|
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
|
||||||
String attributeValue = user.getAttribute(attributeName);
|
List<String> attributeValue = user.getAttribute(attributeName);
|
||||||
if (attributeValue == null) return;
|
if (attributeValue == null) return;
|
||||||
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue);
|
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue);
|
||||||
}
|
}
|
||||||
|
@ -93,12 +100,18 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
|
||||||
String userAttribute,
|
String userAttribute,
|
||||||
String tokenClaimName, String claimType,
|
String tokenClaimName, String claimType,
|
||||||
boolean consentRequired, String consentText,
|
boolean consentRequired, String consentText,
|
||||||
boolean accessToken, boolean idToken) {
|
boolean accessToken, boolean idToken, boolean multivalued) {
|
||||||
return OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute,
|
ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute,
|
||||||
tokenClaimName, claimType,
|
tokenClaimName, claimType,
|
||||||
consentRequired, consentText,
|
consentRequired, consentText,
|
||||||
accessToken, idToken,
|
accessToken, idToken,
|
||||||
PROVIDER_ID);
|
PROVIDER_ID);
|
||||||
|
|
||||||
|
if (multivalued) {
|
||||||
|
mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
@ -21,8 +24,26 @@ public class AttributeFormDataProcessor {
|
||||||
for (String key : formData.keySet()) {
|
for (String key : formData.keySet()) {
|
||||||
if (!key.startsWith("user.attributes.")) continue;
|
if (!key.startsWith("user.attributes.")) continue;
|
||||||
String attribute = key.substring("user.attributes.".length());
|
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.BadRequestException;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.authentication.RequiredActionFactory;
|
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailProvider;
|
||||||
|
@ -228,8 +227,8 @@ public class UsersResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rep.getAttributes() != null) {
|
if (rep.getAttributesAsListValues() != null) {
|
||||||
for (Map.Entry<String, String> attr : rep.getAttributes().entrySet()) {
|
for (Map.Entry<String, List<String>> attr : rep.getAttributesAsListValues().entrySet()) {
|
||||||
user.setAttribute(attr.getKey(), attr.getValue());
|
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.HttpClient;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -51,8 +52,8 @@ public class ProfileTest {
|
||||||
UserModel user = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
|
UserModel user = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
|
||||||
user.setFirstName("First");
|
user.setFirstName("First");
|
||||||
user.setLastName("Last");
|
user.setLastName("Last");
|
||||||
user.setAttribute("key1", "value1");
|
user.setSingleAttribute("key1", "value1");
|
||||||
user.setAttribute("key2", "value2");
|
user.setSingleAttribute("key2", "value2");
|
||||||
|
|
||||||
ClientModel accountApp = appRealm.getClientByClientId(org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
|
ClientModel accountApp = appRealm.getClientByClientId(org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
|
||||||
|
|
||||||
|
@ -114,8 +115,12 @@ public class ProfileTest {
|
||||||
assertEquals("Last", profile.getString("lastName"));
|
assertEquals("Last", profile.getString("lastName"));
|
||||||
|
|
||||||
JSONObject attributes = profile.getJSONObject("attributes");
|
JSONObject attributes = profile.getJSONObject("attributes");
|
||||||
assertEquals("value1", attributes.getString("key1"));
|
JSONArray attrValue = attributes.getJSONArray("key1");
|
||||||
assertEquals("value2", attributes.getString("key2"));
|
assertEquals(1, attrValue.length());
|
||||||
|
assertEquals("value1", attrValue.get(0));
|
||||||
|
attrValue = attributes.getJSONArray("key2");
|
||||||
|
assertEquals(1, attrValue.length());
|
||||||
|
assertEquals("value2", attrValue.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -12,6 +12,8 @@ import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
|
||||||
import javax.ws.rs.ClientErrorException;
|
import javax.ws.rs.ClientErrorException;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -271,8 +273,8 @@ public class UserTest extends AbstractClientTest {
|
||||||
public void attributes() {
|
public void attributes() {
|
||||||
UserRepresentation user1 = new UserRepresentation();
|
UserRepresentation user1 = new UserRepresentation();
|
||||||
user1.setUsername("user1");
|
user1.setUsername("user1");
|
||||||
user1.attribute("attr1", "value1user1");
|
user1.singleAttribute("attr1", "value1user1");
|
||||||
user1.attribute("attr2", "value2user1");
|
user1.singleAttribute("attr2", "value2user1");
|
||||||
|
|
||||||
Response response = realm.users().create(user1);
|
Response response = realm.users().create(user1);
|
||||||
String user1Id = ApiUtil.getCreatedId(response);
|
String user1Id = ApiUtil.getCreatedId(response);
|
||||||
|
@ -280,40 +282,45 @@ public class UserTest extends AbstractClientTest {
|
||||||
|
|
||||||
UserRepresentation user2 = new UserRepresentation();
|
UserRepresentation user2 = new UserRepresentation();
|
||||||
user2.setUsername("user2");
|
user2.setUsername("user2");
|
||||||
user2.attribute("attr1", "value1user2");
|
user2.singleAttribute("attr1", "value1user2");
|
||||||
user2.attribute("attr2", "value2user2");
|
List<String> vals = new ArrayList<>();
|
||||||
|
vals.add("value2user2");
|
||||||
|
vals.add("value2user2_2");
|
||||||
|
user2.getAttributesAsListValues().put("attr2", vals);
|
||||||
|
|
||||||
response = realm.users().create(user2);
|
response = realm.users().create(user2);
|
||||||
String user2Id = ApiUtil.getCreatedId(response);
|
String user2Id = ApiUtil.getCreatedId(response);
|
||||||
response.close();
|
response.close();
|
||||||
user1 = realm.users().get(user1Id).toRepresentation();
|
user1 = realm.users().get(user1Id).toRepresentation();
|
||||||
assertEquals(2, user1.getAttributes().size());
|
assertEquals(2, user1.getAttributesAsListValues().size());
|
||||||
assertEquals("value1user1", user1.getAttributes().get("attr1"));
|
assertAttributeValue("value1user1", user1.getAttributesAsListValues().get("attr1"));
|
||||||
assertEquals("value2user1", user1.getAttributes().get("attr2"));
|
assertAttributeValue("value2user1", user1.getAttributesAsListValues().get("attr2"));
|
||||||
|
|
||||||
user2 = realm.users().get(user2Id).toRepresentation();
|
user2 = realm.users().get(user2Id).toRepresentation();
|
||||||
assertEquals(2, user2.getAttributes().size());
|
assertEquals(2, user2.getAttributesAsListValues().size());
|
||||||
assertEquals("value1user2", user2.getAttributes().get("attr1"));
|
assertAttributeValue("value1user2", user2.getAttributesAsListValues().get("attr1"));
|
||||||
assertEquals("value2user2", user2.getAttributes().get("attr2"));
|
vals = user2.getAttributesAsListValues().get("attr2");
|
||||||
|
assertEquals(2, vals.size());
|
||||||
|
assertTrue(vals.contains("value2user2") && vals.contains("value2user2_2"));
|
||||||
|
|
||||||
user1.attribute("attr1", "value3user1");
|
user1.singleAttribute("attr1", "value3user1");
|
||||||
user1.attribute("attr3", "value4user1");
|
user1.singleAttribute("attr3", "value4user1");
|
||||||
|
|
||||||
realm.users().get(user1Id).update(user1);
|
realm.users().get(user1Id).update(user1);
|
||||||
|
|
||||||
user1 = realm.users().get(user1Id).toRepresentation();
|
user1 = realm.users().get(user1Id).toRepresentation();
|
||||||
assertEquals(3, user1.getAttributes().size());
|
assertEquals(3, user1.getAttributesAsListValues().size());
|
||||||
assertEquals("value3user1", user1.getAttributes().get("attr1"));
|
assertAttributeValue("value3user1", user1.getAttributesAsListValues().get("attr1"));
|
||||||
assertEquals("value2user1", user1.getAttributes().get("attr2"));
|
assertAttributeValue("value2user1", user1.getAttributesAsListValues().get("attr2"));
|
||||||
assertEquals("value4user1", user1.getAttributes().get("attr3"));
|
assertAttributeValue("value4user1", user1.getAttributesAsListValues().get("attr3"));
|
||||||
|
|
||||||
user1.getAttributes().remove("attr1");
|
user1.getAttributes().remove("attr1");
|
||||||
realm.users().get(user1Id).update(user1);
|
realm.users().get(user1Id).update(user1);
|
||||||
|
|
||||||
user1 = realm.users().get(user1Id).toRepresentation();
|
user1 = realm.users().get(user1Id).toRepresentation();
|
||||||
assertEquals(2, user1.getAttributes().size());
|
assertEquals(2, user1.getAttributesAsListValues().size());
|
||||||
assertEquals("value2user1", user1.getAttributes().get("attr2"));
|
assertAttributeValue("value2user1", user1.getAttributesAsListValues().get("attr2"));
|
||||||
assertEquals("value4user1", user1.getAttributes().get("attr3"));
|
assertAttributeValue("value4user1", user1.getAttributesAsListValues().get("attr3"));
|
||||||
|
|
||||||
user1.getAttributes().clear();
|
user1.getAttributes().clear();
|
||||||
realm.users().get(user1Id).update(user1);
|
realm.users().get(user1Id).update(user1);
|
||||||
|
@ -322,6 +329,11 @@ public class UserTest extends AbstractClientTest {
|
||||||
assertNull(user1.getAttributes());
|
assertNull(user1.getAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertAttributeValue(String expectedValue, List<String> attrValues) {
|
||||||
|
assertEquals(1, attrValues.size());
|
||||||
|
assertEquals(expectedValue, attrValues.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void sendResetPasswordEmail() {
|
public void sendResetPasswordEmail() {
|
||||||
UserRepresentation userRep = new UserRepresentation();
|
UserRepresentation userRep = new UserRepresentation();
|
||||||
|
|
|
@ -141,7 +141,7 @@ public abstract class AbstractIdentityProviderTest {
|
||||||
identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON);
|
identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON);
|
||||||
|
|
||||||
UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true);
|
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
|
@Test
|
||||||
|
@ -304,7 +304,7 @@ public abstract class AbstractIdentityProviderTest {
|
||||||
identityProviderModel.setTrustEmail(true);
|
identityProviderModel.setTrustEmail(true);
|
||||||
|
|
||||||
UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", 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 {
|
} finally {
|
||||||
identityProviderModel.setTrustEmail(false);
|
identityProviderModel.setTrustEmail(false);
|
||||||
getRealm().setVerifyEmail(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
|
@Test
|
||||||
public void testFullNameMapper() {
|
public void testFullNameMapper() {
|
||||||
KeycloakSession session = keycloakRule.startSession();
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.keycloak.testsuite.federation;
|
package org.keycloak.testsuite.federation;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.Assert;
|
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.LDAPFederationProviderFactory;
|
||||||
import org.keycloak.federation.ldap.LDAPUtils;
|
import org.keycloak.federation.ldap.LDAPUtils;
|
||||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
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.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapper;
|
||||||
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapperFactory;
|
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapperFactory;
|
||||||
|
@ -69,11 +71,11 @@ class FederationTestUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttribute(String name) {
|
public List<String> getAttribute(String name) {
|
||||||
if ("postal_code".equals(name)) {
|
if ("postal_code".equals(name)) {
|
||||||
return postalCode;
|
return Arrays.asList(postalCode);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -91,7 +93,7 @@ class FederationTestUtils {
|
||||||
Assert.assertEquals(expectedFirstName, user.getFirstName());
|
Assert.assertEquals(expectedFirstName, user.getFirstName());
|
||||||
Assert.assertEquals(expectedLastName, user.getLastName());
|
Assert.assertEquals(expectedLastName, user.getLastName());
|
||||||
Assert.assertEquals(expectedEmail, user.getEmail());
|
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) {
|
public static void addZipCodeLDAPMapper(RealmModel realm, UserFederationProviderModel providerModel) {
|
||||||
|
@ -102,7 +104,8 @@ class FederationTestUtils {
|
||||||
UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel(mapperName, providerModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel(mapperName, providerModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, userModelAttributeName,
|
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, userModelAttributeName,
|
||||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, ldapAttributeName,
|
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, ldapAttributeName,
|
||||||
UserAttributeLDAPFederationMapper.READ_ONLY, "false");
|
UserAttributeLDAPFederationMapper.READ_ONLY, "false",
|
||||||
|
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||||
realm.addUserFederationMapper(mapperModel);
|
realm.addUserFederationMapper(mapperModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +140,7 @@ class FederationTestUtils {
|
||||||
|
|
||||||
public static void removeAllLDAPUsers(LDAPFederationProvider ldapProvider, RealmModel realm) {
|
public static void removeAllLDAPUsers(LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||||
LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
|
LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
|
||||||
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm);
|
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm);
|
||||||
List<LDAPObject> allUsers = ldapQuery.getResultList();
|
List<LDAPObject> allUsers = ldapQuery.getResultList();
|
||||||
|
|
||||||
for (LDAPObject ldapUser : allUsers) {
|
for (LDAPObject ldapUser : allUsers) {
|
||||||
|
@ -148,7 +151,7 @@ class FederationTestUtils {
|
||||||
public static void removeAllLDAPRoles(KeycloakSession session, RealmModel appRealm, UserFederationProviderModel ldapModel, String mapperName) {
|
public static void removeAllLDAPRoles(KeycloakSession session, RealmModel appRealm, UserFederationProviderModel ldapModel, String mapperName) {
|
||||||
UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), mapperName);
|
UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), mapperName);
|
||||||
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
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();
|
List<LDAPObject> ldapRoles = roleQuery.getResultList();
|
||||||
for (LDAPObject ldapRole : ldapRoles) {
|
for (LDAPObject ldapRole : ldapRoles) {
|
||||||
ldapProvider.getLdapIdentityStore().remove(ldapRole);
|
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.model.LDAPObject;
|
||||||
import org.keycloak.federation.ldap.idm.query.Condition;
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
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.query.internal.LDAPQueryConditionsBuilder;
|
||||||
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapper;
|
import org.keycloak.federation.ldap.mappers.RoleLDAPFederationMapper;
|
||||||
import org.keycloak.models.AccountRoles;
|
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) {
|
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();
|
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
|
||||||
Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(LDAPConstants.CN), roleName);
|
Condition roleNameCondition = conditionsBuilder.equal(new QueryParameter(LDAPConstants.CN), roleName);
|
||||||
ldapQuery.where(roleNameCondition);
|
ldapQuery.where(roleNameCondition);
|
||||||
|
|
|
@ -126,8 +126,8 @@ public class SyncProvidersTest {
|
||||||
FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
|
FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
|
||||||
LDAPObject ldapUser5 = ldapFedProvider.loadLDAPUserByUsername(testRealm, "user5");
|
LDAPObject ldapUser5 = ldapFedProvider.loadLDAPUserByUsername(testRealm, "user5");
|
||||||
// NOTE: Changing LDAP attributes directly here
|
// NOTE: Changing LDAP attributes directly here
|
||||||
ldapUser5.setAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
|
ldapUser5.setSingleAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
|
||||||
ldapUser5.setAttribute(LDAPConstants.POSTAL_CODE, "521");
|
ldapUser5.setSingleAttribute(LDAPConstants.POSTAL_CODE, "521");
|
||||||
ldapFedProvider.getLdapIdentityStore().update(ldapUser5);
|
ldapFedProvider.getLdapIdentityStore().update(ldapUser5);
|
||||||
|
|
||||||
// Assert still old users in local provider
|
// Assert still old users in local provider
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class EmailTest {
|
||||||
UserModel user = manager.getSession().users().addUser(appRealm, "login-test");
|
UserModel user = manager.getSession().users().addUser(appRealm, "login-test");
|
||||||
user.setEmail("login@test.com");
|
user.setEmail("login@test.com");
|
||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
user.setAttribute(UserModel.LOCALE, "de");
|
user.setSingleAttribute(UserModel.LOCALE, "de");
|
||||||
|
|
||||||
UserCredentialModel creds = new UserCredentialModel();
|
UserCredentialModel creds = new UserCredentialModel();
|
||||||
creds.setType(CredentialRepresentation.PASSWORD);
|
creds.setType(CredentialRepresentation.PASSWORD);
|
||||||
|
@ -91,7 +91,7 @@ public class EmailTest {
|
||||||
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
@Override
|
@Override
|
||||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -159,7 +158,7 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
test1CreateRealm();
|
test1CreateRealm();
|
||||||
|
|
||||||
UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke");
|
UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke");
|
||||||
user.setAttribute("attr1", "val1");
|
user.setSingleAttribute("attr1", "val1");
|
||||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
|
|
||||||
RoleModel testRole = realmModel.addRole("test");
|
RoleModel testRole = realmModel.addRole("test");
|
||||||
|
|
|
@ -140,6 +140,22 @@ public class ImportTest extends AbstractModelTest {
|
||||||
Assert.assertEquals(1, appRoles.size());
|
Assert.assertEquals(1, appRoles.size());
|
||||||
Assert.assertEquals("app-admin", appRoles.iterator().next().getName());
|
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
|
// Test client
|
||||||
ClientModel oauthClient = realm.getClientByClientId("oauthclient");
|
ClientModel oauthClient = realm.getClientByClientId("oauthclient");
|
||||||
Assert.assertEquals("clientpassword", oauthClient.getSecret());
|
Assert.assertEquals("clientpassword", oauthClient.getSecret());
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserModel.RequiredAction;
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -137,6 +138,61 @@ public class UserModelTest extends AbstractModelTest {
|
||||||
Assert.assertTrue(user.getRequiredActions().isEmpty());
|
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) {
|
public static void assertEquals(UserModel expected, UserModel actual) {
|
||||||
Assert.assertEquals(expected.getUsername(), actual.getUsername());
|
Assert.assertEquals(expected.getUsername(), actual.getUsername());
|
||||||
Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
|
Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
|
||||||
|
|
|
@ -69,7 +69,9 @@ import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
@ -616,19 +618,22 @@ public class AccessTokenTest {
|
||||||
KeycloakSession session = keycloakRule.startSession();
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
UserModel user = session.users().getUserByUsername("test-user@localhost", realm);
|
UserModel user = session.users().getUserByUsername("test-user@localhost", realm);
|
||||||
user.setAttribute("street", "5 Yawkey Way");
|
user.setSingleAttribute("street", "5 Yawkey Way");
|
||||||
user.setAttribute("locality", "Boston");
|
user.setSingleAttribute("locality", "Boston");
|
||||||
user.setAttribute("region", "MA");
|
user.setSingleAttribute("region", "MA");
|
||||||
user.setAttribute("postal_code", "02115");
|
user.setSingleAttribute("postal_code", "02115");
|
||||||
user.setAttribute("country", "USA");
|
user.setSingleAttribute("country", "USA");
|
||||||
user.setAttribute("phone", "617-777-6666");
|
user.setSingleAttribute("phone", "617-777-6666");
|
||||||
|
List<String> departments = Arrays.asList("finance", "development");
|
||||||
|
user.setAttribute("departments", departments);
|
||||||
ClientModel app = realm.getClientByClientId("test-app");
|
ClientModel app = realm.getClientByClientId("test-app");
|
||||||
ProtocolMapperModel mapper = AddressMapper.createAddressMapper(true, true);
|
ProtocolMapperModel mapper = AddressMapper.createAddressMapper(true, true);
|
||||||
app.addProtocolMapper(mapper);
|
app.addProtocolMapper(mapper);
|
||||||
app.addProtocolMapper(HardcodedClaim.create("hard", "hard", "coded", "String", false, null, true, true));
|
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(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("custom phone", "phone", "home_phone", "String", true, "", true, true, false));
|
||||||
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true));
|
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-realm", "hardcoded"));
|
||||||
app.addProtocolMapper(HardcodedRole.create("hard-app", "app.hardcoded"));
|
app.addProtocolMapper(HardcodedRole.create("hard-app", "app.hardcoded"));
|
||||||
app.addProtocolMapper(RoleNameMapper.create("rename-app-role", "test-app.customer-user", "realm-user"));
|
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"));
|
Assert.assertEquals("coded-nested", nested.get("hard"));
|
||||||
nested = (Map)idToken.getOtherClaims().get("home");
|
nested = (Map)idToken.getOtherClaims().get("home");
|
||||||
Assert.assertEquals("617-777-6666", nested.get("phone"));
|
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);
|
AccessToken accessToken = getAccessToken(tokenResponse);
|
||||||
Assert.assertEquals(accessToken.getName(), "Tom Brady");
|
Assert.assertEquals(accessToken.getName(), "Tom Brady");
|
||||||
|
@ -671,6 +679,9 @@ public class AccessTokenTest {
|
||||||
Assert.assertEquals("coded-nested", nested.get("hard"));
|
Assert.assertEquals("coded-nested", nested.get("hard"));
|
||||||
nested = (Map)accessToken.getOtherClaims().get("home");
|
nested = (Map)accessToken.getOtherClaims().get("home");
|
||||||
Assert.assertEquals("617-777-6666", nested.get("phone"));
|
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("hardcoded"));
|
||||||
Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains("realm-user"));
|
Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains("realm-user"));
|
||||||
Assert.assertFalse(accessToken.getResourceAccess("test-app").getRoles().contains("customer-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
|
objectclass: organizationalUnit
|
||||||
ou: FinanceRoles
|
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",
|
"username": "admin",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"attributes": {
|
||||||
|
"key1": [
|
||||||
|
"val1"
|
||||||
|
],
|
||||||
|
"key2": [
|
||||||
|
"val21",
|
||||||
|
"val22"
|
||||||
|
]
|
||||||
|
},
|
||||||
"credentials": [
|
"credentials": [
|
||||||
{
|
{
|
||||||
"type": "password",
|
"type": "password",
|
||||||
|
|
Loading…
Reference in a new issue