KEYCLOAK-3499 Revise OIDCProtocolMapper support

Moved methods `transformUserInfoToken`, `transformAccessToken`,
`transformIDToken` to the `AbstractOIDCProtocolMapper` base class
in order to reduce code duplication.
Previously every mapper implemented at least one or two of those
methods with exactly the same code.
Having those methods in the base class ensures that the code is the
same for all mappers. Since the mentioned methods are declared
on the `OIDCIDTokenMapper`, `OIDCAccessTokenMapper` and `UserInfoTokenMapper`
interfaces `AbstractOIDCProtocolMapper` implementations can now choose
how they should be handled by the `TokenManager`
by implementing the desired set of interfaces `*TokenMapper`-interfaces.

I think this provides a good balance between ease of use, reduced code duplication
and ensured backwards compatiblity.
Existing protocol mapper implementations will still work since they just implement
their own logic for `transformUserInfoToken`, `transformAccessToken`,
`transformIDToken`.

The "claim" information provided by a `ProtocolMapper` to a `*Token` can now
be provided by overriding the `AbstractOIDCProtocolMapper.setClaim` method.

Adapted all eligible ProtocolMapper implementations within the
`org.keycloak.protocol.oidc.mappers` package accordingly.
This commit is contained in:
Thomas Darimont 2016-08-31 23:23:40 +02:00 committed by mposolda
parent 386bf8d39e
commit c3b577de11
14 changed files with 89 additions and 175 deletions

View file

@ -506,10 +506,11 @@ public class TokenManager {
for (ProtocolMapperModel mapping : mappings) {
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
if (mapper == null || !(mapper instanceof OIDCAccessTokenMapper)) continue;
token = ((OIDCAccessTokenMapper)mapper).transformAccessToken(token, mapping, session, userSession, clientSession);
if (mapper instanceof OIDCAccessTokenMapper) {
token = ((OIDCAccessTokenMapper) mapper).transformAccessToken(token, mapping, session, userSession, clientSession);
}
}
return token;
}
@ -520,16 +521,11 @@ public class TokenManager {
for (ProtocolMapperModel mapping : mappings) {
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
if (mapper == null || !(mapper instanceof OIDCAccessTokenMapper)) continue;
if(mapper instanceof UserInfoTokenMapper){
token = ((UserInfoTokenMapper)mapper).transformUserInfoToken(token, mapping, session, userSession, clientSession);
continue;
if (mapper instanceof UserInfoTokenMapper) {
token = ((UserInfoTokenMapper) mapper).transformUserInfoToken(token, mapping, session, userSession, clientSession);
}
token = ((OIDCAccessTokenMapper)mapper).transformAccessToken(token, mapping, session, userSession, clientSession);
}
return token;
}
@ -540,13 +536,12 @@ public class TokenManager {
for (ProtocolMapperModel mapping : mappings) {
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
if (mapper == null || !(mapper instanceof OIDCIDTokenMapper)) continue;
token = ((OIDCIDTokenMapper)mapper).transformIDToken(token, mapping, session, userSession, clientSession);
if (mapper instanceof OIDCIDTokenMapper) {
token = ((OIDCIDTokenMapper) mapper).transformIDToken(token, mapping, session, userSession, clientSession);
}
}
}
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, ClientSessionModel clientSession, UriInfo uriInfo) {
AccessToken token = new AccessToken();
if (clientSession != null) token.clientSession(clientSession.getId());

View file

@ -18,10 +18,15 @@
package org.keycloak.protocol.oidc.mappers;
import org.keycloak.Config;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -54,4 +59,46 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper {
public void postInit(KeycloakSessionFactory factory) {
}
public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInUserInfo(mappingModel)) {
return token;
}
setClaim(token, mappingModel, userSession);
return token;
}
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)){
return token;
}
setClaim(token, mappingModel, userSession);
return token;
}
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)){
return token;
}
setClaim(token, mappingModel, userSession);
return token;
}
/**
* Intended to be overridden in {@link ProtocolMapper} implementations to add claims to an token.
* @param token
* @param mappingModel
* @param userSession
*/
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
}
}

View file

@ -35,33 +35,7 @@ import java.util.Set;
*
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
*/
abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) {
return token;
}
setClaim(token, mappingModel, userSession);
return token;
}
@Override
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) {
return token;
}
setClaim(token, mappingModel, userSession);
return token;
}
protected abstract void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession);
abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper {
/**
* Returns the role names extracted from the given {@code roleModels} while recursively traversing "Composite Roles".

View file

@ -39,7 +39,7 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
@ -118,21 +118,7 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc
}
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
setClaim(token, userSession);
return token;
}
@Override
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
setClaim(token, userSession);
return token;
}
protected void setClaim(IDToken token, UserSessionModel userSession) {
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
UserModel user = userSession.getUser();
AddressClaimSet addressSet = new AddressClaimSet();
addressSet.setStreetAddress(user.getFirstAttribute("street"));

View file

@ -38,7 +38,7 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
@ -88,28 +88,13 @@ public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
return "Maps the user's first and last name to the OpenID Connect 'name' claim. Format is <first> + ' ' + <last>";
}
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
setClaim(token, userSession);
return token;
}
protected void setClaim(IDToken token, UserSessionModel userSession) {
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
UserModel user = userSession.getUser();
String first = user.getFirstName() == null ? "" : user.getFirstName() + " ";
String last = user.getLastName() == null ? "" : user.getLastName();
token.getOtherClaims().put("name", first + last);
}
@Override
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
setClaim(token, userSession);
return token;
}
public static ProtocolMapperModel create(String name,
boolean consentRequired, String consentText,
boolean accessToken, boolean idToken) {

View file

@ -40,7 +40,7 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
@ -113,15 +113,14 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements
}
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
buildMembership(token, mappingModel, userSession);
return token;
}
/**
* Adds the group membership information to the {@link IDToken#otherClaims}.
* @param token
* @param mappingModel
* @param userSession
*/
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
public void buildMembership(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
List<String> membership = new LinkedList<>();
boolean fullPath = useFullPath(mappingModel);
for (GroupModel group : userSession.getUser().getGroups()) {
@ -136,13 +135,6 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements
token.getOtherClaims().put(protocolClaim, membership);
}
@Override
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
buildMembership(token, mappingModel, userSession);
return token;
}
public static ProtocolMapperModel create(String name,
String tokenClaimName,
boolean consentRequired, String consentText,

View file

@ -37,7 +37,7 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
@ -113,28 +113,13 @@ public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAc
return "Hardcode a claim into the token.";
}
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
setClaim(token, mappingModel, userSession);
return token;
}
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
String attributeValue = mappingModel.getConfig().get(CLAIM_VALUE);
if (attributeValue == null) return;
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue);
}
@Override
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
setClaim(token, mappingModel, userSession);
return token;
}
public static ProtocolMapperModel create(String name,
String hardcodedName,
String hardcodedValue, String claimType,

View file

@ -83,6 +83,7 @@ public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAcc
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
String role = mappingModel.getConfig().get(ROLE_CONFIG);
String[] scopedRole = KeycloakModelUtils.parseRole(role);
String appName = scopedRole[0];
@ -97,6 +98,7 @@ public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAcc
}
access.addRole(role);
}
return token;
}

View file

@ -123,7 +123,7 @@ public class OIDCAttributeMapperHelper {
boolean consentRequired, String consentText,
boolean accessToken, boolean idToken,
String mapperId) {
return createClaimMapper(name, userAttribute,tokenClaimName, claimType, consentRequired, consentText, accessToken, idToken, false, mapperId);
return createClaimMapper(name, userAttribute,tokenClaimName, claimType, consentRequired, consentText, accessToken, idToken, true, mapperId);
}
public static ProtocolMapperModel createClaimMapper(String name,
@ -166,6 +166,7 @@ public class OIDCAttributeMapperHelper {
}
public static void addAttributeConfig(List<ProviderConfigProperty> configProperties) {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(TOKEN_CLAIM_NAME);
@ -173,6 +174,7 @@ public class OIDCAttributeMapperHelper {
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText(TOKEN_CLAIM_NAME_TOOLTIP);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(JSON_TYPE);
property.setLabel(JSON_TYPE);
@ -185,6 +187,7 @@ public class OIDCAttributeMapperHelper {
property.setOptions(types);
property.setHelpText(JSON_TYPE_TOOLTIP);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(INCLUDE_IN_ID_TOKEN);
property.setLabel(INCLUDE_IN_ID_TOKEN_LABEL);
@ -192,6 +195,7 @@ public class OIDCAttributeMapperHelper {
property.setDefaultValue("true");
property.setHelpText(INCLUDE_IN_ID_TOKEN_HELP_TEXT);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(INCLUDE_IN_ACCESS_TOKEN);
property.setLabel(INCLUDE_IN_ACCESS_TOKEN_LABEL);
@ -199,11 +203,12 @@ public class OIDCAttributeMapperHelper {
property.setDefaultValue("true");
property.setHelpText(INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(INCLUDE_IN_USERINFO);
property.setLabel(INCLUDE_IN_USERINFO_LABEL);
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("false");
property.setDefaultValue("true");
property.setHelpText(INCLUDE_IN_USERINFO_HELP_TEXT);
configProperties.add(property);
}

View file

@ -25,6 +25,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.ArrayList;
import java.util.HashMap;
@ -88,8 +89,8 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
}
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session2,
UserSessionModel userSession2, ClientSessionModel clientSessio2n) {
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
String role = mappingModel.getConfig().get(ROLE_CONFIG);
String newName = mappingModel.getConfig().get(NEW_ROLE_NAME);
@ -120,6 +121,7 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc
} else {
access = token.addAccess(newAppName);
}
access.addRole(newRoleName);
return token;
}

View file

@ -89,16 +89,8 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
return "Map a custom user attribute to a token claim.";
}
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
setClaim(token, mappingModel, userSession);
return token;
}
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
UserModel user = userSession.getUser();
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
List<String> attributeValue = KeycloakModelUtils.resolveAttribute(user, attributeName);
@ -106,24 +98,6 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue);
}
@Override
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
setClaim(token, mappingModel, userSession);
return token;
}
@Override
public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInUserInfo(mappingModel)) {
return token;
}
setClaim(token, mappingModel, userSession);
return token;
}
public static ProtocolMapperModel createClaimMapper(String name,
String userAttribute,
String tokenClaimName, String claimType,

View file

@ -29,5 +29,5 @@ import org.keycloak.representations.AccessToken;
public interface UserInfoTokenMapper {
AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession);
UserSessionModel userSession, ClientSessionModel clientSession);
}

View file

@ -79,24 +79,8 @@ public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OI
return "Map a built in user property (email, firstName, lastName) to a token claim.";
}
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
setClaim(token, mappingModel, userSession);
return token;
}
@Override
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
setClaim(token, mappingModel, userSession);
return token;
}
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
UserModel user = userSession.getUser();
String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName);
@ -114,6 +98,4 @@ public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OI
accessToken, idToken,
PROVIDER_ID);
}
}

View file

@ -79,29 +79,14 @@ public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements
return "Map a custom user session note to a token claim.";
}
@Override
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
setClaim(token, mappingModel, userSession);
return token;
}
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
String noteName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_SESSION_NOTE);
String noteValue = userSession.getNote(noteName);
if (noteValue == null) return;
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, noteValue);
}
@Override
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
setClaim(token, mappingModel, userSession);
return token;
}
public static ProtocolMapperModel createClaimMapper(String name,
String userSessionNote,
String tokenClaimName, String jsonType,