Merge pull request #1963 from mposolda/master

KEYCLOAK-1899 Added HardcodedLDAPRoleMapper
This commit is contained in:
Marek Posolda 2015-12-22 20:43:09 +01:00
commit 6752a4f9b0
18 changed files with 290 additions and 63 deletions

View file

@ -1,14 +1,11 @@
package org.keycloak.broker.provider;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
@ -20,7 +17,7 @@ import java.util.List;
*/
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
public static final String ROLE = "role";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
ProviderConfigProperty property;
@ -32,34 +29,6 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
configProperties.add(property);
}
public static String[] parseRole(String role) {
int scopeIndex = role.lastIndexOf('.');
if (scopeIndex > -1) {
String appName = role.substring(0, scopeIndex);
role = role.substring(scopeIndex + 1);
String[] rtn = {appName, role};
return rtn;
} else {
String[] rtn = {null, role};
return rtn;
}
}
public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
String[] parsedRole = parseRole(roleName);
RoleModel role = null;
if (parsedRole[0] == null) {
role = realm.getRole(parsedRole[1]);
} else {
ClientModel client = realm.getClientByClientId(parsedRole[0]);
role = client.getRole(parsedRole[1]);
}
return role;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
@ -93,7 +62,7 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(ROLE);
RoleModel role = getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
user.grantRole(role);
}

View file

@ -10,6 +10,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
@ -80,7 +81,7 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
if (hasClaimValue(mapperModel, context)) {
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
user.grantRole(role);
}
@ -90,7 +91,7 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
if (!hasClaimValue(mapperModel, context)) {
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
user.deleteRoleMapping(role);
}

View file

@ -11,6 +11,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.JsonWebToken;
@ -85,7 +86,7 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
//if (token == null) return;
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
String[] parseRole = HardcodedRoleMapper.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
String[] parseRole = KeycloakModelUtils.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
String externalRoleName = parseRole[1];
String claimName = null;
if (parseRole[0] == null) {
@ -95,7 +96,7 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
}
Object claim = getClaimValue(token, claimName);
if (valueEquals(externalRoleName, claim)) {
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
return role;
}

View file

@ -15,6 +15,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.JsonWebToken;
@ -95,7 +96,7 @@ public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
if (isAttributePresent(mapperModel, context)) {
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
user.grantRole(role);
}
@ -125,7 +126,7 @@ public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
if (!isAttributePresent(mapperModel, context)) {
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
user.deleteRoleMapping(role);
}

View file

@ -75,7 +75,7 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
}
@Override
public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
checkMandatoryConfigAttribute(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", mapperModel);
}

View file

@ -0,0 +1,109 @@
package org.keycloak.federation.ldap.mappers;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.UserModelDelegate;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HardcodedLDAPRoleMapper extends AbstractLDAPFederationMapper {
private static final Logger logger = Logger.getLogger(HardcodedLDAPRoleMapper.class);
public static final String ROLE = "role";
public HardcodedLDAPRoleMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
super(mapperModel, ldapProvider, realm);
}
@Override
public void beforeLDAPQuery(LDAPQuery query) {
}
@Override
public UserModel proxy(LDAPObject ldapUser, UserModel delegate) {
return new UserModelDelegate(delegate) {
@Override
public Set<RoleModel> getRealmRoleMappings() {
Set<RoleModel> roles = super.getRealmRoleMappings();
RoleModel role = getRole();
if (role != null && role.getContainer().equals(realm)) {
roles.add(role);
}
return roles;
}
@Override
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
Set<RoleModel> roles = super.getClientRoleMappings(app);
RoleModel role = getRole();
if (role != null && role.getContainer().equals(app)) {
roles.add(role);
}
return roles;
}
@Override
public boolean hasRole(RoleModel role) {
return super.hasRole(role) || role.equals(getRole());
}
@Override
public Set<RoleModel> getRoleMappings() {
Set<RoleModel> roles = super.getRoleMappings();
RoleModel role = getRole();
if (role != null) {
roles.add(role);
}
return roles;
}
@Override
public void deleteRoleMapping(RoleModel role) {
if (role.equals(getRole())) {
throw new ModelException("Not possible to delete role. It's hardcoded by LDAP mapper");
} else {
super.deleteRoleMapping(role);
}
}
};
}
@Override
public void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser) {
}
@Override
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, boolean isCreate) {
}
private RoleModel getRole() {
String roleName = mapperModel.getConfig().get(HardcodedLDAPRoleMapper.ROLE);
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) {
logger.warnf("Hardcoded role '%s' configured in mapper '%s' is not available anymore");
}
return role;
}
}

View file

@ -0,0 +1,78 @@
package org.keycloak.federation.ldap.mappers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.mappers.MapperConfigValidationException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HardcodedLDAPRoleMapperFactory extends AbstractLDAPFederationMapperFactory {
public static final String PROVIDER_ID = "hardcoded-ldap-role-mapper";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
ProviderConfigProperty roleAttr = createConfigProperty(HardcodedLDAPRoleMapper.ROLE, "Role",
"Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole",
ProviderConfigProperty.ROLE_TYPE, null);
configProperties.add(roleAttr);
}
@Override
public String getHelpText() {
return "When user is imported from LDAP, he will be automatically added into this configured role.";
}
@Override
public String getDisplayCategory() {
return ROLE_MAPPER_CATEGORY;
}
@Override
public String getDisplayType() {
return "Hardcoded Role";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public Map<String, String> getDefaultConfig(UserFederationProviderModel providerModel) {
return new HashMap<>();
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
String roleName = mapperModel.getConfig().get(HardcodedLDAPRoleMapper.ROLE);
if (roleName == null) {
throw new MapperConfigValidationException("Role can't be null");
}
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) {
throw new MapperConfigValidationException("There is no role corresponding to configured value");
}
}
@Override
protected AbstractLDAPFederationMapper createMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider federationProvider, RealmModel realm) {
return new HardcodedLDAPRoleMapper(mapperModel, federationProvider, realm);
}
}

View file

@ -87,7 +87,7 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera
}
@Override
public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", mapperModel);
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute", mapperModel);
}

View file

@ -168,7 +168,7 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
}
@Override
public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel);
checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", mapperModel);

View file

@ -161,7 +161,7 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
}
@Override
public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", mapperModel);
checkMandatoryConfigAttribute(RoleMapperConfig.MODE, "Mode", mapperModel);

View file

@ -1,4 +1,5 @@
org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory
org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory
org.keycloak.federation.ldap.mappers.HardcodedLDAPRoleMapperFactory
org.keycloak.federation.ldap.mappers.membership.role.RoleLDAPFederationMapperFactory
org.keycloak.federation.ldap.mappers.membership.group.GroupLDAPFederationMapperFactory

View file

@ -2,6 +2,7 @@ package org.keycloak.mappers;
import java.util.Map;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.provider.ConfiguredProvider;
@ -37,7 +38,7 @@ public interface UserFederationMapperFactory extends ProviderFactory<UserFederat
* @param mapperModel
* @throws MapperConfigValidationException if configuration provided in mapperModel is not valid
*/
void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException;
void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException;
/**
* Used to detect what are default values for ProviderConfigProperties specified during mapper creation

View file

@ -540,4 +540,34 @@ public final class KeycloakModelUtils {
return result;
}
// Used in various role mappers
public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
String[] parsedRole = parseRole(roleName);
RoleModel role = null;
if (parsedRole[0] == null) {
role = realm.getRole(parsedRole[1]);
} else {
ClientModel client = realm.getClientByClientId(parsedRole[0]);
if (client != null) {
role = client.getRole(parsedRole[1]);
}
}
return role;
}
// Used for hardcoded role mappers
public static String[] parseRole(String role) {
int scopeIndex = role.lastIndexOf('.');
if (scopeIndex > -1) {
String appName = role.substring(0, scopeIndex);
role = role.substring(scopeIndex + 1);
String[] rtn = {appName, role};
return rtn;
} else {
String[] rtn = {null, role};
return rtn;
}
}
}

View file

@ -47,20 +47,6 @@ public class ProtocolMapperUtils {
return null;
}
public static String[] parseRole(String role) {
int scopeIndex = role.lastIndexOf('.');
if (scopeIndex > -1) {
String appName = role.substring(0, scopeIndex);
role = role.substring(scopeIndex + 1);
String[] rtn = {appName, role};
return rtn;
} else {
String[] rtn = {null, role};
return rtn;
}
}
/**
* Find the builtin locale mapper.
*

View file

@ -4,6 +4,7 @@ import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.ProviderConfigProperty;
@ -67,7 +68,7 @@ public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAcc
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
String role = mappingModel.getConfig().get(ROLE_CONFIG);
String[] scopedRole = ProtocolMapperUtils.parseRole(role);
String[] scopedRole = KeycloakModelUtils.parseRole(role);
String appName = scopedRole[0];
String roleName = scopedRole[1];
if (appName != null) {

View file

@ -5,6 +5,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.ProviderConfigProperty;
@ -78,8 +79,8 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
String role = mappingModel.getConfig().get(ROLE_CONFIG);
String newName = mappingModel.getConfig().get(NEW_ROLE_NAME);
String[] scopedRole = ProtocolMapperUtils.parseRole(role);
String[] newScopedRole = ProtocolMapperUtils.parseRole(newName);
String[] scopedRole = KeycloakModelUtils.parseRole(role);
String[] newScopedRole = KeycloakModelUtils.parseRole(newName);
String appName = scopedRole[0];
String roleName = scopedRole[1];
if (appName != null) {

View file

@ -353,7 +353,7 @@ public class UserFederationProviderResource {
private void validateModel(UserFederationMapperModel model) {
try {
UserFederationMapperFactory mapperFactory = (UserFederationMapperFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationMapper.class, model.getFederationMapperType());
mapperFactory.validateConfig(model);
mapperFactory.validateConfig(realm, model);
} catch (MapperConfigValidationException ex) {
throw new ErrorResponseException("Validation error", ex.getMessage(), Response.Status.BAD_REQUEST);
}

View file

@ -15,11 +15,15 @@ import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
import org.keycloak.federation.ldap.mappers.HardcodedLDAPRoleMapper;
import org.keycloak.federation.ldap.mappers.HardcodedLDAPRoleMapperFactory;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationMapperModel;
@ -524,6 +528,50 @@ public class FederationProvidersIntegrationTest {
}
}
@Test
public void testHardcodedRoleMapper() {
KeycloakSession session = keycloakRule.startSession();
UserFederationMapperModel firstNameMapper = null;
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
RoleModel hardcodedRole = appRealm.addRole("hardcoded-role");
// assert that user "johnkeycloak" doesn't have hardcoded role
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertFalse(john.hasRole(hardcodedRole));
UserFederationMapperModel hardcodedMapperModel = KeycloakModelUtils.createUserFederationMapperModel("hardcoded role", ldapModel.getId(), HardcodedLDAPRoleMapperFactory.PROVIDER_ID,
HardcodedLDAPRoleMapper.ROLE, "hardcoded-role");
appRealm.addUserFederationMapper(hardcodedMapperModel);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
RoleModel hardcodedRole = appRealm.getRole("hardcoded-role");
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertTrue(john.hasRole(hardcodedRole));
// Can't remove user from hardcoded role
try {
john.deleteRoleMapping(hardcodedRole);
Assert.fail("Didn't expected to remove role mapping");
} catch (ModelException expected) {
}
// Revert mappers
UserFederationMapperModel hardcodedMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "hardcoded role");
appRealm.removeUserFederationMapper(hardcodedMapperModel);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void testImportExistingUserFromLDAP() throws Exception {
// Add LDAP user with same email like existing model user