This commit is contained in:
Bill Burke 2015-03-02 21:39:43 -05:00
parent 5e12ee3e7a
commit 753feae49e
15 changed files with 406 additions and 206 deletions

View file

@ -16,6 +16,8 @@ public class ProtocolMapperTypeRepresentation {
protected String name;
protected String label;
protected String helpText;
protected String type;
protected String defaultValue;
public String getName() {
return name;
@ -33,6 +35,22 @@ public class ProtocolMapperTypeRepresentation {
this.label = label;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
public String getHelpText() {
return helpText;
}

View file

@ -457,12 +457,6 @@ public class RepresentationToModel {
applicationModel.updateDefaultRoles(resourceRep.getDefaultRoles());
}
if (resourceRep.getClaims() != null) {
setClaims(applicationModel, resourceRep.getClaims());
} else {
applicationModel.setAllowedClaimsMask(ClaimMask.ALL);
}
if (resourceRep.getProtocolMappers() != null) {
Set<String> ids = new HashSet<String>();
for (ClientProtocolMappingRepresentation map : resourceRep.getProtocolMappers()) {
@ -524,10 +518,6 @@ public class RepresentationToModel {
}
}
if (rep.getClaims() != null) {
setClaims(resource, rep.getClaims());
}
updateClientIdentityProvides(rep.getIdentityProviders(), resource);
}
@ -633,10 +623,6 @@ public class RepresentationToModel {
model.setWebOrigins(new HashSet<String>(webOrigins));
}
if (rep.getClaims() != null) {
setClaims(model, rep.getClaims());
}
if (rep.getNotBefore() != null) {
model.setNotBefore(rep.getNotBefore());
}

View file

@ -16,9 +16,14 @@ public interface ProtocolMapper extends Provider, ProviderFactory<ProtocolMapper
String getHelpText();
public static class ConfigProperty {
public static final String BOOLEAN_TYPE="boolean";
public static final String STRING_TYPE="String";
protected String name;
protected String label;
protected String helpText;
protected String type;
protected String defaultValue;
public String getName() {
return name;
@ -36,6 +41,22 @@ public interface ProtocolMapper extends Provider, ProviderFactory<ProtocolMapper
this.label = label;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
public String getHelpText() {
return helpText;
}

View file

@ -29,27 +29,32 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
"username",
"preferred_username", "String",
true, "username",
true);
true,
true, true);
OIDCUserModelMapper.addClaimMapper(realm, "email",
"email",
"email", "String",
true, "email",
true);
true,
true, true);
OIDCUserModelMapper.addClaimMapper(realm, "given name",
"firstName",
"given_name", "String",
true, "given name",
true);
true,
true, true);
OIDCUserModelMapper.addClaimMapper(realm, "family name",
"lastName",
"family_name", "String",
true, "family name",
true);
true,
true, true);
OIDCUserModelMapper.addClaimMapper(realm, "email verified",
"emailVerified",
"email_verified", "boolean",
false, null,
false);
false,
true, true);
ProtocolMapperModel fullName = new ProtocolMapperModel();
if (realm.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "full name") == null) {

View file

@ -9,7 +9,6 @@ import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
@ -25,7 +24,6 @@ import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.UserClaimSet;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.services.managers.AuthenticationManager;
@ -244,7 +242,7 @@ public class TokenManager {
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
if (mapper == null || !(mapper instanceof OIDCAccessTokenMapper)) continue;
token = ((OIDCAccessTokenMapper)mapper).transformToken(token, mapping, session, userSession, clientSession);
token = ((OIDCAccessTokenMapper)mapper).transformAccessToken(token, mapping, session, userSession, clientSession);

View file

@ -1,154 +1,154 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.oidc;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.ClientConnection;
import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.UserClaimSet;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.EventsManager;
import org.keycloak.services.resources.Cors;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.util.HashMap;
import java.util.Map;
/**
* @author pedroigor
*/
public class UserInfoService {
@Context
private HttpRequest request;
@Context
private HttpResponse response;
@Context
private KeycloakSession session;
@Context
private ClientConnection clientConnection;
private final TokenManager tokenManager;
private final AppAuthManager appAuthManager;
private final OIDCLoginProtocolService openIdConnectService;
private final RealmModel realmModel;
public UserInfoService(OIDCLoginProtocolService openIDConnectService) {
this.realmModel = openIDConnectService.getRealm();
if (this.realmModel == null) {
throw new RuntimeException("Null realm.");
}
this.tokenManager = openIDConnectService.getTokenManager();
if (this.tokenManager == null) {
throw new RuntimeException("Null token manager.");
}
this.openIdConnectService = openIDConnectService;
this.appAuthManager = new AppAuthManager();
}
@Path("/")
@OPTIONS
@Produces(MediaType.APPLICATION_JSON)
public Response issueUserInfoPreflight() {
return Cors.add(this.request, Response.ok()).auth().preflight().build();
}
@Path("/")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public Response issueUserInfoGet(@Context final HttpHeaders headers) {
String accessToken = this.appAuthManager.extractAuthorizationHeaderToken(headers);
return issueUserInfo(accessToken);
}
@Path("/")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response issueUserInfoPost(@FormParam("access_token") String accessToken) {
return issueUserInfo(accessToken);
}
private Response issueUserInfo(String token) {
try {
EventBuilder event = new EventsManager(this.realmModel, this.session, this.clientConnection).createEventBuilder()
.event(EventType.USER_INFO_REQUEST)
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN);
Response validationResponse = this.openIdConnectService.validateAccessToken(token);
if (!AccessToken.class.isInstance(validationResponse.getEntity())) {
event.error(EventType.USER_INFO_REQUEST.name());
return Response.fromResponse(validationResponse).status(Status.FORBIDDEN).build();
}
AccessToken accessToken = (AccessToken) validationResponse.getEntity();
UserSessionModel userSession = session.sessions().getUserSession(realmModel, accessToken.getSessionState());
ClientModel clientModel = realmModel.findClient(accessToken.getIssuedFor());
UserModel userModel = userSession.getUser();
AccessToken userInfo = new AccessToken();
this.tokenManager.transformToken(session, userInfo, realmModel, clientModel, userModel, userSession, null);
event
.detail(Details.USERNAME, userModel.getUsername())
.client(clientModel)
.session(userSession)
.user(userModel)
.success();
Map<String, Object> claims = new HashMap<String, Object>();
claims.putAll(userInfo.getOtherClaims());
claims.put("sub", userModel.getId());
return Cors.add(request, Response.ok(claims)).auth().allowedOrigins(accessToken).build();
} catch (Exception e) {
throw new UnauthorizedException("Could not retrieve user info.", e);
}
}
}
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.oidc;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.ClientConnection;
import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.UserClaimSet;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.EventsManager;
import org.keycloak.services.resources.Cors;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.util.HashMap;
import java.util.Map;
/**
* @author pedroigor
*/
public class UserInfoService {
@Context
private HttpRequest request;
@Context
private HttpResponse response;
@Context
private KeycloakSession session;
@Context
private ClientConnection clientConnection;
private final TokenManager tokenManager;
private final AppAuthManager appAuthManager;
private final OIDCLoginProtocolService openIdConnectService;
private final RealmModel realmModel;
public UserInfoService(OIDCLoginProtocolService openIDConnectService) {
this.realmModel = openIDConnectService.getRealm();
if (this.realmModel == null) {
throw new RuntimeException("Null realm.");
}
this.tokenManager = openIDConnectService.getTokenManager();
if (this.tokenManager == null) {
throw new RuntimeException("Null token manager.");
}
this.openIdConnectService = openIDConnectService;
this.appAuthManager = new AppAuthManager();
}
@Path("/")
@OPTIONS
@Produces(MediaType.APPLICATION_JSON)
public Response issueUserInfoPreflight() {
return Cors.add(this.request, Response.ok()).auth().preflight().build();
}
@Path("/")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public Response issueUserInfoGet(@Context final HttpHeaders headers) {
String accessToken = this.appAuthManager.extractAuthorizationHeaderToken(headers);
return issueUserInfo(accessToken);
}
@Path("/")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response issueUserInfoPost(@FormParam("access_token") String accessToken) {
return issueUserInfo(accessToken);
}
private Response issueUserInfo(String token) {
try {
EventBuilder event = new EventsManager(this.realmModel, this.session, this.clientConnection).createEventBuilder()
.event(EventType.USER_INFO_REQUEST)
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN);
Response validationResponse = this.openIdConnectService.validateAccessToken(token);
if (!AccessToken.class.isInstance(validationResponse.getEntity())) {
event.error(EventType.USER_INFO_REQUEST.name());
return Response.fromResponse(validationResponse).status(Status.FORBIDDEN).build();
}
AccessToken accessToken = (AccessToken) validationResponse.getEntity();
UserSessionModel userSession = session.sessions().getUserSession(realmModel, accessToken.getSessionState());
ClientModel clientModel = realmModel.findClient(accessToken.getIssuedFor());
UserModel userModel = userSession.getUser();
AccessToken userInfo = new AccessToken();
this.tokenManager.transformToken(session, userInfo, realmModel, clientModel, userModel, userSession, null);
event
.detail(Details.USERNAME, userModel.getUsername())
.client(clientModel)
.session(userSession)
.user(userModel)
.success();
Map<String, Object> claims = new HashMap<String, Object>();
claims.putAll(userInfo.getOtherClaims());
claims.put("sub", userModel.getId());
return Cors.add(request, Response.ok(claims)).auth().allowedOrigins(accessToken).build();
} catch (Exception e) {
throw new UnauthorizedException("Could not retrieve user info.", e);
}
}
}

View file

@ -12,6 +12,6 @@ import org.keycloak.representations.AccessToken;
*/
public interface OIDCAccessTokenMapper {
AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession);
AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession);
}

View file

@ -6,6 +6,7 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.UserClaimSet;
import java.util.ArrayList;
@ -17,12 +18,26 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
public class OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
static {
ConfigProperty property;
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
configProperties.add(property);
}
public static final String PROVIDER_ID = "oidc-address-mapper";
@ -53,8 +68,21 @@ public class OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OID
}
@Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
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) {
UserModel user = userSession.getUser();
UserClaimSet.AddressClaimSet addressSet = new UserClaimSet.AddressClaimSet();
addressSet.setStreetAddress(user.getAttribute("street"));
@ -63,7 +91,6 @@ public class OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OID
addressSet.setPostalCode(user.getAttribute("postal_code"));
addressSet.setCountry(user.getAttribute("country"));
token.getOtherClaims().put("address", addressSet);
return token;
}
}

View file

@ -5,6 +5,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.HashMap;
import java.util.Map;
@ -16,6 +17,12 @@ import java.util.Map;
public class OIDCAttributeMapperHelper {
public static final String TOKEN_CLAIM_NAME = "Token Claim Name";
public static final String JSON_TYPE = "Claim JSON Type";
public static final String INCLUDE_IN_ACCESS_TOKEN = "access.token.claim";
public static final String INCLUDE_IN_ACCESS_TOKEN_LABEL = "Add to access token";
public static final String INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT = "Should the claim be added to the access token?";
public static final String INCLUDE_IN_ID_TOKEN = "id.token.claim";
public static final String INCLUDE_IN_ID_TOKEN_LABEL = "Add to ID token";
public static final String INCLUDE_IN_ID_TOKEN_HELP_TEXT = "Should the claim be added to the ID token?";
public static Object mapAttributeValue(ProtocolMapperModel mappingModel, Object attributeValue) {
if (attributeValue == null) return null;
@ -40,7 +47,7 @@ public class OIDCAttributeMapperHelper {
return attributeValue;
}
public static void mapClaim(AccessToken token, ProtocolMapperModel mappingModel, Object attributeValue) {
public static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Object attributeValue) {
if (attributeValue == null) return;
attributeValue = mapAttributeValue(mappingModel, attributeValue);
String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME);
@ -65,6 +72,7 @@ public class OIDCAttributeMapperHelper {
String tokenClaimName, String claimType,
boolean consentRequired, String consentText,
boolean appliedByDefault,
boolean accessToken, boolean idToken,
String mapperId) {
ProtocolMapperModel mapper = realm.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, name);
if (mapper != null) return;
@ -79,7 +87,17 @@ public class OIDCAttributeMapperHelper {
config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute);
config.put(TOKEN_CLAIM_NAME, tokenClaimName);
config.put(JSON_TYPE, claimType);
if (accessToken) config.put(INCLUDE_IN_ACCESS_TOKEN, "true");
if (idToken) config.put(INCLUDE_IN_ID_TOKEN, "true");
mapper.setConfig(config);
realm.addProtocolMapper(mapper);
}
public static boolean includeInIDToken(ProtocolMapperModel mappingModel) {
return "true".equals(mappingModel.getConfig().get(INCLUDE_IN_ID_TOKEN));
}
public static boolean includeInAccessToken(ProtocolMapperModel mappingModel) {
return "true".equals(mappingModel.getConfig().get(INCLUDE_IN_ACCESS_TOKEN));
}
}

View file

@ -6,6 +6,7 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.ArrayList;
import java.util.List;
@ -16,11 +17,26 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCFullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
public class OIDCFullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
static {
ConfigProperty property;
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
configProperties.add(property);
}
@ -52,13 +68,24 @@ public class OIDCFullNameMapper extends AbstractOIDCProtocolMapper implements OI
}
@Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
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) {
UserModel user = userSession.getUser();
String first = user.getFirstName() == null ? "" : user.getFirstName() + " ";
String last = user.getLastName() == null ? "" : user.getLastName();
token.getOtherClaims().put("name", first + last);
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;
}
}

View file

@ -0,0 +1,18 @@
package org.keycloak.protocol.oidc.mappers;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface OIDCIDTokenMapper {
IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession);
}

View file

@ -8,6 +8,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.ArrayList;
import java.util.List;
@ -20,7 +21,7 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
@ -30,12 +31,35 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL);
property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
property.setType(ConfigProperty.STRING_TYPE);
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setType(ConfigProperty.STRING_TYPE);
property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created.");
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.JSON_TYPE);
property.setLabel(OIDCAttributeMapperHelper.JSON_TYPE);
property.setType(ConfigProperty.STRING_TYPE);
property.setDefaultValue(ConfigProperty.STRING_TYPE);
property.setHelpText("JSON type that should be used to populate the json claim in the token. long, int, boolean, and String are valid values.");
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
configProperties.add(property);
}
@ -67,13 +91,26 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen
}
@Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
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);
String attributeValue = user.getAttribute(attributeName);
if (attributeValue == null) return token;
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;
}
@ -81,11 +118,13 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen
String userAttribute,
String tokenClaimName, String claimType,
boolean consentRequired, String consentText,
boolean appliedByDefault) {
boolean appliedByDefault,
boolean accessToken, boolean idToken) {
OIDCAttributeMapperHelper.addClaimMapper(realm, name, userAttribute,
tokenClaimName, claimType,
consentRequired, consentText,
appliedByDefault, PROVIDER_ID);
appliedByDefault, accessToken, idToken,
PROVIDER_ID);
}

View file

@ -8,6 +8,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.ArrayList;
import java.util.List;
@ -20,7 +21,7 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>();
static {
@ -28,14 +29,43 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O
property = new ConfigProperty();
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL);
property.setType(ConfigProperty.STRING_TYPE);
property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT);
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setType(ConfigProperty.STRING_TYPE);
property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created.");
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.JSON_TYPE);
property.setLabel(OIDCAttributeMapperHelper.JSON_TYPE);
property.setType(ConfigProperty.STRING_TYPE);
property.setDefaultValue(ConfigProperty.STRING_TYPE);
property.setHelpText("JSON type that should be used to populate the json claim in the token. long, int, boolean, and String are valid values.");
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.JSON_TYPE);
property.setLabel(OIDCAttributeMapperHelper.JSON_TYPE);
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText("JSON type that should be used to populate the json claim in the token. long, int, boolean, and String are valid values.");
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
configProperties.add(property);
property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
property.setType(ConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
configProperties.add(property);
}
public static final String PROVIDER_ID = "oidc-usermodel-property-mapper";
@ -66,25 +96,40 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O
}
@Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) {
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);
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, propertyValue);
return token;
}
public static void addClaimMapper(RealmModel realm, String name,
String userAttribute,
String tokenClaimName, String claimType,
boolean consentRequired, String consentText,
boolean appliedByDefault) {
boolean appliedByDefault,
boolean accessToken, boolean idToken) {
OIDCAttributeMapperHelper.addClaimMapper(realm, name, userAttribute,
tokenClaimName, claimType,
consentRequired, consentText,
appliedByDefault, PROVIDER_ID);
appliedByDefault, accessToken, idToken,
PROVIDER_ID);
}

View file

@ -150,6 +150,8 @@ public class ServerInfoAdminResource {
ProtocolMapperTypeRepresentation.ConfigProperty propRep = new ProtocolMapperTypeRepresentation.ConfigProperty();
propRep.setName(prop.getName());
propRep.setLabel(prop.getLabel());
propRep.setType(prop.getType());
propRep.setDefaultValue(prop.getDefaultValue());
propRep.setHelpText(prop.getHelpText());
rep.getProperties().add(propRep);
}

View file

@ -214,10 +214,6 @@ public class AdminAPITest {
Assert.assertEquals(set, storedSet);
}
if (appRep.getClaims() != null) {
Assert.assertEquals(appRep.getClaims(), storedApp.getClaims());
}
}
protected void checkRealmRep(RealmRepresentation rep, RealmRepresentation storedRealm) {