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 name;
protected String label; protected String label;
protected String helpText; protected String helpText;
protected String type;
protected String defaultValue;
public String getName() { public String getName() {
return name; return name;
@ -33,6 +35,22 @@ public class ProtocolMapperTypeRepresentation {
this.label = label; 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() { public String getHelpText() {
return helpText; return helpText;
} }

View file

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

View file

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

View file

@ -29,27 +29,32 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
"username", "username",
"preferred_username", "String", "preferred_username", "String",
true, "username", true, "username",
true); true,
true, true);
OIDCUserModelMapper.addClaimMapper(realm, "email", OIDCUserModelMapper.addClaimMapper(realm, "email",
"email", "email",
"email", "String", "email", "String",
true, "email", true, "email",
true); true,
true, true);
OIDCUserModelMapper.addClaimMapper(realm, "given name", OIDCUserModelMapper.addClaimMapper(realm, "given name",
"firstName", "firstName",
"given_name", "String", "given_name", "String",
true, "given name", true, "given name",
true); true,
true, true);
OIDCUserModelMapper.addClaimMapper(realm, "family name", OIDCUserModelMapper.addClaimMapper(realm, "family name",
"lastName", "lastName",
"family_name", "String", "family_name", "String",
true, "family name", true, "family name",
true); true,
true, true);
OIDCUserModelMapper.addClaimMapper(realm, "email verified", OIDCUserModelMapper.addClaimMapper(realm, "email verified",
"emailVerified", "emailVerified",
"email_verified", "boolean", "email_verified", "boolean",
false, null, false, null,
false); false,
true, true);
ProtocolMapperModel fullName = new ProtocolMapperModel(); ProtocolMapperModel fullName = new ProtocolMapperModel();
if (realm.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "full name") == null) { 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.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ApplicationModel; import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -25,7 +24,6 @@ import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper; import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.UserClaimSet;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
@ -244,7 +242,7 @@ public class TokenManager {
ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper()); ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
if (mapper == null || !(mapper instanceof OIDCAccessTokenMapper)) continue; 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 * JBoss, Home of Professional Open Source
* *
* Copyright 2013 Red Hat, Inc. and/or its affiliates. * Copyright 2013 Red Hat, Inc. and/or its affiliates.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.protocol.oidc; package org.keycloak.protocol.oidc;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.UnauthorizedException; import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.ClientConnection; import org.keycloak.ClientConnection;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
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.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.UserClaimSet; import org.keycloak.representations.UserClaimSet;
import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.EventsManager; import org.keycloak.services.managers.EventsManager;
import org.keycloak.services.resources.Cors; import org.keycloak.services.resources.Cors;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam; import javax.ws.rs.FormParam;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS; import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* @author pedroigor * @author pedroigor
*/ */
public class UserInfoService { public class UserInfoService {
@Context @Context
private HttpRequest request; private HttpRequest request;
@Context @Context
private HttpResponse response; private HttpResponse response;
@Context @Context
private KeycloakSession session; private KeycloakSession session;
@Context @Context
private ClientConnection clientConnection; private ClientConnection clientConnection;
private final TokenManager tokenManager; private final TokenManager tokenManager;
private final AppAuthManager appAuthManager; private final AppAuthManager appAuthManager;
private final OIDCLoginProtocolService openIdConnectService; private final OIDCLoginProtocolService openIdConnectService;
private final RealmModel realmModel; private final RealmModel realmModel;
public UserInfoService(OIDCLoginProtocolService openIDConnectService) { public UserInfoService(OIDCLoginProtocolService openIDConnectService) {
this.realmModel = openIDConnectService.getRealm(); this.realmModel = openIDConnectService.getRealm();
if (this.realmModel == null) { if (this.realmModel == null) {
throw new RuntimeException("Null realm."); throw new RuntimeException("Null realm.");
} }
this.tokenManager = openIDConnectService.getTokenManager(); this.tokenManager = openIDConnectService.getTokenManager();
if (this.tokenManager == null) { if (this.tokenManager == null) {
throw new RuntimeException("Null token manager."); throw new RuntimeException("Null token manager.");
} }
this.openIdConnectService = openIDConnectService; this.openIdConnectService = openIDConnectService;
this.appAuthManager = new AppAuthManager(); this.appAuthManager = new AppAuthManager();
} }
@Path("/") @Path("/")
@OPTIONS @OPTIONS
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Response issueUserInfoPreflight() { public Response issueUserInfoPreflight() {
return Cors.add(this.request, Response.ok()).auth().preflight().build(); return Cors.add(this.request, Response.ok()).auth().preflight().build();
} }
@Path("/") @Path("/")
@GET @GET
@NoCache @NoCache
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Response issueUserInfoGet(@Context final HttpHeaders headers) { public Response issueUserInfoGet(@Context final HttpHeaders headers) {
String accessToken = this.appAuthManager.extractAuthorizationHeaderToken(headers); String accessToken = this.appAuthManager.extractAuthorizationHeaderToken(headers);
return issueUserInfo(accessToken); return issueUserInfo(accessToken);
} }
@Path("/") @Path("/")
@POST @POST
@NoCache @NoCache
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Response issueUserInfoPost(@FormParam("access_token") String accessToken) { public Response issueUserInfoPost(@FormParam("access_token") String accessToken) {
return issueUserInfo(accessToken); return issueUserInfo(accessToken);
} }
private Response issueUserInfo(String token) { private Response issueUserInfo(String token) {
try { try {
EventBuilder event = new EventsManager(this.realmModel, this.session, this.clientConnection).createEventBuilder() EventBuilder event = new EventsManager(this.realmModel, this.session, this.clientConnection).createEventBuilder()
.event(EventType.USER_INFO_REQUEST) .event(EventType.USER_INFO_REQUEST)
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN); .detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN);
Response validationResponse = this.openIdConnectService.validateAccessToken(token); Response validationResponse = this.openIdConnectService.validateAccessToken(token);
if (!AccessToken.class.isInstance(validationResponse.getEntity())) { if (!AccessToken.class.isInstance(validationResponse.getEntity())) {
event.error(EventType.USER_INFO_REQUEST.name()); event.error(EventType.USER_INFO_REQUEST.name());
return Response.fromResponse(validationResponse).status(Status.FORBIDDEN).build(); return Response.fromResponse(validationResponse).status(Status.FORBIDDEN).build();
} }
AccessToken accessToken = (AccessToken) validationResponse.getEntity(); AccessToken accessToken = (AccessToken) validationResponse.getEntity();
UserSessionModel userSession = session.sessions().getUserSession(realmModel, accessToken.getSessionState()); UserSessionModel userSession = session.sessions().getUserSession(realmModel, accessToken.getSessionState());
ClientModel clientModel = realmModel.findClient(accessToken.getIssuedFor()); ClientModel clientModel = realmModel.findClient(accessToken.getIssuedFor());
UserModel userModel = userSession.getUser(); UserModel userModel = userSession.getUser();
AccessToken userInfo = new AccessToken(); AccessToken userInfo = new AccessToken();
this.tokenManager.transformToken(session, userInfo, realmModel, clientModel, userModel, userSession, null); this.tokenManager.transformToken(session, userInfo, realmModel, clientModel, userModel, userSession, null);
event event
.detail(Details.USERNAME, userModel.getUsername()) .detail(Details.USERNAME, userModel.getUsername())
.client(clientModel) .client(clientModel)
.session(userSession) .session(userSession)
.user(userModel) .user(userModel)
.success(); .success();
Map<String, Object> claims = new HashMap<String, Object>(); Map<String, Object> claims = new HashMap<String, Object>();
claims.putAll(userInfo.getOtherClaims()); claims.putAll(userInfo.getOtherClaims());
claims.put("sub", userModel.getId()); claims.put("sub", userModel.getId());
return Cors.add(request, Response.ok(claims)).auth().allowedOrigins(accessToken).build(); return Cors.add(request, Response.ok(claims)).auth().allowedOrigins(accessToken).build();
} catch (Exception e) { } catch (Exception e) {
throw new UnauthorizedException("Could not retrieve user info.", e); throw new UnauthorizedException("Could not retrieve user info.", e);
} }
} }
} }

View file

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

View file

@ -6,6 +6,7 @@ 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.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.UserClaimSet; import org.keycloak.representations.UserClaimSet;
import java.util.ArrayList; import java.util.ArrayList;
@ -17,12 +18,26 @@ 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 OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { public class OIDCAddressMapper 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 {
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"; public static final String PROVIDER_ID = "oidc-address-mapper";
@ -53,8 +68,21 @@ public class OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OID
} }
@Override @Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) { 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(); UserModel user = userSession.getUser();
UserClaimSet.AddressClaimSet addressSet = new UserClaimSet.AddressClaimSet(); UserClaimSet.AddressClaimSet addressSet = new UserClaimSet.AddressClaimSet();
addressSet.setStreetAddress(user.getAttribute("street")); addressSet.setStreetAddress(user.getAttribute("street"));
@ -63,7 +91,6 @@ public class OIDCAddressMapper extends AbstractOIDCProtocolMapper implements OID
addressSet.setPostalCode(user.getAttribute("postal_code")); addressSet.setPostalCode(user.getAttribute("postal_code"));
addressSet.setCountry(user.getAttribute("country")); addressSet.setCountry(user.getAttribute("country"));
token.getOtherClaims().put("address", addressSet); 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.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -16,6 +17,12 @@ import java.util.Map;
public class OIDCAttributeMapperHelper { public class OIDCAttributeMapperHelper {
public static final String TOKEN_CLAIM_NAME = "Token Claim Name"; public static final String TOKEN_CLAIM_NAME = "Token Claim Name";
public static final String JSON_TYPE = "Claim JSON Type"; 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) { public static Object mapAttributeValue(ProtocolMapperModel mappingModel, Object attributeValue) {
if (attributeValue == null) return null; if (attributeValue == null) return null;
@ -40,7 +47,7 @@ public class OIDCAttributeMapperHelper {
return attributeValue; 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; if (attributeValue == null) return;
attributeValue = mapAttributeValue(mappingModel, attributeValue); attributeValue = mapAttributeValue(mappingModel, attributeValue);
String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME); String protocolClaim = mappingModel.getConfig().get(TOKEN_CLAIM_NAME);
@ -65,6 +72,7 @@ public class OIDCAttributeMapperHelper {
String tokenClaimName, String claimType, String tokenClaimName, String claimType,
boolean consentRequired, String consentText, boolean consentRequired, String consentText,
boolean appliedByDefault, boolean appliedByDefault,
boolean accessToken, boolean idToken,
String mapperId) { String mapperId) {
ProtocolMapperModel mapper = realm.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, name); ProtocolMapperModel mapper = realm.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, name);
if (mapper != null) return; if (mapper != null) return;
@ -79,7 +87,17 @@ public class OIDCAttributeMapperHelper {
config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute); config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute);
config.put(TOKEN_CLAIM_NAME, tokenClaimName); config.put(TOKEN_CLAIM_NAME, tokenClaimName);
config.put(JSON_TYPE, claimType); 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); mapper.setConfig(config);
realm.addProtocolMapper(mapper); 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.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -16,11 +17,26 @@ 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 { public class OIDCFullNameMapper 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 {
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 @Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) { 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(); UserModel user = userSession.getUser();
String first = user.getFirstName() == null ? "" : user.getFirstName() + " "; String first = user.getFirstName() == null ? "" : user.getFirstName() + " ";
String last = user.getLastName() == null ? "" : user.getLastName(); String last = user.getLastName() == null ? "" : user.getLastName();
token.getOtherClaims().put("name", first + last); 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.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -20,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 { public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
private static final List<ConfigProperty> configProperties = new ArrayList<ConfigProperty>(); 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.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL); property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL);
property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT); property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
property.setType(ConfigProperty.STRING_TYPE);
configProperties.add(property); configProperties.add(property);
property = new ConfigProperty(); property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setLabel(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."); 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); 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 @Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) { 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(); UserModel user = userSession.getUser();
String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE); String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
String attributeValue = user.getAttribute(attributeName); String attributeValue = user.getAttribute(attributeName);
if (attributeValue == null) return token; if (attributeValue == null) return;
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue); 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; return token;
} }
@ -81,11 +118,13 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen
String userAttribute, String userAttribute,
String tokenClaimName, String claimType, String tokenClaimName, String claimType,
boolean consentRequired, String consentText, boolean consentRequired, String consentText,
boolean appliedByDefault) { boolean appliedByDefault,
boolean accessToken, boolean idToken) {
OIDCAttributeMapperHelper.addClaimMapper(realm, name, userAttribute, OIDCAttributeMapperHelper.addClaimMapper(realm, name, userAttribute,
tokenClaimName, claimType, tokenClaimName, claimType,
consentRequired, consentText, 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.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -20,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 { public class OIDCUserModelMapper 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 {
@ -28,14 +29,43 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O
property = new ConfigProperty(); property = new ConfigProperty();
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE); property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL); property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL);
property.setType(ConfigProperty.STRING_TYPE);
property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT); property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT);
configProperties.add(property); configProperties.add(property);
property = new ConfigProperty(); property = new ConfigProperty();
property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME); property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setLabel(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."); 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); 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"; public static final String PROVIDER_ID = "oidc-usermodel-property-mapper";
@ -66,25 +96,40 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O
} }
@Override @Override
public AccessToken transformToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
UserSessionModel userSession, ClientSessionModel clientSession) { 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(); UserModel user = userSession.getUser();
String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE); String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName); String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName);
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, propertyValue); OIDCAttributeMapperHelper.mapClaim(token, mappingModel, propertyValue);
return token;
} }
public static void addClaimMapper(RealmModel realm, String name, public static void addClaimMapper(RealmModel realm, String name,
String userAttribute, String userAttribute,
String tokenClaimName, String claimType, String tokenClaimName, String claimType,
boolean consentRequired, String consentText, boolean consentRequired, String consentText,
boolean appliedByDefault) { boolean appliedByDefault,
boolean accessToken, boolean idToken) {
OIDCAttributeMapperHelper.addClaimMapper(realm, name, userAttribute, OIDCAttributeMapperHelper.addClaimMapper(realm, name, userAttribute,
tokenClaimName, claimType, tokenClaimName, claimType,
consentRequired, consentText, 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(); ProtocolMapperTypeRepresentation.ConfigProperty propRep = new ProtocolMapperTypeRepresentation.ConfigProperty();
propRep.setName(prop.getName()); propRep.setName(prop.getName());
propRep.setLabel(prop.getLabel()); propRep.setLabel(prop.getLabel());
propRep.setType(prop.getType());
propRep.setDefaultValue(prop.getDefaultValue());
propRep.setHelpText(prop.getHelpText()); propRep.setHelpText(prop.getHelpText());
rep.getProperties().add(propRep); rep.getProperties().add(propRep);
} }

View file

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