Merge pull request #1032 from patriot1burke/master

mapper fixes and tests
This commit is contained in:
Bill Burke 2015-03-09 21:33:38 -04:00
commit bf27c22dd5
25 changed files with 585 additions and 230 deletions

View file

@ -9,7 +9,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.protocol.AbstractLoginProtocolFactory; import org.keycloak.protocol.AbstractLoginProtocolFactory;
import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper; import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
import org.keycloak.protocol.saml.mappers.SAMLBasicRoleListMapper; import org.keycloak.protocol.saml.mappers.RoleListMapper;
import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper; import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.picketlink.common.constants.JBossSAMLURIConstants; import org.picketlink.common.constants.JBossSAMLURIConstants;
@ -77,7 +77,7 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
X500SAMLProfileConstants.SURNAME.getFriendlyName(), X500SAMLProfileConstants.SURNAME.getFriendlyName(),
true, "family name"); true, "family name");
builtins.add(model); builtins.add(model);
model = SAMLBasicRoleListMapper.create("role list", "Role", AttributeStatementHelper.BASIC, null, false); model = RoleListMapper.create("role list", "Role", AttributeStatementHelper.BASIC, null, false);
builtins.add(model); builtins.add(model);
defaultBuiltins.add(model); defaultBuiltins.add(model);

View file

@ -73,14 +73,14 @@ public class AttributeStatementHelper {
} }
public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute, String samlAttributeName, String nameFormat, String friendlyName, boolean consentRequired, String consentText, String mapperId) { public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute, String samlAttributeName, String nameFormat, String friendlyName, boolean consentRequired, String consentText, String mapperId) {
ProtocolMapperModel mapper = mapper = new ProtocolMapperModel(); ProtocolMapperModel mapper = new ProtocolMapperModel();
mapper.setName(name); mapper.setName(name);
mapper.setProtocolMapper(mapperId); mapper.setProtocolMapper(mapperId);
mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL); mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
mapper.setConsentRequired(consentRequired); mapper.setConsentRequired(consentRequired);
mapper.setConsentText(consentText); mapper.setConsentText(consentText);
Map<String, String> config = new HashMap<String, String>(); Map<String, String> config = new HashMap<String, String>();
config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute); if (userAttribute != null) config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute);
config.put(SAML_ATTRIBUTE_NAME, samlAttributeName); config.put(SAML_ATTRIBUTE_NAME, samlAttributeName);
if (friendlyName != null) { if (friendlyName != null) {
config.put(FRIENDLY_NAME, friendlyName); config.put(FRIENDLY_NAME, friendlyName);

View file

@ -0,0 +1,85 @@
package org.keycloak.protocol.saml.mappers;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
import java.util.ArrayList;
import java.util.List;
/**
* Mappings UserModel property (the property name of a getter method) to an AttributeStatement.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HardcodedAttributeMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper {
public static final String PROVIDER_ID = "saml-hardcode-attribute-mapper";
public static final String ATTRIBUTE_VALUE = "attribute.value";
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
static {
ConfigProperty property;
property = new ConfigProperty();
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL);
property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
configProperties.add(property);
AttributeStatementHelper.setConfigProperties(configProperties);
property = new ConfigProperty();
property.setName(ATTRIBUTE_VALUE);
property.setLabel("Attribute value");
property.setType(ConfigProperty.STRING_TYPE);
property.setHelpText("Value of the attribute you want to hard code.");
configProperties.add(property);
}
public List<ConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getDisplayType() {
return "Hardcoded attribute";
}
@Override
public String getDisplayCategory() {
return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY;
}
@Override
public String getHelpText() {
return "Hardcode an attribute into the SAML Assertion.";
}
@Override
public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
String attributeValue = mappingModel.getConfig().get(ATTRIBUTE_VALUE);
AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue);
}
public static ProtocolMapperModel create(String name,
String samlAttributeName, String nameFormat, String friendlyName, String value,
boolean consentRequired, String consentText) {
String mapperId = PROVIDER_ID;
ProtocolMapperModel model = AttributeStatementHelper.createAttributeMapper(name, null, samlAttributeName, nameFormat, friendlyName,
consentRequired, consentText, mapperId);
model.getConfig().put(ATTRIBUTE_VALUE, value);
return model;
}
}

View file

@ -0,0 +1,76 @@
package org.keycloak.protocol.saml.mappers;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.saml.SamlProtocol;
import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Mappings UserModel property (the property name of a getter method) to an AttributeStatement.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HardcodedRole extends AbstractSAMLProtocolMapper {
public static final String PROVIDER_ID = "saml-hardcode-role-mapper";
public static final String ATTRIBUTE_VALUE = "attribute.value";
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
static {
ConfigProperty property;
property = new ConfigProperty();
property.setName("role");
property.setLabel("Role");
property.setHelpText("Role name you want to hardcode.");
property.setType(ConfigProperty.STRING_TYPE);
configProperties.add(property);
}
public List<ConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getDisplayType() {
return "Hardcoded role";
}
@Override
public String getDisplayCategory() {
return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY;
}
@Override
public String getHelpText() {
return "Hardcode role into SAML Assertion.";
}
public static ProtocolMapperModel create(String name,
String role) {
String mapperId = PROVIDER_ID;
ProtocolMapperModel mapper = new ProtocolMapperModel();
mapper.setName(name);
mapper.setProtocolMapper(mapperId);
mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
Map<String, String> config = new HashMap<String, String>();
config.put("role", role);
mapper.setConfig(config);
return mapper;
}
}

View file

@ -14,6 +14,7 @@ import org.picketlink.identity.federation.saml.v2.assertion.AttributeType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -21,7 +22,7 @@ import java.util.Map;
* @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 $
*/ */
public class SAMLBasicRoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRoleListMapper { public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRoleListMapper {
public static final String PROVIDER_ID = "saml-role-list-mapper"; public static final String PROVIDER_ID = "saml-role-list-mapper";
public static final String SINGLE_ROLE_ATTRIBUTE = "single"; public static final String SINGLE_ROLE_ATTRIBUTE = "single";
@ -92,17 +93,33 @@ public class SAMLBasicRoleListMapper extends AbstractSAMLProtocolMapper implemen
String single = mappingModel.getConfig().get(SINGLE_ROLE_ATTRIBUTE); String single = mappingModel.getConfig().get(SINGLE_ROLE_ATTRIBUTE);
boolean singleAttribute = Boolean.parseBoolean(single); boolean singleAttribute = Boolean.parseBoolean(single);
Map<ProtocolMapperModel, SAMLRoleNameMapper> roleNameMappers = new HashMap<>(); List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
AttributeType singleAttributeType = null;
for (ProtocolMapperModel mapping : clientSession.getClient().getProtocolMappers()) { for (ProtocolMapperModel mapping : clientSession.getClient().getProtocolMappers()) {
if (!mapping.getProtocol().equals(SamlProtocol.LOGIN_PROTOCOL)) continue; if (!mapping.getProtocol().equals(SamlProtocol.LOGIN_PROTOCOL)) continue;
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper()); ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
if (mapper == null || !(mapper instanceof SAMLRoleNameMapper)) continue; if (mapper == null) continue;
roleNameMappers.put(mapping, (SAMLRoleNameMapper)mapper); if (mapper instanceof SAMLRoleNameMapper) {
roleNameMappers.add(new SamlProtocol.ProtocolMapperProcessor<>((SAMLRoleNameMapper) mapper,mapping));
}
if (mapper instanceof HardcodedRole) {
AttributeType attributeType = null;
if (singleAttribute) {
if (singleAttributeType == null) {
singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType));
}
attributeType = singleAttributeType;
} else {
attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
}
attributeType.addAttributeValue(mapping.getConfig().get("role"));
}
} }
AttributeType singleAttributeType = null;
for (String roleId : clientSession.getRoles()) { for (String roleId : clientSession.getRoles()) {
// todo need a role mapping // todo need a role mapping
RoleModel roleModel = clientSession.getRealm().getRoleById(roleId); RoleModel roleModel = clientSession.getRealm().getRoleById(roleId);
@ -118,8 +135,8 @@ public class SAMLBasicRoleListMapper extends AbstractSAMLProtocolMapper implemen
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType)); roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
} }
String roleName = roleModel.getName(); String roleName = roleModel.getName();
for (Map.Entry<ProtocolMapperModel, SAMLRoleNameMapper> entry : roleNameMappers.entrySet()) { for (SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper> entry : roleNameMappers) {
String newName = entry.getValue().mapName(entry.getKey(), roleModel); String newName = entry.mapper.mapName(entry.model, roleModel);
if (newName != null) { if (newName != null) {
roleName = newName; roleName = newName;
break; break;

View file

@ -5,9 +5,12 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper; import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
import org.keycloak.protocol.saml.SamlProtocol;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Map an assigned role to a different position and name in the token * Map an assigned role to a different position and name in the token
@ -15,7 +18,7 @@ import java.util.List;
* @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 $
*/ */
public class SAMLBasicRoleNameMapper extends AbstractOIDCProtocolMapper implements SAMLRoleNameMapper { public class RoleNameMapper extends AbstractOIDCProtocolMapper implements SAMLRoleNameMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
@ -88,4 +91,21 @@ public class SAMLBasicRoleNameMapper extends AbstractOIDCProtocolMapper implemen
if (roleModel.getName().equals(role)) return newName; if (roleModel.getName().equals(role)) return newName;
return null; return null;
} }
public static ProtocolMapperModel create(String name,
String role,
String newName) {
String mapperId = PROVIDER_ID;
ProtocolMapperModel mapper = new ProtocolMapperModel();
mapper.setName(name);
mapper.setProtocolMapper(mapperId);
mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
Map<String, String> config = new HashMap<String, String>();
config.put(ROLE_CONFIG, role);
config.put(NEW_ROLE_NAME, newName);
mapper.setConfig(config);
return mapper;
}
} }

View file

@ -1,5 +1,7 @@
org.keycloak.protocol.saml.mappers.SAMLBasicRoleListMapper org.keycloak.protocol.saml.mappers.RoleListMapper
org.keycloak.protocol.saml.mappers.SAMLBasicRoleNameMapper org.keycloak.protocol.saml.mappers.RoleNameMapper
org.keycloak.protocol.saml.mappers.HardcodedRole
org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper
org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper
org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper

View file

@ -8,11 +8,11 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.protocol.AbstractLoginProtocolFactory; import org.keycloak.protocol.AbstractLoginProtocolFactory;
import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.oidc.mappers.OIDCAddressMapper; import org.keycloak.protocol.oidc.mappers.AddressMapper;
import org.keycloak.protocol.oidc.mappers.FullNameMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper; import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
import org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper; import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import java.util.ArrayList; import java.util.ArrayList;
@ -41,35 +41,35 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
static { static {
ProtocolMapperModel model; ProtocolMapperModel model;
model = OIDCUserModelMapper.createClaimMapper("username", model = UserPropertyMapper.createClaimMapper("username",
"username", "username",
"preferred_username", "String", "preferred_username", "String",
true, "username", true, "username",
true, true); true, true);
builtins.add(model); builtins.add(model);
defaultBuiltins.add(model); defaultBuiltins.add(model);
model = OIDCUserModelMapper.createClaimMapper("email", model = UserPropertyMapper.createClaimMapper("email",
"email", "email",
"email", "String", "email", "String",
true, "email", true, "email",
true, true); true, true);
builtins.add(model); builtins.add(model);
defaultBuiltins.add(model); defaultBuiltins.add(model);
model = OIDCUserModelMapper.createClaimMapper("given name", model = UserPropertyMapper.createClaimMapper("given name",
"firstName", "firstName",
"given_name", "String", "given_name", "String",
true, "given name", true, "given name",
true, true); true, true);
builtins.add(model); builtins.add(model);
defaultBuiltins.add(model); defaultBuiltins.add(model);
model = OIDCUserModelMapper.createClaimMapper("family name", model = UserPropertyMapper.createClaimMapper("family name",
"lastName", "lastName",
"family_name", "String", "family_name", "String",
true, "family name", true, "family name",
true, true); true, true);
builtins.add(model); builtins.add(model);
defaultBuiltins.add(model); defaultBuiltins.add(model);
model = OIDCUserModelMapper.createClaimMapper("email verified", model = UserPropertyMapper.createClaimMapper("email verified",
"emailVerified", "emailVerified",
"email_verified", "boolean", "email_verified", "boolean",
false, null, false, null,
@ -78,7 +78,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
ProtocolMapperModel fullName = new ProtocolMapperModel(); ProtocolMapperModel fullName = new ProtocolMapperModel();
fullName.setName("full name"); fullName.setName("full name");
fullName.setProtocolMapper(OIDCFullNameMapper.PROVIDER_ID); fullName.setProtocolMapper(FullNameMapper.PROVIDER_ID);
fullName.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); fullName.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
fullName.setConsentRequired(true); fullName.setConsentRequired(true);
fullName.setConsentText("full name"); fullName.setConsentText("full name");
@ -89,10 +89,10 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
builtins.add(fullName); builtins.add(fullName);
defaultBuiltins.add(fullName); defaultBuiltins.add(fullName);
ProtocolMapperModel address = OIDCAddressMapper.createAddressMapper(); ProtocolMapperModel address = AddressMapper.createAddressMapper();
builtins.add(address); builtins.add(address);
model = OIDCUserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, model = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
KerberosConstants.GSS_DELEGATION_CREDENTIAL, KerberosConstants.GSS_DELEGATION_CREDENTIAL,
KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String", KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,

View file

@ -30,6 +30,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OAuthClientModel; import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider; import org.keycloak.models.UserSessionProvider;
@ -372,85 +373,22 @@ public class OIDCLoginProtocolService {
Map<String, String> err = new HashMap<String, String>(); Map<String, String> err = new HashMap<String, String>();
err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT); err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token invalid"); err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token invalid");
logger.error("Invalid token. Token verification failed.");
event.error(Errors.INVALID_TOKEN); event.error(Errors.INVALID_TOKEN);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err) return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
.build(); .build();
} }
event.user(token.getSubject()).session(token.getSessionState()).detail(Details.VALIDATE_ACCESS_TOKEN, token.getId()); event.user(token.getSubject()).session(token.getSessionState()).detail(Details.VALIDATE_ACCESS_TOKEN, token.getId());
if (token.isExpired()
|| token.getIssuedAt() < realm.getNotBefore()
) {
Map<String, String> err = new HashMap<String, String>();
err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token expired");
event.error(Errors.INVALID_TOKEN);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
.build();
}
UserModel user = session.users().getUserById(token.getSubject(), realm);
if (user == null) {
Map<String, String> err = new HashMap<String, String>();
err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
err.put(OAuth2Constants.ERROR_DESCRIPTION, "User does not exist");
event.error(Errors.USER_NOT_FOUND);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
.build();
}
if (!user.isEnabled()) {
Map<String, String> err = new HashMap<String, String>();
err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
err.put(OAuth2Constants.ERROR_DESCRIPTION, "User disabled");
event.error(Errors.USER_DISABLED);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
.build();
}
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
Map<String, String> err = new HashMap<String, String>();
err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
err.put(OAuth2Constants.ERROR_DESCRIPTION, "Expired session");
event.error(Errors.USER_SESSION_NOT_FOUND);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
.build();
}
ClientModel client = realm.findClient(token.getIssuedFor());
if (client == null) {
Map<String, String> err = new HashMap<String, String>();
err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_CLIENT);
err.put(OAuth2Constants.ERROR_DESCRIPTION, "Issued for client no longer exists");
event.error(Errors.CLIENT_NOT_FOUND);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
.build();
}
if (token.getIssuedAt() < client.getNotBefore()) {
Map<String, String> err = new HashMap<String, String>();
err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_CLIENT);
err.put(OAuth2Constants.ERROR_DESCRIPTION, "Issued for client no longer exists");
event.error(Errors.INVALID_TOKEN);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
.build();
}
try { try {
tokenManager.verifyAccess(token, realm, client, user); tokenManager.validateToken(session, uriInfo, clientConnection, realm, token);
} catch (OAuthErrorException e) { } catch (OAuthErrorException e) {
Map<String, String> err = new HashMap<String, String>(); Map<String, String> error = new HashMap<String, String>();
err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_SCOPE); error.put(OAuth2Constants.ERROR, e.getError());
err.put(OAuth2Constants.ERROR_DESCRIPTION, "Role mappings have changed"); if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
event.error(Errors.INVALID_TOKEN); event.error(Errors.INVALID_TOKEN);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err) return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
.build();
} }
event.success(); event.success();
return Response.ok(token, MediaType.APPLICATION_JSON_TYPE).build(); return Response.ok(token, MediaType.APPLICATION_JSON_TYPE).build();
@ -666,16 +604,6 @@ public class OIDCLoginProtocolService {
AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession); AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
try {
tokenManager.verifyAccess(token, realm, client, user);
} catch (OAuthErrorException e) {
Map<String, String> error = new HashMap<String, String>();
error.put(OAuth2Constants.ERROR, e.getError());
if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
event.error(Errors.INVALID_CODE);
return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
}
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession) AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
.accessToken(token) .accessToken(token)
.generateIDToken() .generateIDToken()

View file

@ -59,12 +59,22 @@ public class TokenManager {
} }
} }
public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel client, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException { public static class TokenValidation {
RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken); public final UserModel user;
public final UserSessionModel userSession;
public final ClientSessionModel clientSession;
public final AccessToken newToken;
event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId()); public TokenValidation(UserModel user, UserSessionModel userSession, ClientSessionModel clientSession, AccessToken newToken) {
this.user = user;
this.userSession = userSession;
this.clientSession = clientSession;
this.newToken = newToken;
}
}
UserModel user = session.users().getUserById(refreshToken.getSubject(), realm); public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, AccessToken oldToken) throws OAuthErrorException {
UserModel user = session.users().getUserById(oldToken.getSubject(), realm);
if (user == null) { if (user == null) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user");
} }
@ -73,24 +83,14 @@ public class TokenManager {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "User disabled", "User disabled"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "User disabled", "User disabled");
} }
UserSessionModel userSession = session.sessions().getUserSession(realm, refreshToken.getSessionState()); UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState());
int currentTime = Time.currentTime();
if (!AuthenticationManager.isSessionValid(realm, userSession)) { if (!AuthenticationManager.isSessionValid(realm, userSession)) {
AuthenticationManager.logout(session, realm, userSession, uriInfo, connection); AuthenticationManager.logout(session, realm, userSession, uriInfo, connection);
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
} }
if (!client.getClientId().equals(refreshToken.getIssuedFor())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Unmatching clients", "Unmatching clients");
}
if (refreshToken.getIssuedAt() < client.getNotBefore()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
}
ClientSessionModel clientSession = null; ClientSessionModel clientSession = null;
for (ClientSessionModel clientSessionModel : userSession.getClientSessions()) { for (ClientSessionModel clientSessionModel : userSession.getClientSessions()) {
if (clientSessionModel.getId().equals(refreshToken.getClientSession())) { if (clientSessionModel.getId().equals(oldToken.getClientSession())) {
clientSession = clientSessionModel; clientSession = clientSessionModel;
break; break;
} }
@ -98,20 +98,48 @@ public class TokenManager {
if (clientSession == null) { if (clientSession == null) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Client session not active", "Client session not active"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Client session not active", "Client session not active");
}
ClientModel client = clientSession.getClient();
if (!client.getClientId().equals(oldToken.getIssuedFor())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Unmatching clients", "Unmatching clients");
}
if (oldToken.getIssuedAt() < client.getNotBefore()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
}
if (oldToken.getIssuedAt() < realm.getNotBefore()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
}
// recreate token.
Set<RoleModel> requestedRoles = TokenManager.getAccess(null, clientSession.getClient(), user);
AccessToken newToken = createClientAccessToken(session, requestedRoles, realm, client, user, userSession, clientSession);
verifyAccess(oldToken, newToken);
return new TokenValidation(user, userSession, clientSession, newToken);
} }
verifyAccess(refreshToken, realm, client, user); public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException {
RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
AccessToken accessToken = initToken(realm, client, user, userSession, clientSession); event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
accessToken.setRealmAccess(refreshToken.getRealmAccess());
accessToken.setResourceAccess(refreshToken.getResourceAccess());
accessToken = transformAccessToken(session, accessToken, realm, client, user, userSession, clientSession);
userSession.setLastSessionRefresh(currentTime); TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken);
// validate authorizedClient is same as validated client
if (!validation.clientSession.getClient().getId().equals(authorizedClient.getId())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token. Token client and authorized client don't match");
}
AccessTokenResponse res = responseBuilder(realm, client, event, session, userSession, clientSession) int currentTime = Time.currentTime();
.accessToken(accessToken) validation.userSession.setLastSessionRefresh(currentTime);
AccessTokenResponse res = responseBuilder(realm, authorizedClient, event, session, validation.userSession, validation.clientSession)
.accessToken(validation.newToken)
.generateIDToken() .generateIDToken()
.generateRefreshToken().build(); .generateRefreshToken().build();
return res; return res;
@ -198,41 +226,27 @@ public class TokenManager {
return requestedRoles; return requestedRoles;
} }
public void verifyAccess(AccessToken token, RealmModel realm, ClientModel client, UserModel user) throws OAuthErrorException { public void verifyAccess(AccessToken token, AccessToken newToken) throws OAuthErrorException {
ApplicationModel clientApp = (client instanceof ApplicationModel) ? (ApplicationModel)client : null;
if (token.getRealmAccess() != null) { if (token.getRealmAccess() != null) {
if (newToken.getRealmAccess() == null) throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm roles");
for (String roleName : token.getRealmAccess().getRoles()) { for (String roleName : token.getRealmAccess().getRoles()) {
RoleModel role = realm.getRole(roleName); if (!newToken.getRealmAccess().getRoles().contains(roleName)) {
if (role == null) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid realm role " + roleName);
}
if (!user.hasRole(role)) {
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm role: " + roleName); throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm role: " + roleName);
} }
if (!client.hasScope(role)) {
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Client no longer has realm scope: " + roleName);
}
} }
} }
if (token.getResourceAccess() != null) { if (token.getResourceAccess() != null) {
for (Map.Entry<String, AccessToken.Access> entry : token.getResourceAccess().entrySet()) { for (Map.Entry<String, AccessToken.Access> entry : token.getResourceAccess().entrySet()) {
ApplicationModel app = realm.getApplicationByName(entry.getKey()); AccessToken.Access appAccess = newToken.getResourceAccess(entry.getKey());
if (app == null) { if (appAccess == null && !entry.getValue().getRoles().isEmpty()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + entry.getKey()); throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User or application no longer has role permissions for application key: " + entry.getKey());
} }
for (String roleName : entry.getValue().getRoles()) { for (String roleName : entry.getValue().getRoles()) {
RoleModel role = app.getRole(roleName); if (!appAccess.getRoles().contains(roleName)) {
if (role == null) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown application role: " + roleName);
}
if (!user.hasRole(role)) {
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for application role " + roleName); throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for application role " + roleName);
} }
if (clientApp != null && !clientApp.equals(app) && !client.hasScope(role)) {
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Client no longer has application scope" + roleName);
}
} }
} }
} }

View file

@ -21,7 +21,7 @@ import java.util.Map;
* @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 $
*/ */
public class OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();

View file

@ -5,11 +5,15 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
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.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Set the 'name' claim to be first + last name. * Set the 'name' claim to be first + last name.
@ -17,7 +21,7 @@ import java.util.List;
* @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 $
*/ */
public class OIDCFullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
@ -88,4 +92,21 @@ public class OIDCFullNameMapper extends AbstractOIDCProtocolMapper implements OI
setClaim(token, userSession); setClaim(token, userSession);
return token; return token;
} }
public static ProtocolMapperModel create(String name,
boolean consentRequired, String consentText,
boolean accessToken, boolean idToken) {
ProtocolMapperModel mapper = new ProtocolMapperModel();
mapper.setName(name);
mapper.setProtocolMapper(PROVIDER_ID);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
mapper.setConsentRequired(consentRequired);
mapper.setConsentText(consentText);
Map<String, String> config = new HashMap<String, String>();
if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
mapper.setConfig(config);
return mapper;
}
} }

View file

@ -6,11 +6,14 @@ import org.keycloak.models.ProtocolMapperModel;
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;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* *
@ -18,7 +21,7 @@ import java.util.List;
* @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 $
*/ */
public class OIDCAddClaimMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
@ -62,7 +65,7 @@ public class OIDCAddClaimMapper extends AbstractOIDCProtocolMapper implements OI
} }
public static final String PROVIDER_ID = "oidc-add-claim-mapper"; public static final String PROVIDER_ID = "oidc-hardcoded-claim-mapper";
public List<ConfigProperty> getConfigProperties() { public List<ConfigProperty> getConfigProperties() {
@ -76,7 +79,7 @@ public class OIDCAddClaimMapper extends AbstractOIDCProtocolMapper implements OI
@Override @Override
public String getDisplayType() { public String getDisplayType() {
return "Hard coded claim"; return "Hardcoded claim";
} }
@Override @Override
@ -111,16 +114,25 @@ public class OIDCAddClaimMapper extends AbstractOIDCProtocolMapper implements OI
return token; return token;
} }
public static ProtocolMapperModel createClaimMapper(String name, public static ProtocolMapperModel create(String name,
String userAttribute, String hardcodedName,
String tokenClaimName, String claimType, String hardcodedValue, String claimType,
boolean consentRequired, String consentText, boolean consentRequired, String consentText,
boolean accessToken, boolean idToken) { boolean accessToken, boolean idToken) {
return OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute, ProtocolMapperModel mapper = new ProtocolMapperModel();
tokenClaimName, claimType, mapper.setName(name);
consentRequired, consentText, mapper.setProtocolMapper(PROVIDER_ID);
accessToken, idToken, mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
PROVIDER_ID); mapper.setConsentRequired(consentRequired);
mapper.setConsentText(consentText);
Map<String, String> config = new HashMap<String, String>();
config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, hardcodedName);
config.put(CLAIM_VALUE, hardcodedValue);
config.put(OIDCAttributeMapperHelper.JSON_TYPE, claimType);
if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
mapper.setConfig(config);
return mapper;
} }

View file

@ -4,10 +4,13 @@ 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.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Add a role to a token * Add a role to a token
@ -15,7 +18,7 @@ import java.util.List;
* @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 $
*/ */
public class OIDCAddRoleMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
@ -31,7 +34,7 @@ public class OIDCAddRoleMapper extends AbstractOIDCProtocolMapper implements OID
configProperties.add(property); configProperties.add(property);
} }
public static final String PROVIDER_ID = "oidc-role-mapper"; public static final String PROVIDER_ID = "oidc-hardcoded-role-mapper";
public List<ConfigProperty> getConfigProperties() { public List<ConfigProperty> getConfigProperties() {
@ -78,4 +81,19 @@ public class OIDCAddRoleMapper extends AbstractOIDCProtocolMapper implements OID
} }
return token; return token;
} }
public static ProtocolMapperModel create(String name,
String role) {
String mapperId = PROVIDER_ID;
ProtocolMapperModel mapper = new ProtocolMapperModel();
mapper.setName(name);
mapper.setProtocolMapper(mapperId);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
Map<String, String> config = new HashMap<String, String>();
config.put(ROLE_CONFIG, role);
mapper.setConfig(config);
return mapper;
}
} }

View file

@ -6,11 +6,14 @@ import org.keycloak.models.ProtocolMapperModel;
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;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Map an assigned role to a different position and name in the token * Map an assigned role to a different position and name in the token
@ -18,7 +21,7 @@ import java.util.List;
* @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 $
*/ */
public class OIDCRoleMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
@ -41,7 +44,7 @@ public class OIDCRoleMapper extends AbstractOIDCProtocolMapper implements OIDCAc
configProperties.add(property); configProperties.add(property);
} }
public static final String PROVIDER_ID = "oidc-role-mapper"; public static final String PROVIDER_ID = "oidc-role-name-mapper";
public List<ConfigProperty> getConfigProperties() { public List<ConfigProperty> getConfigProperties() {
@ -98,4 +101,21 @@ public class OIDCRoleMapper extends AbstractOIDCProtocolMapper implements OIDCAc
} }
return token; return token;
} }
public static ProtocolMapperModel create(String name,
String role,
String newName) {
String mapperId = PROVIDER_ID;
ProtocolMapperModel mapper = new ProtocolMapperModel();
mapper.setName(name);
mapper.setProtocolMapper(mapperId);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
Map<String, String> config = new HashMap<String, String>();
config.put(ROLE_CONFIG, role);
config.put(NEW_ROLE_NAME, newName);
mapper.setConfig(config);
return mapper;
}
} }

View file

@ -21,7 +21,7 @@ import java.util.List;
* @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 $
*/ */
public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();

View file

@ -21,7 +21,7 @@ import java.util.List;
* @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 $
*/ */
public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
static { static {

View file

@ -19,7 +19,7 @@ import org.keycloak.representations.IDToken;
* *
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class OIDCUserSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();

View file

@ -1,10 +1,10 @@
org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper org.keycloak.protocol.oidc.mappers.UserAttributeMapper
org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper org.keycloak.protocol.oidc.mappers.FullNameMapper
org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper org.keycloak.protocol.oidc.mappers.UserPropertyMapper
org.keycloak.protocol.oidc.mappers.OIDCAddressMapper org.keycloak.protocol.oidc.mappers.AddressMapper
org.keycloak.protocol.oidc.mappers.OIDCAddClaimMapper org.keycloak.protocol.oidc.mappers.HardcodedClaim
org.keycloak.protocol.oidc.mappers.OIDCAddRoleMapper org.keycloak.protocol.oidc.mappers.HardcodedRole
org.keycloak.protocol.oidc.mappers.OIDCRoleMapper org.keycloak.protocol.oidc.mappers.RoleNameMapper
org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper

View file

@ -276,8 +276,8 @@ public class AccountTest {
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent(); events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent();
Assert.assertEquals("", profilePage.getFirstName()); Assert.assertEquals("Tom", profilePage.getFirstName());
Assert.assertEquals("", profilePage.getLastName()); Assert.assertEquals("Brady", profilePage.getLastName());
Assert.assertEquals("test-user@localhost", profilePage.getEmail()); Assert.assertEquals("test-user@localhost", profilePage.getEmail());
// All fields are required, so there should be an error when something is missing. // All fields are required, so there should be an error when something is missing.
@ -310,8 +310,8 @@ public class AccountTest {
profilePage.clickCancel(); profilePage.clickCancel();
Assert.assertEquals("", profilePage.getFirstName()); Assert.assertEquals("Tom", profilePage.getFirstName());
Assert.assertEquals("", profilePage.getLastName()); Assert.assertEquals("Brady", profilePage.getLastName());
Assert.assertEquals("test-user@localhost", profilePage.getEmail()); Assert.assertEquals("test-user@localhost", profilePage.getEmail());
events.assertEmpty(); events.assertEmpty();

View file

@ -17,7 +17,6 @@ import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.HttpClientBuilder; import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.federation.kerberos.CommonKerberosConfig; import org.keycloak.federation.kerberos.CommonKerberosConfig;
@ -31,15 +30,11 @@ import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.adapter.AdapterTest;
import org.keycloak.testsuite.adapter.AdapterTestStrategy;
import org.keycloak.testsuite.pages.AccountPasswordPage; import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebResource;
@ -182,7 +177,7 @@ public abstract class AbstractKerberosTest {
@Override @Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ProtocolMapperModel protocolMapper = OIDCUserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
KerberosConstants.GSS_DELEGATION_CREDENTIAL, KerberosConstants.GSS_DELEGATION_CREDENTIAL,
KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String", KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,

View file

@ -40,8 +40,12 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.mappers.OIDCAddressMapper; import org.keycloak.protocol.oidc.mappers.AddressMapper;
import org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper; import org.keycloak.protocol.oidc.mappers.FullNameMapper;
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
import org.keycloak.protocol.oidc.mappers.HardcodedRole;
import org.keycloak.protocol.oidc.mappers.RoleNameMapper;
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
@ -67,6 +71,7 @@ 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.HashMap; import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -594,9 +599,14 @@ public class AccessTokenTest {
user.setAttribute("country", "USA"); user.setAttribute("country", "USA");
user.setAttribute("phone", "617-777-6666"); user.setAttribute("phone", "617-777-6666");
ApplicationModel app = realm.getApplicationByName("test-app"); ApplicationModel app = realm.getApplicationByName("test-app");
ProtocolMapperModel mapper = OIDCAddressMapper.createAddressMapper(true, true); ProtocolMapperModel mapper = AddressMapper.createAddressMapper(true, true);
app.addProtocolMapper(mapper); app.addProtocolMapper(mapper);
app.addProtocolMapper(OIDCUserAttributeMapper.createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", 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(UserAttributeMapper.createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true));
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true));
app.addProtocolMapper(HardcodedRole.create("hard-realm", "hardcoded"));
app.addProtocolMapper(HardcodedRole.create("hard-app", "app.hardcoded"));
session.getTransaction().commit(); session.getTransaction().commit();
session.close(); session.close();
} }
@ -607,15 +617,22 @@ public class AccessTokenTest {
org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class); org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class);
IDToken idToken = getIdToken(tokenResponse); IDToken idToken = getIdToken(tokenResponse);
Assert.assertNotNull(idToken.getAddress()); Assert.assertNotNull(idToken.getAddress());
Assert.assertEquals(idToken.getName(), "Tom Brady");
Assert.assertEquals(idToken.getAddress().getStreetAddress(), "5 Yawkey Way"); Assert.assertEquals(idToken.getAddress().getStreetAddress(), "5 Yawkey Way");
Assert.assertEquals(idToken.getAddress().getLocality(), "Boston"); Assert.assertEquals(idToken.getAddress().getLocality(), "Boston");
Assert.assertEquals(idToken.getAddress().getRegion(), "MA"); Assert.assertEquals(idToken.getAddress().getRegion(), "MA");
Assert.assertEquals(idToken.getAddress().getPostalCode(), "02115"); Assert.assertEquals(idToken.getAddress().getPostalCode(), "02115");
Assert.assertEquals(idToken.getAddress().getCountry(), "USA"); Assert.assertEquals(idToken.getAddress().getCountry(), "USA");
Assert.assertNotNull(idToken.getOtherClaims().get("home_phone")); Assert.assertNotNull(idToken.getOtherClaims().get("home_phone"));
//Assert.assertEquals("617-777-6666", idToken.getOtherClaims().get("home_phone")); Assert.assertEquals("617-777-6666", idToken.getOtherClaims().get("home_phone"));
Assert.assertEquals("coded", idToken.getOtherClaims().get("hard"));
Map nested = (Map)idToken.getOtherClaims().get("nested");
Assert.assertEquals("coded-nested", nested.get("hard"));
nested = (Map)idToken.getOtherClaims().get("home");
Assert.assertEquals("617-777-6666", nested.get("phone"));
AccessToken accessToken = getAccessToken(tokenResponse); AccessToken accessToken = getAccessToken(tokenResponse);
Assert.assertEquals(accessToken.getName(), "Tom Brady");
Assert.assertNotNull(accessToken.getAddress()); Assert.assertNotNull(accessToken.getAddress());
Assert.assertEquals(accessToken.getAddress().getStreetAddress(), "5 Yawkey Way"); Assert.assertEquals(accessToken.getAddress().getStreetAddress(), "5 Yawkey Way");
Assert.assertEquals(accessToken.getAddress().getLocality(), "Boston"); Assert.assertEquals(accessToken.getAddress().getLocality(), "Boston");
@ -624,11 +641,41 @@ public class AccessTokenTest {
Assert.assertEquals(accessToken.getAddress().getCountry(), "USA"); Assert.assertEquals(accessToken.getAddress().getCountry(), "USA");
Assert.assertNotNull(accessToken.getOtherClaims().get("home_phone")); Assert.assertNotNull(accessToken.getOtherClaims().get("home_phone"));
Assert.assertEquals("617-777-6666", accessToken.getOtherClaims().get("home_phone")); Assert.assertEquals("617-777-6666", accessToken.getOtherClaims().get("home_phone"));
Assert.assertEquals("coded", accessToken.getOtherClaims().get("hard"));
nested = (Map)accessToken.getOtherClaims().get("nested");
Assert.assertEquals("coded-nested", nested.get("hard"));
nested = (Map)accessToken.getOtherClaims().get("home");
Assert.assertEquals("617-777-6666", nested.get("phone"));
Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains("hardcoded"));
Assert.assertTrue(accessToken.getResourceAccess("app").getRoles().contains("hardcoded"));
response.close(); response.close();
} }
client.close(); client.close();
// undo mappers
{
KeycloakSession session = keycloakRule.startSession();
RealmModel realm = session.realms().getRealmByName("test");
ApplicationModel app = realm.getApplicationByName("test-app");
for (ProtocolMapperModel model : app.getProtocolMappers()) {
if (model.getName().equals("address")
|| model.getName().equals("hard")
|| model.getName().equals("hard-nested")
|| model.getName().equals("custom phone")
|| model.getName().equals("nested phone")
|| model.getName().equals("hard-realm")
|| model.getName().equals("hard-app")
) {
app.removeProtocolMapper(model);
}
}
session.getTransaction().commit();
session.close();
}
events.clear(); events.clear();
} }
@ -645,7 +692,7 @@ public class AccessTokenTest {
} }
private AccessToken getAccessToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws VerificationException { private AccessToken getAccessToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws VerificationException {
JWSInput input = new JWSInput(tokenResponse.getIdToken()); JWSInput input = new JWSInput(tokenResponse.getToken());
AccessToken idToken = null; AccessToken idToken = null;
try { try {
idToken = input.readJsonContent(AccessToken.class); idToken = input.readJsonContent(AccessToken.class);

View file

@ -3,21 +3,27 @@ package org.keycloak.testsuite.saml;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
import org.junit.Assert; import org.junit.Assert;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.models.ApplicationModel; import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; 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.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
import org.keycloak.protocol.saml.mappers.HardcodedRole;
import org.keycloak.protocol.saml.mappers.RoleListMapper;
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.admin.AdminRoot; import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule; import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
@ -101,10 +107,12 @@ public class SamlBindingTest {
private void handler(HttpServletRequest req, HttpServletResponse resp) { private void handler(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("********* HERE ******"); System.out.println("********* HERE ******");
if (req.getParameterMap().isEmpty()) { if (req.getParameterMap().isEmpty()) {
System.out.println("redirecting");
resp.setStatus(302); resp.setStatus(302);
resp.setHeader("Location", "http://localhost:8081/auth/realms/demo/protocol/saml?SAMLRequest=jVJbT8IwFP4rS99HuwluNIwEIUYSLwugD76Y2h2kSdfOng7l31uGRn0ATfrQ9HznfJfTEYpaN3zS%2Bo1ZwGsL6KP3WhvkXaEgrTPcClTIjagBuZd8Obm55mmP8cZZb6XV5NByGiwQwXllDYkmX9epNdjW4JbgtkrC%2FeK6IBvvG06ptlLojUXPc5YnFOpG2x0AJdEsaFRG7PuPoUWwQx0IXSOtoLb0SynduyLRpXUSOs8FWQuNQKL5rCDz2VO%2FymEgIY2zlJ3H%2FSx9jkU%2BzOK0ys8yNmSSsUEAYxnsqC18tyO2MDfohfEFSVkyiNlZzM5XacrDSbJePug%2Fkqj8FHKhTKXMy%2BnIng8g5FerVRmXd8sViR7AYec8AMh4tPfDO3L3Y2%2F%2F3cT4j7BH9Mf8A1nDb8PA%2Bay0WsldNNHavk1D1D5k4V0LXbi18MclJL2ke1FVvO6gvDXYgFRrBRWh4wPp7z85%2FgA%3D"); resp.setHeader("Location", "http://localhost:8081/auth/realms/demo/protocol/saml?SAMLRequest=jVJbT8IwFP4rS99HuwluNIwEIUYSLwugD76Y2h2kSdfOng7l31uGRn0ATfrQ9HznfJfTEYpaN3zS%2Bo1ZwGsL6KP3WhvkXaEgrTPcClTIjagBuZd8Obm55mmP8cZZb6XV5NByGiwQwXllDYkmX9epNdjW4JbgtkrC%2FeK6IBvvG06ptlLojUXPc5YnFOpG2x0AJdEsaFRG7PuPoUWwQx0IXSOtoLb0SynduyLRpXUSOs8FWQuNQKL5rCDz2VO%2FymEgIY2zlJ3H%2FSx9jkU%2BzOK0ys8yNmSSsUEAYxnsqC18tyO2MDfohfEFSVkyiNlZzM5XacrDSbJePug%2Fkqj8FHKhTKXMy%2BnIng8g5FerVRmXd8sViR7AYec8AMh4tPfDO3L3Y2%2F%2F3cT4j7BH9Mf8A1nDb8PA%2Bay0WsldNNHavk1D1D5k4V0LXbi18MclJL2ke1FVvO6gvDXYgFRrBRWh4wPp7z85%2FgA%3D");
return; return;
} }
System.out.println("received response");
samlResponse = req.getParameter("SAMLResponse"); samlResponse = req.getParameter("SAMLResponse");
} }
} }
@ -199,6 +207,8 @@ public class SamlBindingTest {
// this test has a hardcoded SAMLRequest and we hack a SP face servlet to get the SAMLResponse so we can look // this test has a hardcoded SAMLRequest and we hack a SP face servlet to get the SAMLResponse so we can look
// at the assertions sent. This is because Picketlink, AFAICT, does not give you any way to get access to // at the assertions sent. This is because Picketlink, AFAICT, does not give you any way to get access to
// the assertion. // the assertion.
{
SamlSPFacade.samlResponse = null; SamlSPFacade.samlResponse = null;
driver.navigate().to("http://localhost:8081/employee/"); driver.navigate().to("http://localhost:8081/employee/");
Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml")); Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
@ -212,10 +222,12 @@ public class SamlBindingTest {
Assert.assertTrue(rt.getAssertions().size() == 1); Assert.assertTrue(rt.getAssertions().size() == 1);
AssertionType assertion = rt.getAssertions().get(0).getAssertion(); AssertionType assertion = rt.getAssertions().get(0).getAssertion();
// test attributes // test attributes and roles
boolean email = false; boolean email = false;
boolean phone = false; boolean phone = false;
boolean userRole = false;
boolean managerRole = false;
for (AttributeStatementType statement : assertion.getAttributeStatements()) { for (AttributeStatementType statement : assertion.getAttributeStatements()) {
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) { for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
AttributeType attr = choice.getAttribute(); AttributeType attr = choice.getAttribute();
@ -228,6 +240,9 @@ public class SamlBindingTest {
Assert.assertEquals(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get(), attr.getNameFormat()); Assert.assertEquals(JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get(), attr.getNameFormat());
Assert.assertEquals(attr.getAttributeValue().get(0), "617"); Assert.assertEquals(attr.getAttributeValue().get(0), "617");
phone = true; phone = true;
} else if (attr.getName().equals("Role")) {
if (attr.getAttributeValue().get(0).equals("manager")) managerRole = true;
if (attr.getAttributeValue().get(0).equals("user")) userRole = true;
} }
} }
@ -235,7 +250,79 @@ public class SamlBindingTest {
Assert.assertTrue(email); Assert.assertTrue(email);
Assert.assertTrue(phone); Assert.assertTrue(phone);
Assert.assertTrue(userRole);
Assert.assertTrue(managerRole);
}
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ApplicationModel app = appRealm.getApplicationByName("http://localhost:8081/employee/");
for (ProtocolMapperModel mapper : app.getProtocolMappers()) {
if (mapper.getName().equals("role-list")) {
app.removeProtocolMapper(mapper);
mapper.setId(null);
mapper.getConfig().put(RoleListMapper.SINGLE_ROLE_ATTRIBUTE, "true");
mapper.getConfig().put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, "memberOf");
app.addProtocolMapper(mapper);
}
}
app.addProtocolMapper(HardcodedAttributeMapper.create("hardcoded-attribute", "hardcoded-attribute", "Basic", null, "hard", false, null));
app.addProtocolMapper(HardcodedRole.create("hardcoded-role", "hardcoded-role"));
app.addProtocolMapper(RoleNameMapper.create("renamed-role", "manager", "el-jefe"));
app.addProtocolMapper(RoleNameMapper.create("renamed-employee-role", "http://localhost:8081/employee/.employee", "pee-on"));
}
}, "demo");
System.out.println(">>>>>>>>>> single role attribute <<<<<<<<");
{
SamlSPFacade.samlResponse = null;
driver.navigate().to("http://localhost:8081/employee/");
System.out.println(driver.getCurrentUrl());
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee/");
Assert.assertNotNull(SamlSPFacade.samlResponse);
SAML2Response saml2Response = new SAML2Response();
byte[] samlResponse = PostBindingUtil.base64Decode(SamlSPFacade.samlResponse);
ResponseType rt = saml2Response.getResponseType(new ByteArrayInputStream(samlResponse));
Assert.assertTrue(rt.getAssertions().size() == 1);
AssertionType assertion = rt.getAssertions().get(0).getAssertion();
// test attributes and roles
boolean userRole = false;
boolean managerRole = false;
boolean single = false;
boolean hardcodedRole = false;
boolean hardcodedAttribute = false;
boolean peeOn = false;
for (AttributeStatementType statement : assertion.getAttributeStatements()) {
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
AttributeType attr = choice.getAttribute();
if (attr.getName().equals("memberOf")) {
if (single) Assert.fail("too many role attributes");
single = true;
for (Object value : attr.getAttributeValue()) {
if (value.equals("el-jefe")) managerRole = true;
if (value.equals("user")) userRole = true;
if (value.equals("hardcoded-role")) hardcodedRole = true;
if (value.equals("pee-on")) peeOn = true;
}
} else if (attr.getName().equals("hardcoded-attribute")) {
hardcodedAttribute = true;
Assert.assertEquals(attr.getAttributeValue().get(0), "hard");
}
}
}
Assert.assertTrue(single);
Assert.assertTrue(hardcodedAttribute);
Assert.assertTrue(hardcodedRole);
Assert.assertTrue(peeOn);
Assert.assertTrue(userRole);
Assert.assertTrue(managerRole);
}
} }
@Test @Test

View file

@ -27,7 +27,10 @@
"attributes" : { "attributes" : {
"phone": "617" "phone": "617"
}, },
"realmRoles": ["manager", "user"] "realmRoles": ["manager", "user"],
"applicationRoles": {
"http://localhost:8081/employee/": [ "employee" ]
}
} }
], ],
"applications": [ "applications": [
@ -304,6 +307,14 @@
"name": "user", "name": "user",
"description": "Have User privileges" "description": "Have User privileges"
} }
],
"application" : {
"http://localhost:8081/employee/" : [
{
"name": "employee",
"description": "Have Employee privileges"
}
] ]
} }
}
} }

View file

@ -20,6 +20,8 @@
"username" : "test-user@localhost", "username" : "test-user@localhost",
"enabled": true, "enabled": true,
"email" : "test-user@localhost", "email" : "test-user@localhost",
"firstName": "Tom",
"lastName": "Brady",
"credentials" : [ "credentials" : [
{ "type" : "password", { "type" : "password",
"value" : "password" } "value" : "password" }