diff --git a/core/src/main/java/org/keycloak/representations/JsonWebToken.java b/core/src/main/java/org/keycloak/representations/JsonWebToken.java index f2d1a9765a..0ed565171f 100755 --- a/core/src/main/java/org/keycloak/representations/JsonWebToken.java +++ b/core/src/main/java/org/keycloak/representations/JsonWebToken.java @@ -40,6 +40,9 @@ import java.util.Map; * @version $Revision: 1 $ */ public class JsonWebToken implements Serializable, Token { + public static final String AZP = "azp"; + public static final String SUBJECT = "sub"; + @JsonProperty("jti") protected String id; @@ -53,11 +56,11 @@ public class JsonWebToken implements Serializable, Token { @JsonSerialize(using = StringOrArraySerializer.class) @JsonDeserialize(using = StringOrArrayDeserializer.class) protected String[] audience; - @JsonProperty("sub") + @JsonProperty(SUBJECT) protected String subject; @JsonProperty("typ") protected String type; - @JsonProperty("azp") + @JsonProperty(AZP) public String issuedFor; protected Map otherClaims = new HashMap<>(); @@ -184,7 +187,7 @@ public class JsonWebToken implements Serializable, Token { this.iat = iat; return this; } - + /** * @deprecated int will overflow with values after 2038. Use {@link #iat(Long)} ()} instead. */ diff --git a/js/apps/admin-ui/public/locales/en/translation.json b/js/apps/admin-ui/public/locales/en/translation.json index e67335ae0e..e8438a545c 100644 --- a/js/apps/admin-ui/public/locales/en/translation.json +++ b/js/apps/admin-ui/public/locales/en/translation.json @@ -3096,6 +3096,10 @@ "label": "Add to userinfo", "tooltip": "Should the claim be added to the userinfo?" }, + "includeInIntrospection": { + "label": "Add to token introspection", + "tooltip": "Should the claim be added to the token introspection?" + }, "sectorIdentifierUri": { "label": "Sector Identifier URI", "tooltip": "Providers that use pairwise sub values and support Dynamic Client Registration SHOULD use the sector_identifier_uri parameter. It provides a way for a group of websites under common administrative control to have consistent pairwise sub values independent of the individual domain names. It also provides a way for Clients to change redirect_uri domains without having to reregister all their users." diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java index ae314e1d7c..e06408b91f 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java @@ -315,6 +315,7 @@ public class UserSessionAdapter implements UserSessionModel { update(task); } + @Override public SessionPersistenceState getPersistenceState() { return persistenceState; } diff --git a/model/legacy-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java b/model/legacy-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java index da5cb96bd6..2b782c60b3 100644 --- a/model/legacy-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java +++ b/model/legacy-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java @@ -232,6 +232,11 @@ public class PersistentUserSessionAdapter implements OfflineUserSessionModel { return State.valueOf(state); } + @Override + public SessionPersistenceState getPersistenceState() { + return SessionPersistenceState.PERSISTENT; + } + @Override public void setState(State state) { String stateStr = state==null ? null : state.toString(); diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionAdapter.java b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionAdapter.java index a7595e32b6..5087347208 100644 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionAdapter.java @@ -23,7 +23,6 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelIllegalStateException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; - import org.keycloak.models.UserSessionModel; import org.keycloak.models.map.common.TimeAdapter; @@ -289,4 +288,9 @@ public class MapUserSessionAdapter extends AbstractUserSessionModel { public int hashCode() { return getId().hashCode(); } + + @Override + public SessionPersistenceState getPersistenceState() { + return entity.getPersistenceState(); + } } diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java index 121b10e078..aba486ec3b 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java +++ b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java @@ -83,7 +83,7 @@ public interface UserSessionModel { /** * Returns map where key is ID of the client (its UUID) and value is ID respective {@link AuthenticatedClientSessionModel} object. - * @return + * @return */ Map getAuthenticatedClientSessions(); /** @@ -135,6 +135,8 @@ public interface UserSessionModel { } } + SessionPersistenceState getPersistenceState(); + /** * Flag used when creating user session */ diff --git a/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java index 20d5205069..7b2c5d44f6 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java @@ -24,6 +24,12 @@ import org.keycloak.TokenVerifier; import org.keycloak.common.VerificationException; import org.keycloak.crypto.SignatureProvider; import org.keycloak.crypto.SignatureVerifierContext; +import org.keycloak.events.Details; +import org.keycloak.events.EventBuilder; +import org.keycloak.events.EventType; +import org.keycloak.models.AuthenticatedClientSessionModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientSessionContext; import org.keycloak.models.ImpersonationSessionNote; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -31,6 +37,8 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.representations.AccessToken; import org.keycloak.services.Urls; +import org.keycloak.services.util.DefaultClientSessionContext; +import org.keycloak.services.util.UserSessionUtil; import org.keycloak.util.JsonSerialization; import jakarta.ws.rs.core.MediaType; @@ -55,6 +63,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi public Response introspect(String token) { try { AccessToken accessToken = verifyAccessToken(token); + accessToken = transformAccessToken(accessToken); ObjectNode tokenMetadata; if (accessToken != null) { @@ -106,6 +115,57 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi } } + private AccessToken transformAccessToken(AccessToken token) { + if (token == null) { + return null; + } + + ClientModel client = realm.getClientByClientId(token.getIssuedFor()); + EventBuilder event = new EventBuilder(realm, session, session.getContext().getConnection()) + .event(EventType.INTROSPECT_TOKEN) + .detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN); + UserSessionModel userSession; + try { + userSession = UserSessionUtil.findValidSession(session, realm, token, event, client); + } catch (Exception e) { + logger.debugf("Can not get user session: %s", e.getMessage()); + // Backwards compatibility + return token; + } + if (userSession.getUser() == null) { + logger.debugf("User not found"); + // Backwards compatibility + return token; + } + AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId()); + ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession, session); + AccessToken smallToken = getAccessTokenFromStoredData(token, userSession); + return tokenManager.transformIntrospectionAccessToken(session, smallToken, userSession, clientSessionCtx); + } + + private AccessToken getAccessTokenFromStoredData(AccessToken token, UserSessionModel userSession) { + // Copy just "basic" claims from the initial token. The same like filled in TokenManager.initToken. The rest should be possibly added by protocol mappers (only if configured for introspection response) + AccessToken newToken = new AccessToken(); + newToken.id(token.getId()); + newToken.type(token.getType()); + newToken.subject(token.getSubject() != null ? token.getSubject() : userSession.getUser().getId()); + newToken.iat(token.getIat()); + newToken.exp(token.getExp()); + newToken.issuedFor(token.getIssuedFor()); + newToken.issuer(token.getIssuer()); + newToken.setNonce(token.getNonce()); + newToken.setScope(token.getScope()); + newToken.setAuth_time(token.getAuth_time()); + newToken.setSessionState(token.getSessionState()); + + // In the case of a refresh token, aud is a basic claim. + newToken.audience(token.getAudience()); + + // The cnf is not a claim controlled by the protocol mapper. + newToken.setConfirmation(token.getConfirmation()); + return newToken; + } + protected AccessToken verifyAccessToken(String token) { AccessToken accessToken; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java index c9bf0c46af..3a5f572213 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java @@ -133,29 +133,29 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { private Map builtins = new HashMap<>(); void initBuiltIns() { - ProtocolMapperModel model; + ProtocolMapperModel model; model = UserAttributeMapper.createClaimMapper(USERNAME, "username", "preferred_username", String.class.getSimpleName(), - true, true); + true, true, true); builtins.put(USERNAME, model); model = UserAttributeMapper.createClaimMapper(EMAIL, "email", "email", "String", - true, true); + true, true, true); builtins.put(EMAIL, model); model = UserAttributeMapper.createClaimMapper(GIVEN_NAME, "firstName", "given_name", "String", - true, true); + true, true, true); builtins.put(GIVEN_NAME, model); model = UserAttributeMapper.createClaimMapper(FAMILY_NAME, "lastName", "family_name", "String", - true, true); + true, true, true); builtins.put(FAMILY_NAME, model); createUserAttributeMapper(MIDDLE_NAME, "middleName", IDToken.MIDDLE_NAME, "String"); @@ -175,10 +175,10 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { model = UserPropertyMapper.createClaimMapper(EMAIL_VERIFIED, "emailVerified", "email_verified", "boolean", - true, true); + true, true, true); builtins.put(EMAIL_VERIFIED, model); - ProtocolMapperModel fullName = FullNameMapper.create(FULL_NAME, true, true, true); + ProtocolMapperModel fullName = FullNameMapper.create(FULL_NAME, true, true, true, true); builtins.put(FULL_NAME, fullName); ProtocolMapperModel address = AddressMapper.createAddressMapper(); @@ -187,19 +187,19 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { model = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, KerberosConstants.GSS_DELEGATION_CREDENTIAL, KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String", - true, false); + true, false, true); builtins.put(KerberosConstants.GSS_DELEGATION_CREDENTIAL, model); - model = UserRealmRoleMappingMapper.create(null, REALM_ROLES, "realm_access.roles", true, false, true); + model = UserRealmRoleMappingMapper.create(null, REALM_ROLES, "realm_access.roles", true, false, true, true); builtins.put(REALM_ROLES, model); - model = UserClientRoleMappingMapper.create(null, null, CLIENT_ROLES, "resource_access.${client_id}.roles", true, false, true); + model = UserClientRoleMappingMapper.create(null, null, CLIENT_ROLES, "resource_access.${client_id}.roles", true, false, true, true); builtins.put(CLIENT_ROLES, model); - model = AudienceResolveProtocolMapper.createClaimMapper(AUDIENCE_RESOLVE); + model = AudienceResolveProtocolMapper.createClaimMapper(AUDIENCE_RESOLVE, true, true); builtins.put(AUDIENCE_RESOLVE, model); - model = AllowedWebOriginsProtocolMapper.createClaimMapper(ALLOWED_WEB_ORIGINS); + model = AllowedWebOriginsProtocolMapper.createClaimMapper(ALLOWED_WEB_ORIGINS, true, true); builtins.put(ALLOWED_WEB_ORIGINS, model); builtins.put(IMPERSONATOR_ID.getDisplayName(), UserSessionNoteMapper.createUserSessionNoteMapper(IMPERSONATOR_ID)); @@ -207,14 +207,14 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { model = UserAttributeMapper.createClaimMapper(UPN, "username", "upn", "String", - true, true); + true, true, true); builtins.put(UPN, model); - model = UserRealmRoleMappingMapper.create(null, GROUPS, GROUPS, true, true, true); + model = UserRealmRoleMappingMapper.create(null, GROUPS, GROUPS, true, true, true, true); builtins.put(GROUPS, model); if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { - model = AcrProtocolMapper.create(ACR, true, true); + model = AcrProtocolMapper.create(ACR, true, true, true); builtins.put(ACR, model); } } @@ -223,7 +223,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { ProtocolMapperModel model = UserAttributeMapper.createClaimMapper(name, attrName, claimName, type, - true, true, false); + true, true, true, false); builtins.put(name, model); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 460624ad50..203c888b3f 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -61,6 +61,7 @@ import org.keycloak.models.utils.SessionExpirationUtils; import org.keycloak.models.utils.RoleUtils; import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.ProtocolMapperUtils; +import org.keycloak.protocol.oidc.mappers.TokenIntrospectionTokenMapper; import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper; import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenResponseMapper; import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper; @@ -785,6 +786,17 @@ public class TokenManager { }); } + public AccessToken transformIntrospectionAccessToken(KeycloakSession session, AccessToken token, + UserSessionModel userSession, ClientSessionContext clientSessionCtx) { + return ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx, mapper -> mapper.getValue() instanceof TokenIntrospectionTokenMapper) + .collect(new TokenCollector(token) { + @Override + protected AccessToken applyMapper(AccessToken token, Map.Entry mapper) { + return ((TokenIntrospectionTokenMapper) mapper.getValue()).transformIntrospectionToken(token, mapper.getKey(), session, userSession, clientSessionCtx); + } + }); + } + public Map generateUserInfoClaims(AccessToken userInfo, UserModel userModel) { Map claims = new HashMap<>(); claims.put("sub", userInfo.getSubject() == null? userModel.getId() : userInfo.getSubject()); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java index 48ee7493f1..3762e1e16e 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java @@ -24,7 +24,6 @@ import org.keycloak.TokenVerifier; import org.keycloak.common.ClientConnection; import org.keycloak.common.Profile; import org.keycloak.common.VerificationException; -import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.crypto.ContentEncryptionProvider; import org.keycloak.crypto.CekManagementProvider; import org.keycloak.crypto.KeyWrapper; @@ -48,7 +47,6 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; -import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.TokenManager; @@ -59,15 +57,11 @@ import org.keycloak.services.Urls; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.context.UserInfoRequestContext; import org.keycloak.services.managers.AppAuthManager; -import org.keycloak.services.managers.AuthenticationManager; -import org.keycloak.services.managers.UserSessionCrossDCManager; -import org.keycloak.services.managers.UserSessionManager; import org.keycloak.services.resources.Cors; import org.keycloak.services.util.DPoPUtil; import org.keycloak.services.util.DefaultClientSessionContext; import org.keycloak.services.util.MtlsHoKTokenUtil; -import org.keycloak.sessions.AuthenticationSessionModel; -import org.keycloak.sessions.RootAuthenticationSessionModel; +import org.keycloak.services.util.UserSessionUtil; import org.keycloak.util.JsonSerialization; import org.keycloak.util.TokenUtil; import org.keycloak.utils.MediaType; @@ -145,15 +139,15 @@ public class UserInfoEndpoint { authorization(accessToken); try { - + String contentType = headers.getHeaderString(HttpHeaders.CONTENT_TYPE); jakarta.ws.rs.core.MediaType mediaType = jakarta.ws.rs.core.MediaType.valueOf(contentType); - + if (jakarta.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED_TYPE.isCompatible(mediaType)) { MultivaluedMap formParams = request.getDecodedFormParameters(); checkAccessTokenDuplicated(formParams); accessToken = formParams.getFirst(OAuth2Constants.ACCESS_TOKEN); - authorization(accessToken); + authorization(accessToken); } } catch (IllegalArgumentException e) { // not application/x-www-form-urlencoded, ignore @@ -229,7 +223,7 @@ public class UserInfoEndpoint { throw error.invalidToken("Client disabled"); } - UserSessionModel userSession = findValidSession(token, event, clientModel); + UserSessionModel userSession = UserSessionUtil.findValidSession(session, realm, token, event, clientModel); UserModel userModel = userSession.getUser(); if (userModel == null) { @@ -350,73 +344,6 @@ public class UserInfoEndpoint { return encryptedToken; } - private UserSessionModel createTransientSessionForClient(AccessToken token, ClientModel client) { - // create a transient session - UserModel user = TokenManager.lookupUserFromStatelessToken(session, realm, token); - if (user == null) { - throw error.invalidToken("User not found"); - } - UserSessionModel userSession = new UserSessionManager(session).createUserSession(KeycloakModelUtils.generateId(), realm, user, user.getUsername(), clientConnection.getRemoteAddr(), - ServiceAccountConstants.CLIENT_AUTH, false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT); - // attach an auth session for the client - RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().createRootAuthenticationSession(realm); - AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client); - authSession.setAuthenticatedUser(userSession.getUser()); - authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName())); - AuthenticationManager.setClientScopesInSession(authSession); - TokenManager.attachAuthenticationSession(session, userSession, authSession); - return userSession; - } - - private UserSessionModel findValidSession(AccessToken token, EventBuilder event, ClientModel client) { - if (token.getSessionState() == null) { - return createTransientSessionForClient(token, client); - } - - UserSessionModel userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, token.getSessionState(), false, client.getId()); - UserSessionModel offlineUserSession = null; - if (AuthenticationManager.isSessionValid(realm, userSession)) { - checkTokenIssuedAt(token, userSession, event, client); - event.session(userSession); - return userSession; - } else { - offlineUserSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, token.getSessionState(), true, client.getId()); - if (AuthenticationManager.isOfflineSessionValid(realm, offlineUserSession)) { - checkTokenIssuedAt(token, offlineUserSession, event, client); - event.session(offlineUserSession); - return offlineUserSession; - } - } - - if (userSession == null && offlineUserSession == null) { - event.error(Errors.USER_SESSION_NOT_FOUND); - throw error.invalidToken("User session not found or doesn't have client attached on it"); - } - - if (userSession != null) { - event.session(userSession); - } else { - event.session(offlineUserSession); - } - - event.error(Errors.SESSION_EXPIRED); - throw error.invalidToken("Session expired"); - } - - private void checkTokenIssuedAt(AccessToken token, UserSessionModel userSession, EventBuilder event, ClientModel client) { - if (token.isIssuedBeforeSessionStart(userSession.getStarted())) { - event.error(Errors.INVALID_TOKEN); - throw error.invalidToken("Stale token"); - } - - AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId()); - if (token.isIssuedBeforeSessionStart(clientSession.getStarted())) { - event.error(Errors.INVALID_TOKEN); - throw error.invalidToken("Stale token"); - } - } - private void checkAccessTokenDuplicated(MultivaluedMap formParams) { // If access_token is not provided, error is thrown in issueUserInfo(). // Only checks duplication of access token parameter in this function. diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java index 0ca3af4f2f..42f4e32f7f 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractOIDCProtocolMapper.java @@ -106,6 +106,17 @@ public abstract class AbstractOIDCProtocolMapper implements ProtocolMapper { return accessTokenResponse; } + public AccessToken transformIntrospectionToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionContext clientSessionCtx) { + + if (!OIDCAttributeMapperHelper.includeInIntrospection(mappingModel)){ + return token; + } + + setClaim(token, mappingModel, userSession, session, clientSessionCtx); + return token; + } + /** * Intended to be overridden in {@link ProtocolMapper} implementations to add claims to an token. * @param token diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java index ef3b9bdcf1..fd9389644b 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java @@ -14,6 +14,7 @@ import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.AccessToken; import org.keycloak.representations.IDToken; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -22,7 +23,7 @@ import java.util.List; * * @author Martin Hardselius */ -public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { +public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { public static final String PROVIDER_ID_SUFFIX = "-pairwise-sub-mapper"; public abstract String getIdPrefix(); @@ -75,6 +76,12 @@ public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapp return token; } + @Override + public AccessToken transformIntrospectionToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionContext clientSessionCtx) { + setAccessTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSessionCtx.getClientSession().getClient(), mappingModel), userSession.getUser().getId())); + return token; + } + @Override public AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionContext clientSessionCtx) { setUserInfoTokenSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSessionCtx.getClientSession().getClient(), mappingModel), userSession.getUser().getId())); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java index 831804baa6..2f37151391 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java @@ -38,7 +38,7 @@ import java.util.stream.Collectors; * * @author Thomas Darimont */ -abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { +abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { @Override public int getPriority() { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AcrProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AcrProtocolMapper.java index 3da2a5823d..1b02130546 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AcrProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AcrProtocolMapper.java @@ -42,7 +42,7 @@ import org.keycloak.services.managers.AuthenticationManager; /** * @author Marek Posolda */ -public class AcrProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, EnvironmentDependentProviderFactory { +public class AcrProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, TokenIntrospectionTokenMapper, EnvironmentDependentProviderFactory { private static final Logger logger = Logger.getLogger(AcrProtocolMapper.class); @@ -87,7 +87,7 @@ public class AcrProtocolMapper extends AbstractOIDCProtocolMapper implements OID token.setAcr(acr); } - public static ProtocolMapperModel create(String name, boolean accessToken, boolean idToken) { + public static ProtocolMapperModel create(String name, boolean accessToken, boolean idToken, boolean introspectionEndpoint) { ProtocolMapperModel mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(PROVIDER_ID); @@ -95,6 +95,7 @@ public class AcrProtocolMapper extends AbstractOIDCProtocolMapper implements OID Map config = new HashMap<>(); if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + if (introspectionEndpoint) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, "true"); mapper.setConfig(config); return mapper; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java index 9d61b81ff3..a35a9360cf 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AddressMapper.java @@ -35,7 +35,7 @@ import java.util.Map; * @author Bill Burke * @version $Revision: 1 $ */ -public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { +public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList(); @@ -69,10 +69,10 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc public static final String PROVIDER_ID = "oidc-address-mapper"; public static ProtocolMapperModel createAddressMapper() { - return createAddressMapper(true, true, true); + return createAddressMapper(true, true, true, true); } - public static ProtocolMapperModel createAddressMapper(boolean idToken, boolean accessToken, boolean userInfo) { + public static ProtocolMapperModel createAddressMapper(boolean idToken, boolean accessToken, boolean userInfo, boolean introspectionEndpoint) { Map config; ProtocolMapperModel address = new ProtocolMapperModel(); address.setName("address"); @@ -82,6 +82,7 @@ public class AddressMapper extends AbstractOIDCProtocolMapper implements OIDCAcc config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, Boolean.toString(accessToken)); config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, Boolean.toString(idToken)); config.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, Boolean.toString(userInfo)); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, Boolean.toString(introspectionEndpoint)); config.put(getModelPropertyName(STREET), STREET); config.put(getModelPropertyName(AddressClaimSet.LOCALITY), AddressClaimSet.LOCALITY); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AllowedWebOriginsProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AllowedWebOriginsProtocolMapper.java index 2ffe70cc6d..5d8a7d40b4 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AllowedWebOriginsProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AllowedWebOriginsProtocolMapper.java @@ -19,7 +19,9 @@ package org.keycloak.protocol.oidc.mappers; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.keycloak.models.ClientModel; @@ -32,18 +34,24 @@ import org.keycloak.protocol.oidc.utils.WebOriginsUtils; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.AccessToken; +import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN; +import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION; + /** * Protocol mapper to add allowed web origins to the access token to the 'allowed-origins' claim * * @author Marek Posolda */ -public class AllowedWebOriginsProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { +public class AllowedWebOriginsProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList(); public static final String PROVIDER_ID = "oidc-allowed-origins-mapper"; + static { + OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, AllowedWebOriginsProtocolMapper.class); + } public List getConfigProperties() { return configProperties; @@ -72,23 +80,72 @@ public class AllowedWebOriginsProtocolMapper extends AbstractOIDCProtocolMapper @Override public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionContext clientSessionCtx) { + + if (!includeInAccessToken(mappingModel)){ + return token; + } + setWebOrigin(token, session, clientSessionCtx); + return token; + } + + private boolean includeInAccessToken(ProtocolMapperModel mappingModel) { + String includeInAccessToken = mappingModel.getConfig().get(INCLUDE_IN_ACCESS_TOKEN); + + // Backwards compatibility + if (includeInAccessToken == null) { + return true; + } + + return "true".equals(includeInAccessToken); + } + + @Override + public AccessToken transformIntrospectionToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionContext clientSessionCtx) { + if (!includeInIntrospection(mappingModel)) { + return token; + } + setWebOrigin(token, session, clientSessionCtx); + return token; + } + + private boolean includeInIntrospection(ProtocolMapperModel mappingModel) { + String includeInIntrospection = mappingModel.getConfig().get(INCLUDE_IN_INTROSPECTION); + + // Backwards compatibility + if (includeInIntrospection == null) { + return true; + } + + return "true".equals(includeInIntrospection); + } + + private void setWebOrigin(AccessToken token, KeycloakSession session, ClientSessionContext clientSessionCtx) { ClientModel client = clientSessionCtx.getClientSession().getClient(); Set allowedOrigins = client.getWebOrigins(); if (allowedOrigins != null && !allowedOrigins.isEmpty()) { token.setAllowedOrigins(WebOriginsUtils.resolveValidWebOrigins(session, client)); } - - return token; } - - public static ProtocolMapperModel createClaimMapper(String name) { + public static ProtocolMapperModel createClaimMapper(String name, boolean accessToken, boolean introspectionEndpoint) { ProtocolMapperModel mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(PROVIDER_ID); mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - mapper.setConfig(Collections.emptyMap()); + Map config = new HashMap<>(); + if (accessToken) { + config.put(INCLUDE_IN_ACCESS_TOKEN, "true"); + } else { + config.put(INCLUDE_IN_ACCESS_TOKEN, "false"); + } + if (introspectionEndpoint) { + config.put(INCLUDE_IN_INTROSPECTION, "true"); + } else { + config.put(INCLUDE_IN_INTROSPECTION, "false"); + } + mapper.setConfig(config); return mapper; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java index 80914bcecf..c65e321cf3 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceProtocolMapper.java @@ -33,7 +33,7 @@ import org.keycloak.representations.IDToken; /** * @author Marek Posolda */ -public class AudienceProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper { +public class AudienceProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList<>(); @@ -115,7 +115,7 @@ public class AudienceProtocolMapper extends AbstractOIDCProtocolMapper implement public static ProtocolMapperModel createClaimMapper(String name, String includedClientAudience, String includedCustomAudience, - boolean accessToken, boolean idToken) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { ProtocolMapperModel mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(PROVIDER_ID); @@ -131,6 +131,7 @@ public class AudienceProtocolMapper extends AbstractOIDCProtocolMapper implement if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + if (introspectionEndpoint) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, "true"); mapper.setConfig(config); return mapper; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceResolveProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceResolveProtocolMapper.java index b1e1b861bc..f7cd53051c 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceResolveProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AudienceResolveProtocolMapper.java @@ -19,6 +19,7 @@ package org.keycloak.protocol.oidc.mappers; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,16 +33,21 @@ import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.AccessToken; import org.keycloak.utils.RoleResolveUtil; +import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN; +import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION; + /** * Protocol mapper, which adds all client_ids of "allowed" clients to the audience field of the token. Allowed client means the client * for which user has at least one client role * * @author Marek Posolda */ -public class AudienceResolveProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper { +public class AudienceResolveProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList(); - + static { + OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, AudienceResolveProtocolMapper.class); + } public static final String PROVIDER_ID = "oidc-audience-resolve-mapper"; @@ -79,6 +85,46 @@ public class AudienceResolveProtocolMapper extends AbstractOIDCProtocolMapper im @Override public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionContext clientSessionCtx) { + if (!includeInAccessToken(mappingModel)){ + return token; + } + setAudience(token, clientSessionCtx, session); + return token; + } + + private boolean includeInAccessToken(ProtocolMapperModel mappingModel) { + String includeInAccessToken = mappingModel.getConfig().get(INCLUDE_IN_ACCESS_TOKEN); + + // Backwards compatibility + if (includeInAccessToken == null) { + return true; + } + + return "true".equals(includeInAccessToken); + } + + @Override + public AccessToken transformIntrospectionToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionContext clientSessionCtx) { + if (!includeInIntrospection(mappingModel)) { + return token; + } + setAudience(token, clientSessionCtx, session); + return token; + } + + private boolean includeInIntrospection(ProtocolMapperModel mappingModel) { + String includeInIntrospection = mappingModel.getConfig().get(INCLUDE_IN_INTROSPECTION); + + // Backwards compatibility + if (includeInIntrospection == null) { + return true; + } + + return "true".equals(includeInIntrospection); + } + + private void setAudience(AccessToken token, ClientSessionContext clientSessionCtx, KeycloakSession session) { String clientId = clientSessionCtx.getClientSession().getClient().getClientId(); for (Map.Entry entry : RoleResolveUtil.getAllResolvedClientRoles(session, clientSessionCtx).entrySet()) { @@ -92,16 +138,25 @@ public class AudienceResolveProtocolMapper extends AbstractOIDCProtocolMapper im token.addAudience(entry.getKey()); } } - - return token; } - public static ProtocolMapperModel createClaimMapper(String name) { + public static ProtocolMapperModel createClaimMapper(String name, boolean accessToken, boolean introspectionEndpoint) { ProtocolMapperModel mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(PROVIDER_ID); mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); - mapper.setConfig(Collections.emptyMap()); + Map config = new HashMap<>(); + if (accessToken) { + config.put(INCLUDE_IN_ACCESS_TOKEN, "true"); + } else { + config.put(INCLUDE_IN_ACCESS_TOKEN, "false"); + } + if (introspectionEndpoint) { + config.put(INCLUDE_IN_INTROSPECTION, "true"); + } else { + config.put(INCLUDE_IN_INTROSPECTION, "false"); + } + mapper.setConfig(config); return mapper; } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ClaimsParameterTokenMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ClaimsParameterTokenMapper.java index a65ca1ff28..45a557b595 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ClaimsParameterTokenMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ClaimsParameterTokenMapper.java @@ -110,16 +110,16 @@ public class ClaimsParameterTokenMapper extends AbstractOIDCProtocolMapper imple fullNameMapper.setClaim(token, mappingModel, userSession); } else if (i.equals(IDToken.GIVEN_NAME)) { UserAttributeMapper userPropertyMapper = new UserAttributeMapper(); - userPropertyMapper.setClaim(token, UserAttributeMapper.createClaimMapper("requested firstName", "firstName", IDToken.GIVEN_NAME, "String", false, true), userSession); + userPropertyMapper.setClaim(token, UserAttributeMapper.createClaimMapper("requested firstName", "firstName", IDToken.GIVEN_NAME, "String", false, true, false), userSession); } else if (i.equals(IDToken.FAMILY_NAME)) { UserAttributeMapper userPropertyMapper = new UserAttributeMapper(); - userPropertyMapper.setClaim(token, UserAttributeMapper.createClaimMapper("requested lastName", "lastName", IDToken.FAMILY_NAME, "String", false, true), userSession); + userPropertyMapper.setClaim(token, UserAttributeMapper.createClaimMapper("requested lastName", "lastName", IDToken.FAMILY_NAME, "String", false, true, false), userSession); } else if (i.equals(IDToken.PREFERRED_USERNAME)) { UserAttributeMapper userPropertyMapper = new UserAttributeMapper(); - userPropertyMapper.setClaim(token, UserAttributeMapper.createClaimMapper("requested username", "username", IDToken.PREFERRED_USERNAME, "String", false, true), userSession); + userPropertyMapper.setClaim(token, UserAttributeMapper.createClaimMapper("requested username", "username", IDToken.PREFERRED_USERNAME, "String", false, true, false), userSession); } else if (i.equals(IDToken.EMAIL)) { UserAttributeMapper userPropertyMapper = new UserAttributeMapper(); - userPropertyMapper.setClaim(token, UserAttributeMapper.createClaimMapper("requested email", "email", IDToken.EMAIL, "String", false, true), userSession); + userPropertyMapper.setClaim(token, UserAttributeMapper.createClaimMapper("requested email", "email", IDToken.EMAIL, "String", false, true, false), userSession); } }); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ClaimsParameterWithValueIdTokenMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ClaimsParameterWithValueIdTokenMapper.java index fa07c5ab6e..92f6d3c9af 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ClaimsParameterWithValueIdTokenMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ClaimsParameterWithValueIdTokenMapper.java @@ -123,7 +123,7 @@ public class ClaimsParameterWithValueIdTokenMapper extends AbstractOIDCProtocolM } HardcodedClaim hardcodedClaimMapper = new HardcodedClaim(); - hardcodedClaimMapper.setClaim(token, HardcodedClaim.create("hard", claimName, claim, "String", false, true), userSession); + hardcodedClaimMapper.setClaim(token, HardcodedClaim.create("hard", claimName, claim, "String", false, true, false), userSession); } public static ProtocolMapperModel createMapper(String name, String attributeValue, boolean idToken) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java index 13dccc5114..3f3e73c83e 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/FullNameMapper.java @@ -37,7 +37,7 @@ import java.util.Optional; * @author Bill Burke * @version $Revision: 1 $ */ -public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { +public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList<>(); @@ -83,7 +83,7 @@ public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc } } - public static ProtocolMapperModel create(String name, boolean accessToken, boolean idToken, boolean userInfo) { + public static ProtocolMapperModel create(String name, boolean accessToken, boolean idToken, boolean userInfo, boolean introspectionEndpoint) { ProtocolMapperModel mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(PROVIDER_ID); @@ -92,6 +92,7 @@ public class FullNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); if (userInfo) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + if (introspectionEndpoint) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, "true"); mapper.setConfig(config); return mapper; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java index 685aed7ca3..27b7186171 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java @@ -39,7 +39,7 @@ import java.util.stream.Collectors; * @author Bill Burke * @version $Revision: 1 $ */ -public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { +public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList(); @@ -105,9 +105,9 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements } public static ProtocolMapperModel create(String name, - String tokenClaimName, - boolean consentRequired, String consentText, - boolean accessToken, boolean idToken) { + String tokenClaimName, + boolean consentRequired, String consentText, + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { ProtocolMapperModel mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(PROVIDER_ID); @@ -116,8 +116,9 @@ public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName); if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + if (introspectionEndpoint) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, "true"); mapper.setConfig(config); - + return mapper; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java index 707dc1f78e..e36a4f2be1 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedClaim.java @@ -38,7 +38,7 @@ import java.util.Map; * @version $Revision: 1 $ */ public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, - OIDCAccessTokenResponseMapper { + OIDCAccessTokenResponseMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList(); @@ -94,7 +94,7 @@ public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAc @Override protected void setClaim(AccessTokenResponse accessTokenResponse, ProtocolMapperModel mappingModel, UserSessionModel userSession, - KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) { + KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) { String attributeValue = mappingModel.getConfig().get(CLAIM_VALUE); if (attributeValue == null) return; @@ -102,9 +102,9 @@ public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAc } public static ProtocolMapperModel create(String name, - String hardcodedName, - String hardcodedValue, String claimType, - boolean accessToken, boolean idToken) { + String hardcodedName, + String hardcodedValue, String claimType, + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { ProtocolMapperModel mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(PROVIDER_ID); @@ -115,6 +115,7 @@ public class HardcodedClaim extends AbstractOIDCProtocolMapper implements OIDCAc config.put(OIDCAttributeMapperHelper.JSON_TYPE, claimType); if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + if (introspectionEndpoint) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, "true"); mapper.setConfig(config); return mapper; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java index 10762d497d..7f1583900c 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/HardcodedRole.java @@ -40,7 +40,7 @@ import java.util.Map; * @author Bill Burke * @version $Revision: 1 $ */ -public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, UserInfoTokenMapper { +public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList<>(); @@ -104,6 +104,14 @@ public class HardcodedRole extends AbstractOIDCProtocolMapper implements OIDCAcc return token; } + @Override + public AccessToken transformIntrospectionToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionContext clientSessionCtx) { + // the mapper is always executed and then other role mappers decide if the claims are really set to the token + setClaim(token, mappingModel, userSession, session, clientSessionCtx); + return token; + } + @Override protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession session, ClientSessionContext clientSessionCtx) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java index 2375e5e6ed..c15037cc82 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCAttributeMapperHelper.java @@ -31,10 +31,17 @@ import org.keycloak.representations.IDToken; import org.keycloak.services.ServicesLogger; import org.keycloak.util.JsonSerialization; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -60,6 +67,10 @@ public class OIDCAttributeMapperHelper { public static final String INCLUDE_IN_USERINFO_LABEL = "includeInUserInfo.label"; public static final String INCLUDE_IN_USERINFO_HELP_TEXT = "includeInUserInfo.tooltip"; + public static final String INCLUDE_IN_INTROSPECTION = "introspection.token.claim"; + public static final String INCLUDE_IN_INTROSPECTION_LABEL = "includeInIntrospection.label"; + public static final String INCLUDE_IN_INTROSPECTION_HELP_TEXT = "includeInIntrospection.tooltip"; + private static final Logger logger = Logger.getLogger(OIDCAttributeMapperHelper.class); /** @@ -230,7 +241,7 @@ public class OIDCAttributeMapperHelper { if (attributeValue instanceof String) return Boolean.valueOf((String) attributeValue); return null; } - + private static JsonNode getJsonNode(Object attributeValue) { if (attributeValue instanceof JsonNode){ return (JsonNode) attributeValue; @@ -259,7 +270,7 @@ public class OIDCAttributeMapperHelper { } private static void mapClaim(T token, ProtocolMapperModel mappingModel, Object attributeValue, - Map> setters, Map jsonObject) { + Map> setters, Map jsonObject) { attributeValue = mapAttributeValue(mappingModel, attributeValue); if (attributeValue == null) { return; @@ -317,16 +328,16 @@ public class OIDCAttributeMapperHelper { public static ProtocolMapperModel createClaimMapper(String name, String userAttribute, String tokenClaimName, String claimType, - boolean accessToken, boolean idToken, + boolean accessToken, boolean idToken, boolean introspectionEndpoint, String mapperId) { - return createClaimMapper(name, userAttribute,tokenClaimName, claimType, accessToken, idToken, true, mapperId); + return createClaimMapper(name, userAttribute, tokenClaimName, claimType, accessToken, idToken, true, introspectionEndpoint, mapperId); } public static ProtocolMapperModel createClaimMapper(String name, - String userAttribute, - String tokenClaimName, String claimType, - boolean accessToken, boolean idToken, boolean userinfo, - String mapperId) { + String userAttribute, + String tokenClaimName, String claimType, + boolean accessToken, boolean idToken, boolean userinfo, boolean introspectionEndpoint, + String mapperId) { ProtocolMapperModel mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(mapperId); @@ -338,6 +349,7 @@ public class OIDCAttributeMapperHelper { if (accessToken) config.put(INCLUDE_IN_ACCESS_TOKEN, "true"); if (idToken) config.put(INCLUDE_IN_ID_TOKEN, "true"); if (userinfo) config.put(INCLUDE_IN_USERINFO, "true"); + if (introspectionEndpoint) config.put(INCLUDE_IN_INTROSPECTION, "true"); mapper.setConfig(config); return mapper; } @@ -369,6 +381,17 @@ public class OIDCAttributeMapperHelper { return "true".equals(includeInUserInfo); } + public static boolean includeInIntrospection(ProtocolMapperModel mappingModel) { + String includeInIntrospection = mappingModel.getConfig().get(INCLUDE_IN_INTROSPECTION); + + // Backwards compatibility + if (includeInIntrospection == null && includeInAccessToken(mappingModel)) { + return true; + } + + return "true".equals(includeInIntrospection); + } + public static void addAttributeConfig(List configProperties, Class protocolMapperClass) { addTokenClaimNameConfig(configProperties); addJsonTypeConfig(configProperties); @@ -441,5 +464,15 @@ public class OIDCAttributeMapperHelper { property.setHelpText(INCLUDE_IN_ACCESS_TOKEN_RESPONSE_HELP_TEXT); configProperties.add(property); } + + if (TokenIntrospectionTokenMapper.class.isAssignableFrom(protocolMapperClass)) { + ProviderConfigProperty property = new ProviderConfigProperty(); + property.setName(INCLUDE_IN_INTROSPECTION); + property.setLabel(INCLUDE_IN_INTROSPECTION_LABEL); + property.setType(ProviderConfigProperty.BOOLEAN_TYPE); + property.setDefaultValue("true"); + property.setHelpText(INCLUDE_IN_INTROSPECTION_HELP_TEXT); + configProperties.add(property); + } } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java index 4316d0ae32..7cb40f888a 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/RoleNameMapper.java @@ -40,7 +40,7 @@ import java.util.Map; * @author Bill Burke * @version $Revision: 1 $ */ -public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, UserInfoTokenMapper { +public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList<>(); @@ -111,6 +111,14 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements OIDCAc return token; } + @Override + public AccessToken transformIntrospectionToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionContext clientSessionCtx) { + // the mapper is always executed and then other role mappers decide if the claims are really set to the token + setClaim(token, mappingModel, userSession, session, clientSessionCtx); + return token; + } + @Override protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession session, ClientSessionContext clientSessionCtx) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java index 47889a4086..4ca2494864 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/ScriptBasedOIDCProtocolMapper.java @@ -46,7 +46,7 @@ import java.util.List; * @author Thomas Darimont */ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, - OIDCAccessTokenResponseMapper, EnvironmentDependentProviderFactory { + OIDCAccessTokenResponseMapper, TokenIntrospectionTokenMapper, EnvironmentDependentProviderFactory { public static final String PROVIDER_ID = "oidc-script-based-protocol-mapper"; @@ -197,14 +197,14 @@ public class ScriptBasedOIDCProtocolMapper extends AbstractOIDCProtocolMapper im public static ProtocolMapperModel create(String name, String userAttribute, String tokenClaimName, String claimType, - boolean accessToken, boolean idToken, String script, boolean multiValued) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint, String script, boolean multiValued) { ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute, tokenClaimName, claimType, - accessToken, idToken, + accessToken, idToken, introspectionEndpoint, script); mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, String.valueOf(multiValued)); - return mapper; + return mapper; } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/TokenIntrospectionTokenMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/TokenIntrospectionTokenMapper.java new file mode 100644 index 0000000000..50a10064e9 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/TokenIntrospectionTokenMapper.java @@ -0,0 +1,12 @@ +package org.keycloak.protocol.oidc.mappers; + +import org.keycloak.models.ClientSessionContext; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.representations.AccessToken; + +public interface TokenIntrospectionTokenMapper { + AccessToken transformIntrospectionToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, + UserSessionModel userSession, ClientSessionContext clientSessionCtx); +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java index 764b8e51de..c99f4da142 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java @@ -37,7 +37,7 @@ import java.util.List; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { +public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList(); @@ -106,19 +106,19 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O public static ProtocolMapperModel createClaimMapper(String name, String userAttribute, String tokenClaimName, String claimType, - boolean accessToken, boolean idToken, boolean multivalued) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint, boolean multivalued) { return createClaimMapper(name, userAttribute, tokenClaimName, claimType, - accessToken, idToken, multivalued, false); + accessToken, idToken, introspectionEndpoint, multivalued, false); } public static ProtocolMapperModel createClaimMapper(String name, String userAttribute, String tokenClaimName, String claimType, - boolean accessToken, boolean idToken, + boolean accessToken, boolean idToken, boolean introspectionEndpoint, boolean multivalued, boolean aggregateAttrs) { ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute, tokenClaimName, claimType, - accessToken, idToken, + accessToken, idToken, introspectionEndpoint, PROVIDER_ID); if (multivalued) { @@ -134,8 +134,8 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O public static ProtocolMapperModel createClaimMapper(String name, String userAttribute, String tokenClaimName, String claimType, - boolean accessToken, boolean idToken) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { return createClaimMapper(name, userAttribute, tokenClaimName, claimType, - accessToken, idToken, false, false); + accessToken, idToken, introspectionEndpoint, false, false); } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java index ca57bd6e86..77c9532f20 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserClientRoleMappingMapper.java @@ -135,18 +135,18 @@ public class UserClientRoleMappingMapper extends AbstractUserRoleMappingMapper { public static ProtocolMapperModel create(String clientId, String clientRolePrefix, String name, String tokenClaimName, - boolean accessToken, boolean idToken) { - return create(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken, false); + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { + return create(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken, introspectionEndpoint, false); } public static ProtocolMapperModel create(String clientId, String clientRolePrefix, String name, String tokenClaimName, - boolean accessToken, boolean idToken, boolean multiValued) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint, boolean multiValued) { ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo", tokenClaimName, "String", - accessToken, idToken, false, + accessToken, idToken, false, introspectionEndpoint, PROVIDER_ID); mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, String.valueOf(multiValued)); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java index 863b9835a7..94b0bc37c9 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserPropertyMapper.java @@ -35,7 +35,7 @@ import java.util.List; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { +public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList(); static { @@ -89,10 +89,10 @@ public class UserPropertyMapper extends AbstractOIDCProtocolMapper implements OI public static ProtocolMapperModel createClaimMapper(String name, String userAttribute, String tokenClaimName, String claimType, - boolean accessToken, boolean idToken) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { return OIDCAttributeMapperHelper.createClaimMapper(name, userAttribute, tokenClaimName, claimType, - accessToken, idToken, + accessToken, idToken, introspectionEndpoint, PROVIDER_ID); } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java index 1f817d6124..089426023e 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserRealmRoleMappingMapper.java @@ -100,18 +100,18 @@ public class UserRealmRoleMappingMapper extends AbstractUserRoleMappingMapper { public static ProtocolMapperModel create(String realmRolePrefix, String name, - String tokenClaimName, boolean accessToken, boolean idToken) { + String tokenClaimName, boolean accessToken, boolean idToken, boolean introspectionEndpoint) { - return create(realmRolePrefix, name, tokenClaimName, accessToken, idToken, false); + return create(realmRolePrefix, name, tokenClaimName, accessToken, idToken, introspectionEndpoint, false); } public static ProtocolMapperModel create(String realmRolePrefix, String name, - String tokenClaimName, boolean accessToken, boolean idToken, boolean multiValued) { + String tokenClaimName, boolean accessToken, boolean idToken, boolean introspectionEndpoint, boolean multiValued) { ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper(name, "foo", - tokenClaimName, "String", - accessToken, idToken, false, - PROVIDER_ID); + tokenClaimName, "String", + accessToken, idToken, false, introspectionEndpoint, + PROVIDER_ID); mapper.getConfig().put(ProtocolMapperUtils.MULTIVALUED, String.valueOf(multiValued)); mapper.getConfig().put(ProtocolMapperUtils.USER_MODEL_REALM_ROLE_MAPPING_ROLE_PREFIX, realmRolePrefix); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java index b7be2cb291..cdc4a0f9af 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserSessionNoteMapper.java @@ -38,7 +38,7 @@ import java.util.Map; * * @author Marek Posolda */ -public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, OIDCAccessTokenResponseMapper, UserInfoTokenMapper { +public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, OIDCAccessTokenResponseMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper { private static final List configProperties = new ArrayList<>(); @@ -90,7 +90,7 @@ public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements @Override protected void setClaim(AccessTokenResponse accessTokenResponse, ProtocolMapperModel mappingModel, UserSessionModel userSession, - KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) { + KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) { String noteName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_SESSION_NOTE); String noteValue = userSession.getNote(noteName); @@ -101,14 +101,14 @@ public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements public static ProtocolMapperModel createClaimMapper(String name, String userSessionNote, String tokenClaimName, String jsonType, - boolean accessToken, boolean idToken) { - return createClaimMapper(name, userSessionNote, tokenClaimName, jsonType, accessToken, idToken, false); + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { + return createClaimMapper(name, userSessionNote, tokenClaimName, jsonType, accessToken, idToken, false, introspectionEndpoint); } public static ProtocolMapperModel createClaimMapper(String name, String userSessionNote, String tokenClaimName, String jsonType, - boolean accessToken, boolean idToken, boolean userInfo) { + boolean accessToken, boolean idToken, boolean userInfo, boolean introspectionEndpoint) { ProtocolMapperModel mapper = new ProtocolMapperModel(); mapper.setName(name); mapper.setProtocolMapper(PROVIDER_ID); @@ -120,6 +120,7 @@ public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); if (userInfo) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + if (introspectionEndpoint) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, "true"); mapper.setConfig(config); return mapper; } @@ -135,7 +136,7 @@ public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements userSessionNoteDescriptor.toString(), userSessionNoteDescriptor.getTokenClaim(), "String", - true, true + true, true, true ); } diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java index 8df85b93ec..d4e4520a00 100644 --- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java +++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java @@ -170,7 +170,7 @@ public class ClientManager { ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER, ServiceAccountConstants.CLIENT_ID, ServiceAccountConstants.CLIENT_ID, "String", - true, true); + true, true, true); client.addProtocolMapper(protocolMapper); } @@ -180,7 +180,7 @@ public class ClientManager { ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_HOST_PROTOCOL_MAPPER, ServiceAccountConstants.CLIENT_HOST, ServiceAccountConstants.CLIENT_HOST, "String", - true, true); + true, true, true); client.addProtocolMapper(protocolMapper); } @@ -189,7 +189,7 @@ public class ClientManager { ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_ADDRESS_PROTOCOL_MAPPER, ServiceAccountConstants.CLIENT_ADDRESS, ServiceAccountConstants.CLIENT_ADDRESS, "String", - true, true); + true, true, true); client.addProtocolMapper(protocolMapper); } } diff --git a/services/src/main/java/org/keycloak/services/util/UserSessionUtil.java b/services/src/main/java/org/keycloak/services/util/UserSessionUtil.java new file mode 100644 index 0000000000..234bc1f6a8 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/util/UserSessionUtil.java @@ -0,0 +1,97 @@ +package org.keycloak.services.util; + +import org.keycloak.common.ClientConnection; +import org.keycloak.common.constants.ServiceAccountConstants; +import org.keycloak.events.Errors; +import org.keycloak.events.EventBuilder; +import org.keycloak.models.AuthenticatedClientSessionModel; +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.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.TokenManager; +import org.keycloak.representations.AccessToken; +import org.keycloak.services.Urls; +import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.managers.UserSessionCrossDCManager; +import org.keycloak.services.managers.UserSessionManager; +import org.keycloak.sessions.AuthenticationSessionModel; +import org.keycloak.sessions.RootAuthenticationSessionModel; +import org.keycloak.utils.OAuth2Error; + +public class UserSessionUtil { + + public static UserSessionModel findValidSession(KeycloakSession session, RealmModel realm, AccessToken token, EventBuilder event, ClientModel client) { + OAuth2Error error = new OAuth2Error().json(false).realm(realm); + if (token.getSessionState() == null) { + return createTransientSessionForClient(session, realm, token, client); + } + + UserSessionModel userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, token.getSessionState(), false, client.getId()); + UserSessionModel offlineUserSession = null; + if (AuthenticationManager.isSessionValid(realm, userSession)) { + checkTokenIssuedAt(realm, token, userSession, event, client); + event.session(userSession); + return userSession; + } else { + offlineUserSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, token.getSessionState(), true, client.getId()); + if (AuthenticationManager.isOfflineSessionValid(realm, offlineUserSession)) { + checkTokenIssuedAt(realm, token, offlineUserSession, event, client); + event.session(offlineUserSession); + return offlineUserSession; + } + } + + if (userSession == null && offlineUserSession == null) { + event.error(Errors.USER_SESSION_NOT_FOUND); + throw error.invalidToken("User session not found or doesn't have client attached on it"); + } + + if (userSession != null) { + event.session(userSession); + } else { + event.session(offlineUserSession); + } + + event.error(Errors.SESSION_EXPIRED); + throw error.invalidToken("Session expired"); + } + + private static UserSessionModel createTransientSessionForClient(KeycloakSession session, RealmModel realm, AccessToken token, ClientModel client) { + OAuth2Error error = new OAuth2Error().json(false).realm(realm); + // create a transient session + UserModel user = TokenManager.lookupUserFromStatelessToken(session, realm, token); + if (user == null) { + throw error.invalidToken("User not found"); + } + ClientConnection clientConnection = session.getContext().getConnection(); + UserSessionModel userSession = new UserSessionManager(session).createUserSession(KeycloakModelUtils.generateId(), realm, user, user.getUsername(), clientConnection.getRemoteAddr(), + ServiceAccountConstants.CLIENT_AUTH, false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT); + // attach an auth session for the client + RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().createRootAuthenticationSession(realm); + AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client); + authSession.setAuthenticatedUser(userSession.getUser()); + authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName())); + AuthenticationManager.setClientScopesInSession(authSession); + TokenManager.attachAuthenticationSession(session, userSession, authSession); + return userSession; + } + + private static void checkTokenIssuedAt(RealmModel realm, AccessToken token, UserSessionModel userSession, EventBuilder event, ClientModel client) { + OAuth2Error error = new OAuth2Error().json(false).realm(realm); + if (token.isIssuedBeforeSessionStart(userSession.getStarted())) { + event.error(Errors.INVALID_TOKEN); + throw error.invalidToken("Stale token"); + } + + AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId()); + if (token.isIssuedBeforeSessionStart(clientSession.getStarted())) { + event.error(Errors.INVALID_TOKEN); + throw error.invalidToken("Stale token"); + } + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java index 8c1fb0a725..114198fbea 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java @@ -817,7 +817,7 @@ public class TestingResourceProvider implements RealmResourceProvider { clientScopeModel.setIncludeInTokenScope(true); // Add audience protocol mapper - ProtocolMapperModel audienceMapper = AudienceProtocolMapper.createClaimMapper("Audience for " + clientId, clientId, null,true, false); + ProtocolMapperModel audienceMapper = AudienceProtocolMapper.createClaimMapper("Audience for " + clientId, clientId, null,true, false, true ); clientScopeModel.addProtocolMapper(audienceMapper); return clientScopeModel.getId(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerSubMatchIntrospectionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerSubMatchIntrospectionTest.java index 2a3603f69d..163c13f797 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerSubMatchIntrospectionTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerSubMatchIntrospectionTest.java @@ -21,12 +21,12 @@ public class KcOidcBrokerSubMatchIntrospectionTest extends AbstractBrokerTest { @Override public List createConsumerClients() { List clients = new ArrayList<>(super.createConsumerClients()); - + clients.add(ClientBuilder.create().clientId("consumer-client") .publicClient() .redirectUris(getConsumerRoot() + "/auth/realms/master/app/auth/*") .publicClient().build()); - + return clients; } @@ -36,14 +36,14 @@ public class KcOidcBrokerSubMatchIntrospectionTest extends AbstractBrokerTest { List mappers = new ArrayList<>(); ProtocolMapperRepresentation hardcodedClaim = createHardcodedClaim("sub-override", "sub", "overriden", - "String", false, false); - + "String", false, false, false); + hardcodedClaim.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, Boolean.TRUE.toString()); - + mappers.add(hardcodedClaim); - + clients.get(0).setProtocolMappers(mappers); - + return clients; } }; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTest.java index b1a4fe79c0..7cf72ac02d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTest.java @@ -100,7 +100,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { IdentityProviderMapperRepresentation attrMapper2 = new IdentityProviderMapperRepresentation(); attrMapper2.setName("user-role-mapper"); attrMapper2.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID); - attrMapper2.setConfig(ImmutableMap.builder() + attrMapper2.setConfig(ImmutableMap.builder() .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put("external.role", ROLE_USER) .put("role", ROLE_USER) @@ -114,7 +114,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { IdentityProviderMapperRepresentation friendlyManagerMapper = new IdentityProviderMapperRepresentation(); friendlyManagerMapper.setName("friendly-manager-role-mapper"); friendlyManagerMapper.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID); - friendlyManagerMapper.setConfig(ImmutableMap.builder() + friendlyManagerMapper.setConfig(ImmutableMap.builder() .put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString()) .put("external.role", ROLE_FRIENDLY_MANAGER) .put("role", ROLE_FRIENDLY_MANAGER) @@ -197,7 +197,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { logInWithBroker(bc); - waitForPage(driver, loginIsDenied? "We are sorry..." : "update account information", false); + waitForPage(driver, loginIsDenied ? "We are sorry..." : "update account information", false); if (loginIsDenied) { return; } @@ -243,7 +243,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { ClientRepresentation brokerApp = clients.findByClientId("brokerapp").get(0); IdentityProviderResource identityProviderResource = getIdentityProviderResource(); - clients.get(brokerApp.getId()).getProtocolMappers().createMapper(createHardcodedClaim("hard-coded", "hard-coded", "hard-coded", "String", true, true)).close(); + clients.get(brokerApp.getId()).getProtocolMappers().createMapper(createHardcodedClaim("hard-coded", "hard-coded", "hard-coded", "String", true, true, true)).close(); IdentityProviderMapperRepresentation hardCodedSessionNoteMapper = new IdentityProviderMapperRepresentation(); @@ -445,7 +445,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { RealmResource realm = adminClient.realm(bc.providerRealmName()); ClientRepresentation rep = realm.clients().findByClientId(BrokerTestConstants.CLIENT_ID).get(0); ClientResource clientResource = realm.clients().get(rep.getId()); - ProtocolMapperRepresentation hardCodedAzp = createHardcodedClaim("hard", "azp", "invalid-azp", ProviderConfigProperty.STRING_TYPE, true, true); + ProtocolMapperRepresentation hardCodedAzp = createHardcodedClaim("hard", "azp", "invalid-azp", ProviderConfigProperty.STRING_TYPE, true, true, true); clientResource.getProtocolMappers().createMapper(hardCodedAzp); log.debug("Logging in"); @@ -469,7 +469,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { RealmResource realm = adminClient.realm(bc.providerRealmName()); ClientRepresentation rep = realm.clients().findByClientId(BrokerTestConstants.CLIENT_ID).get(0); ClientResource clientResource = realm.clients().get(rep.getId()); - ProtocolMapperRepresentation hardCodedAzp = createHardcodedClaim("hard", "aud", "invalid-aud", ProviderConfigProperty.LIST_TYPE, true, true); + ProtocolMapperRepresentation hardCodedAzp = createHardcodedClaim("hard", "aud", "invalid-aud", ProviderConfigProperty.LIST_TYPE, true, true, true); clientResource.getProtocolMappers().createMapper(hardCodedAzp); log.debug("Logging in"); @@ -564,7 +564,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { loginFetchingUserFromUserEndpoint(true); Assert.assertEquals("The ID token issued by the identity provider does not match the configured essential claim. Please contact your administrator.", - loginPage.getInstruction()); + loginPage.getInstruction()); List users = realmsResouce().realm(bc.consumerRealmName()).users().search(bc.getUserLogin()); @@ -572,8 +572,8 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { } protected void postInitializeUser(UserRepresentation user) { - user.setAttributes(ImmutableMap.> builder() - .put(USER_ATTRIBUTE_NAME, ImmutableList. builder().add(USER_ATTRIBUTE_VALUE).build()) + user.setAttributes(ImmutableMap.>builder() + .put(USER_ATTRIBUTE_NAME, ImmutableList.builder().add(USER_ATTRIBUTE_VALUE).build()) .build()); } @@ -585,8 +585,8 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { assertThat(claimFilterValue, Matchers.notNullValue()); if (idProvider.getConfig().getOrDefault(IdentityProviderModel.FILTERED_BY_CLAIMS, "false").equals(Boolean.toString(filteredByClaim)) && - idProvider.getConfig().getOrDefault(IdentityProviderModel.CLAIM_FILTER_NAME, "").equals(claimFilterName) && - idProvider.getConfig().getOrDefault(IdentityProviderModel.CLAIM_FILTER_VALUE, "").equals(claimFilterValue) + idProvider.getConfig().getOrDefault(IdentityProviderModel.CLAIM_FILTER_NAME, "").equals(claimFilterName) && + idProvider.getConfig().getOrDefault(IdentityProviderModel.CLAIM_FILTER_VALUE, "").equals(claimFilterValue) ) { return; } @@ -722,7 +722,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { } private void updateIdPSyncMode(IdentityProviderRepresentation idProvider, IdentityProviderResource idProviderResource, - IdentityProviderSyncMode syncMode, boolean trustEmail) { + IdentityProviderSyncMode syncMode, boolean trustEmail) { assertThat(idProvider, Matchers.notNullValue()); assertThat(idProviderResource, Matchers.notNullValue()); assertThat(syncMode, Matchers.notNullValue()); @@ -755,6 +755,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { } private static final CustomKcOidcBrokerConfiguration BROKER_CONFIG_INSTANCE = new CustomKcOidcBrokerConfiguration(); + static class CustomKcOidcBrokerConfiguration extends KcOidcBrokerConfiguration { @Override @@ -766,7 +767,7 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { userAttrMapper.setName(USER_ATTRIBUTE_NAME); userAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); userAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); - + Map userAttrMapperConfig = userAttrMapper.getConfig(); userAttrMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, USER_ATTRIBUTE_NAME); userAttrMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, USER_ATTRIBUTE_NAME); @@ -781,6 +782,6 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest { client.setProtocolMappers(mappers); return clients; - } + } } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTokenExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTokenExchangeTest.java index 52a99fedb7..6899c587ce 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTokenExchangeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTokenExchangeTest.java @@ -50,18 +50,16 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation; -import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeatures; import org.keycloak.testsuite.util.AdminClientUtil; -import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.util.BasicAuthHelper; import com.google.common.collect.ImmutableMap; -@EnableFeatures({ @EnableFeature(Profile.Feature.TOKEN_EXCHANGE), @EnableFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ) }) +@EnableFeatures({@EnableFeature(Profile.Feature.TOKEN_EXCHANGE), @EnableFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)}) public final class KcOidcBrokerTokenExchangeTest extends AbstractInitializedBaseBrokerTest { @Override @@ -77,7 +75,7 @@ public final class KcOidcBrokerTokenExchangeTest extends AbstractInitializedBase brokerApp.setDirectAccessGrantsEnabled(true); ClientResource brokerAppResource = providerRealm.clients().get(brokerApp.getId()); brokerAppResource.update(brokerApp); - brokerAppResource.getProtocolMappers().createMapper(createHardcodedClaim("hard-coded", "hard-coded", "hard-coded", "String", true, true)).close(); + brokerAppResource.getProtocolMappers().createMapper(createHardcodedClaim("hard-coded", "hard-coded", "hard-coded", "String", true, true, true)).close(); IdentityProviderMapperRepresentation hardCodedSessionNoteMapper = new IdentityProviderMapperRepresentation(); hardCodedSessionNoteMapper.setName("hard-coded"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosSingleRealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosSingleRealmTest.java index 141c8ca23e..80d61f4a00 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosSingleRealmTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosSingleRealmTest.java @@ -47,6 +47,7 @@ import org.keycloak.testsuite.util.AccountHelper; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.TestAppHelper; + import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId; /** @@ -180,7 +181,7 @@ public abstract class AbstractKerberosSingleRealmTest extends AbstractKerberosTe ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, KerberosConstants.GSS_DELEGATION_CREDENTIAL, KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String", - true, false, true); + true, false, true, true); ProtocolMapperRepresentation protocolMapperRep = ModelToRepresentation.toRepresentation(protocolMapper); ClientResource clientResource = findClientByClientId(testRealmResource(), "kerberos-app"); Response response = clientResource.getProtocolMappers().createMapper(protocolMapperRep); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMultipleAttributesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMultipleAttributesTest.java index 2548006286..9c6f1d8395 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMultipleAttributesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPMultipleAttributesTest.java @@ -105,8 +105,8 @@ public class LDAPMultipleAttributesTest extends AbstractLDAPTest { ldapClient.addRedirectUri("/ldap-portal"); ldapClient.addRedirectUri("/ldap-portal/*"); ldapClient.setManagementUrl("/ldap-portal"); - ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("postalCode", "postal_code", "postal_code", "String", true, true, true)); - ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("street", "street", "street", "String", true, true, false)); + ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("postalCode", "postal_code", "postal_code", "String", true, true, true, true)); + ldapClient.addProtocolMapper(UserAttributeMapper.createClaimMapper("street", "street", "street", "String", true, true, true, false)); ldapClient.addScopeMapping(appRealm.getRole("user")); ldapClient.setSecret("password"); }); @@ -237,7 +237,6 @@ public class LDAPMultipleAttributesTest extends AbstractLDAPTest { } - } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java index adc2d18af7..641e991f3b 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java @@ -819,7 +819,7 @@ public class AccessTokenTest extends AbstractKeycloakTest { String clientScopeId = ApiUtil.getCreatedId(response); response.close(); ClientScopeResource clientScopeResource = adminClient.proxy(ClientScopeResource.class, scopeUri); - ProtocolMapperModel hard = HardcodedClaim.create("hard", "hard", "coded", "String", true, true); + ProtocolMapperModel hard = HardcodedClaim.create("hard", "hard", "coded", "String", true, true, true); ProtocolMapperRepresentation mapper = ModelToRepresentation.toRepresentation(hard); response = clientScopeResource.getProtocolMappers().createMapper(mapper); assertEquals(201, response.getStatus()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java index ab6a51d51c..b8ac280f85 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java @@ -175,7 +175,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { directPublic.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); directPublic.setFullScopeAllowed(false); directPublic.addRedirectUri("*"); - directPublic.addProtocolMapper(AudienceProtocolMapper.createClaimMapper("client-exchanger-audience", clientExchanger.getClientId(), null, true, false)); + directPublic.addProtocolMapper(AudienceProtocolMapper.createClaimMapper("client-exchanger-audience", clientExchanger.getClientId(), null, true, false, true)); ClientModel directUntrustedPublic = realm.addClient("direct-public-untrusted"); directUntrustedPublic.setClientId("direct-public-untrusted"); @@ -186,7 +186,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { directUntrustedPublic.setFullScopeAllowed(false); directUntrustedPublic.addRedirectUri("*"); directUntrustedPublic.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+"); - directUntrustedPublic.addProtocolMapper(AudienceProtocolMapper.createClaimMapper("client-exchanger-audience", clientExchanger.getClientId(), null, true, false)); + directUntrustedPublic.addProtocolMapper(AudienceProtocolMapper.createClaimMapper("client-exchanger-audience", clientExchanger.getClientId(), null, true, false, true)); ClientModel directNoSecret = realm.addClient("direct-no-secret"); directNoSecret.setClientId("direct-no-secret"); @@ -538,16 +538,16 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest { } try (Response response = exchangeUrl.request() - .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader("client-exchanger", "secret")) - .post(Entity.form( - new Form() - .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) - .param(OAuth2Constants.SUBJECT_TOKEN, accessToken) - .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE) - .param(OAuth2Constants.REQUESTED_SUBJECT, "impersonated-user") - .param(OAuth2Constants.AUDIENCE, "target") + .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader("client-exchanger", "secret")) + .post(Entity.form( + new Form() + .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) + .param(OAuth2Constants.SUBJECT_TOKEN, accessToken) + .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE) + .param(OAuth2Constants.REQUESTED_SUBJECT, "impersonated-user") + .param(OAuth2Constants.AUDIENCE, "target") - ))) { + ))) { org.junit.Assert.assertEquals(200, response.getStatus()); AccessTokenResponse accessTokenResponse = response.readEntity(AccessTokenResponse.class); String exchangedTokenString = accessTokenResponse.getToken(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java index 2519d7d3d3..894e068be7 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java @@ -401,7 +401,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest { String fooScopeId = ApiUtil.getCreatedId(response); response.close(); - ProtocolMapperRepresentation protocolMapper = ProtocolMapperUtil.createAddressMapper(true, true, true); + ProtocolMapperRepresentation protocolMapper = ProtocolMapperUtil.createAddressMapper(true, true, true, true); response = appRealm.clientScopes().get(fooScopeId).getProtocolMappers().createMapper(protocolMapper); response.close(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java index fa7a86599b..fdaedfd198 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java @@ -153,11 +153,11 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { reconnectAdminClient(); ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); - app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper1","computed-via-script", "computed-via-script", "String", true, true, "script-scripts/test-script-mapper1.js", false)).close(); - app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper2","multiValued-via-script", "multiValued-via-script", "String", true, true, "script-scripts/test-script-mapper2.js", true)).close(); - app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper3","computed-json-via-script", "computed-json-via-script", "JSON", true, true, "script-scripts/test-script-mapper3.js", false)).close(); + app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper1","computed-via-script", "computed-via-script", "String", true, true, true,"script-scripts/test-script-mapper1.js", false)).close(); + app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper2","multiValued-via-script", "multiValued-via-script", "String", true, true, true, "script-scripts/test-script-mapper2.js", true)).close(); + app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper3","computed-json-via-script", "computed-json-via-script", "JSON", true, true, true, "script-scripts/test-script-mapper3.js", false)).close(); - Response response = app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper3", "syntax-error-script", "syntax-error-script", "String", true, true, "script-scripts/test-bad-script-mapper3.js", false)); + Response response = app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper3", "syntax-error-script", "syntax-error-script", "String", true, true, true, "script-scripts/test-bad-script-mapper3.js", false)); assertThat(response.getStatusInfo().getFamily(), is(Response.Status.Family.CLIENT_ERROR)); response.close(); } @@ -199,31 +199,31 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); - ProtocolMapperRepresentation mapper = createAddressMapper(true, true, true); + ProtocolMapperRepresentation mapper = createAddressMapper(true, true, true, true); mapper.getConfig().put(AddressMapper.getModelPropertyName(AddressClaimSet.REGION), "region_some"); mapper.getConfig().put(AddressMapper.getModelPropertyName(AddressClaimSet.COUNTRY), "country_some"); mapper.getConfig().remove(AddressMapper.getModelPropertyName(AddressClaimSet.POSTAL_CODE)); // Even if we remove protocolMapper config property, it should still default to postal_code app.getProtocolMappers().createMapper(mapper).close(); - ProtocolMapperRepresentation hard = createHardcodedClaim("hard", "hard", "coded", "String", true, true); + ProtocolMapperRepresentation hard = createHardcodedClaim("hard", "hard", "coded", "String", true, true, true); app.getProtocolMappers().createMapper(hard).close(); - app.getProtocolMappers().createMapper(createHardcodedClaim("hard-nested", "nested.hard", "coded-nested", "String", true, true)).close(); - app.getProtocolMappers().createMapper(createClaimMapper("custom phone", "phone", "home_phone", "String", true, true, true)).close(); - app.getProtocolMappers().createMapper(createClaimMapper("nested phone", "phone", "home.phone", "String", true, true, true)).close(); - app.getProtocolMappers().createMapper(createClaimMapper("dotted phone", "phone", "home\\.phone", "String", true, true, true)).close(); - app.getProtocolMappers().createMapper(createClaimMapper("departments", "departments", "department", "String", true, true, true)).close(); - app.getProtocolMappers().createMapper(createClaimMapper("firstDepartment", "departments", "firstDepartment", "String", true, true, false)).close(); + app.getProtocolMappers().createMapper(createHardcodedClaim("hard-nested", "nested.hard", "coded-nested", "String", true, true, true)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("custom phone", "phone", "home_phone", "String", true, true, true, true)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("nested phone", "phone", "home.phone", "String", true, true, true, true)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("dotted phone", "phone", "home\\.phone", "String", true, true, true, true)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("departments", "departments", "department", "String", true, true, true, true)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("firstDepartment", "departments", "firstDepartment", "String", true, true, true,false)).close(); app.getProtocolMappers().createMapper(createHardcodedRole("hard-realm", "hardcoded")).close(); app.getProtocolMappers().createMapper(createHardcodedRole("hard-app", "app.hardcoded")).close(); app.getProtocolMappers().createMapper(createRoleNameMapper("rename-app-role", "test-app.customer-user", "realm-user")).close(); - app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper1","computed-via-script", "computed-via-script", "String", true, true, "'hello_' + user.username", false)).close(); - app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper2","multiValued-via-script", "multiValued-via-script", "String", true, true, "new java.util.ArrayList(['A','B'])", true)).close(); + app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper1","computed-via-script", "computed-via-script", "String", true, true, true, "'hello_' + user.username", false)).close(); + app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper2","multiValued-via-script", "multiValued-via-script", "String", true, true, true, "new java.util.ArrayList(['A','B'])", true)).close(); app.getProtocolMappers().createMapper(createClaimMapper("json-attribute-mapper", "json-attribute", "claim-from-json-attribute", - "JSON", true, true, false)).close(); + "JSON", true, true, true, false)).close(); app.getProtocolMappers().createMapper(createClaimMapper("json-attribute-mapper-multi", "json-attribute-multi", "claim-from-json-attribute-multi", - "JSON", true, true, true)).close(); + "JSON", true, true, true, true)).close(); - Response response = app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper3", "syntax-error-script", "syntax-error-script", "String", true, true, "func_tion foo(){ return 'fail';} foo()", false)); + Response response = app.getProtocolMappers().createMapper(createScriptMapper("test-script-mapper3", "syntax-error-script", "syntax-error-script", "String", true, true, true, "func_tion foo(){ return 'fail';} foo()", false)); assertThat(response.getStatusInfo().getFamily(), is(Response.Status.Family.CLIENT_ERROR)); response.close(); } @@ -360,10 +360,10 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create a user attr mapping for some claims that exist as properties in the tokens ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); - app.getProtocolMappers().createMapper(createClaimMapper("userid-as-sub", "userid", "sub", "String", true, true, false)).close(); - app.getProtocolMappers().createMapper(createClaimMapper("useraud", "useraud", "aud", "String", true, true, true)).close(); - app.getProtocolMappers().createMapper(createHardcodedClaim("website-hardcoded", "website", "http://localhost", "String", true, true)).close(); - app.getProtocolMappers().createMapper(createHardcodedClaim("iat-hardcoded", "iat", "123", "long", true, false)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("userid-as-sub", "userid", "sub", "String", true, true, true,false)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("useraud", "useraud", "aud", "String", true, true, true, true)).close(); + app.getProtocolMappers().createMapper(createHardcodedClaim("website-hardcoded", "website", "http://localhost", "String", true, true, true)).close(); + app.getProtocolMappers().createMapper(createHardcodedClaim("iat-hardcoded", "iat", "123", "long", true, false, true)).close(); // login OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password"); @@ -432,6 +432,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { "claim-name", String.class.getSimpleName(), true, + true, true )))) { mapperId = getCreatedId(response); @@ -481,6 +482,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { "claim-name", String.class.getSimpleName(), true, + true, true )))) { mapperId = getCreatedId(response); @@ -519,8 +521,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { userResource.update(user); ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); - app.getProtocolMappers().createMapper(createClaimMapper("empty", "empty", "empty", "String", true, true, false)).close(); - app.getProtocolMappers().createMapper(createClaimMapper("null", "null", "null", "String", true, true, false)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("empty", "empty", "empty", "String", true, true, true,false)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("null", "null", "null", "String", true, true, true,false)).close(); } { @@ -569,8 +571,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { public void testUserRoleToAttributeMappers() throws Exception { // Add mapper for realm roles - ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true); - ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper("test-app", null, "Client roles mapper", "roles-custom.test-app", true, true); + ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, true); + ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper("test-app", null, "Client roles mapper", "roles-custom.test-app", true, true, true); ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper)); @@ -792,7 +794,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { public void testRoleMapperWithRoleInheritedFromMoreGroups() throws Exception { // Create client-mapper String clientId = "test-app"; - ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, null, "Client roles mapper", "roles-custom.test-app", true, true); + ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, null, "Client roles mapper", "roles-custom.test-app", true, true, true); ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers(); protocolMappers.createMapper(Arrays.asList(clientMapper)); @@ -825,8 +827,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { public void testUserGroupRoleToAttributeMappers() throws Exception { // Add mapper for realm roles String clientId = "test-app"; - ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true); - ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, "ta.", "Client roles mapper", "roles-custom.test-app", true, true); + ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, true); + ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, "ta.", "Client roles mapper", "roles-custom.test-app", true, true, true); ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers(); protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper)); @@ -861,8 +863,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { @Test public void testUserGroupRoleToAttributeMappersNotScopedOtherApp() throws Exception { String clientId = "test-app-authz"; - ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true); - ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, null, "Client roles mapper", "roles-custom." + clientId, true, true); + ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, true); + ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, null, "Client roles mapper", "roles-custom." + clientId, true, true, true); ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers(); protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper)); @@ -901,8 +903,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { @Test public void testUserGroupRoleToAttributeMappersScoped() throws Exception { String clientId = "test-app-scope"; - ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true); - ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, null, "Client roles mapper", "roles-custom.test-app-scope", true, true); + ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, true); + ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(clientId, null, "Client roles mapper", "roles-custom.test-app-scope", true, true, true); ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers(); protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper)); @@ -935,8 +937,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { @Test public void testUserGroupRoleToAttributeMappersScopedClientNotSet() throws Exception { String clientId = "test-app-scope"; - ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true); - ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(null, null, "Client roles mapper", "roles-custom.test-app-scope", true, true); + ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, true); + ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(null, null, "Client roles mapper", "roles-custom.test-app-scope", true, true, true); ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers(); protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper)); @@ -971,8 +973,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { @Test public void testSingleValuedRoleMapping() throws Exception { String clientId = "test-app-scope"; - ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, false); - ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(null, null, "Client roles mapper", "roles-custom.test-app-scope", true, true, false); + ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, true,false); + ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(null, null, "Client roles mapper", "roles-custom.test-app-scope", true, true, true, false); ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), clientId).getProtocolMappers(); protocolMappers.createMapper(Arrays.asList(realmMapper, clientMapper)); @@ -1008,8 +1010,8 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { final String diffClient = "test-app"; final String realmName = "test"; - final ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true); - final ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(diffClient, null, "Client roles mapper", "roles-custom.test-app", true, true); + final ProtocolMapperRepresentation realmMapper = ProtocolMapperUtil.createUserRealmRoleMappingMapper("pref.", "Realm roles mapper", "roles-custom.realm", true, true, true); + final ProtocolMapperRepresentation clientMapper = ProtocolMapperUtil.createUserClientRoleMappingMapper(diffClient, null, "Client roles mapper", "roles-custom.test-app", true, true, true); try (ClientAttributeUpdater cau = ClientAttributeUpdater.forClient(adminClient, realmName, clientId).setDirectAccessGrantsEnabled(true); ProtocolMappersUpdater protocolMappers = new ProtocolMappersUpdater(cau.getResource().getProtocolMappers())) { @@ -1057,7 +1059,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { userResource.joinGroup(group1.getId()); // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, false, false)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true,false, false)).close(); try { // test it @@ -1097,7 +1099,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { userResource.joinGroup(group1.getId()); // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true, false)).close(); try { // test it @@ -1138,7 +1140,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { userResource.joinGroup(group1.getId()); // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true, true)).close(); try { // test it @@ -1177,7 +1179,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { userResource.joinGroup(group1.getId()); // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, false, false)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false, false)).close(); try { // test it @@ -1212,7 +1214,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true, false)).close(); try { // test it @@ -1248,7 +1250,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true, true)).close(); try { // test it @@ -1291,7 +1293,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, false, false)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false, false)).close(); try { // test it @@ -1336,7 +1338,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true, false)).close(); try { // test it @@ -1383,7 +1385,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true, true)).close(); try { // test it @@ -1434,7 +1436,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, false, false)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false, false)).close(); try { // test it @@ -1483,7 +1485,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, false, false)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false, false)).close(); try { // test it @@ -1532,7 +1534,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, false)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true, false)).close(); try { // test it @@ -1586,7 +1588,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { // create the attribute mapper ProtocolMappersResource protocolMappers = findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); - protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true, true)).close(); + protocolMappers.createMapper(createClaimMapper("group-value", "group-value", "group-value", "String", true, true, true,true, true)).close(); try { // test it @@ -1776,7 +1778,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { put(ClientScopeModel.DYNAMIC_SCOPE_REGEXP, "dyn-scope-with-mapper:*"); }}); // create the attribute mapper - ProtocolMapperRepresentation protocolMapperRepresentation = createHardcodedClaim("dynamic-scope-hardcoded-mapper", "hardcoded-foo", "hardcoded-bar", "String", true, true); + ProtocolMapperRepresentation protocolMapperRepresentation = createHardcodedClaim("dynamic-scope-hardcoded-mapper", "hardcoded-foo", "hardcoded-bar", "String", true, true, true); scopeRep.setProtocolMappers(Collections.singletonList(protocolMapperRepresentation)); try (Response resp = adminClient.realm("test").clientScopes().create(scopeRep)) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AudienceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AudienceTest.java index 7f349dcd6b..605660132b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AudienceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AudienceTest.java @@ -119,7 +119,7 @@ public class AudienceTest extends AbstractOIDCScopeTest { public void testAudienceProtocolMapperWithClientAudience() throws Exception { // Add audience protocol mapper to the clientScope "audience-scope" ProtocolMapperRepresentation audienceMapper = ProtocolMapperUtil.createAudienceMapper("audience mapper", "service-client", - null, true, false); + null, true, false, true); ClientScopeResource clientScope = ApiUtil.findClientScopeByName(testRealm(), "audience-scope"); Response resp = clientScope.getProtocolMappers().createMapper(audienceMapper); String mapperId = ApiUtil.getCreatedId(resp); @@ -131,7 +131,7 @@ public class AudienceTest extends AbstractOIDCScopeTest { EventRepresentation loginEvent = events.expectLogin() .user(userId) .assertEvent(); - Tokens tokens = sendTokenRequest(loginEvent, userId,"openid profile email audience-scope", "test-app"); + Tokens tokens = sendTokenRequest(loginEvent, userId, "openid profile email audience-scope", "test-app"); assertAudiences(tokens.accessToken, "service-client"); assertAudiences(tokens.idToken, "test-app"); @@ -145,14 +145,14 @@ public class AudienceTest extends AbstractOIDCScopeTest { public void testAudienceProtocolMapperWithCustomAudience() throws Exception { // Add audience protocol mapper to the clientScope "audience-scope" ProtocolMapperRepresentation audienceMapper = ProtocolMapperUtil.createAudienceMapper("audience mapper 1", null, - "http://host/service/ctx1", true, false); + "http://host/service/ctx1", true, false, true); ClientScopeResource clientScope = ApiUtil.findClientScopeByName(testRealm(), "audience-scope"); Response resp = clientScope.getProtocolMappers().createMapper(audienceMapper); String mapper1Id = ApiUtil.getCreatedId(resp); resp.close(); audienceMapper = ProtocolMapperUtil.createAudienceMapper("audience mapper 2", null, - "http://host/service/ctx2", true, true); + "http://host/service/ctx2", true, true, true); resp = clientScope.getProtocolMappers().createMapper(audienceMapper); String mapper2Id = ApiUtil.getCreatedId(resp); resp.close(); @@ -163,7 +163,7 @@ public class AudienceTest extends AbstractOIDCScopeTest { EventRepresentation loginEvent = events.expectLogin() .user(userId) .assertEvent(); - Tokens tokens = sendTokenRequest(loginEvent, userId,"openid profile email audience-scope", "test-app"); + Tokens tokens = sendTokenRequest(loginEvent, userId, "openid profile email audience-scope", "test-app"); assertAudiences(tokens.accessToken, "http://host/service/ctx1", "http://host/service/ctx2"); assertAudiences(tokens.idToken, "test-app", "http://host/service/ctx2"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java new file mode 100644 index 0000000000..63762cbd79 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java @@ -0,0 +1,472 @@ +package org.keycloak.testsuite.oidc; + +import jakarta.ws.rs.NotFoundException; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.ClientScopeResource; +import org.keycloak.admin.client.resource.ProtocolMappersResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.common.Profile; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper; +import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper; +import org.keycloak.protocol.oidc.mappers.HardcodedClaim; +import org.keycloak.protocol.oidc.mappers.HardcodedRole; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.protocol.oidc.mappers.RoleNameMapper; +import org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper; +import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.arquillian.annotation.EnableFeature; +import org.keycloak.testsuite.util.ClientManager; +import org.keycloak.testsuite.util.KeycloakModelUtils; +import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.ProtocolMapperUtil; +import org.keycloak.util.JsonSerialization; +import org.wildfly.common.Assert; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.keycloak.protocol.ProtocolMapperUtils.USER_SESSION_NOTE; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.ACR; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.ACR_SCOPE; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.ADDRESS; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.ALLOWED_WEB_ORIGINS; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.AUDIENCE_RESOLVE; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.CLIENT_ROLES; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.EMAIL; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.EMAIL_VERIFIED; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.FAMILY_NAME; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.FULL_NAME; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.GIVEN_NAME; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.PROFILE_CLAIM; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.REALM_ROLES; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.ROLES_SCOPE; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.USERNAME; +import static org.keycloak.protocol.oidc.OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE; +import static org.keycloak.protocol.oidc.mappers.AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX; +import static org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper.INCLUDED_CLIENT_AUDIENCE; +import static org.keycloak.protocol.oidc.mappers.HardcodedClaim.CLAIM_VALUE; +import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN; +import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION; +import static org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper.PAIRWISE_SUB_ALGORITHM_SALT; +import static org.keycloak.protocol.oidc.mappers.RoleNameMapper.NEW_ROLE_NAME; +import static org.keycloak.protocol.oidc.mappers.RoleNameMapper.ROLE_CONFIG; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; +import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; + +@EnableFeature(value = Profile.Feature.TOKEN_EXCHANGE, skipRestart = true) +public class LightWeightAccessTokenTest extends AbstractKeycloakTest { + + @Before + public void clientConfiguration() { + ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true).setServiceAccountsEnabled(true); + ClientManager.realm(adminClient.realm("test")).clientId("resource-server").directAccessGrant(true); + } + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + UserRepresentation user = findUser(realm, "test-user@localhost"); + Map> attributes = new HashMap<>(){{ + put("street", Arrays.asList("1 My Street")); + put("locality", Arrays.asList("Cardiff")); + put("region", Arrays.asList("Cardiff")); + put("postal_code", Arrays.asList("CF104RA")); + }}; + user.setAttributes(attributes); + user.setGroups(Arrays.asList("/topGroup/level2group")); + ClientRepresentation confApp = KeycloakModelUtils.createClient(realm, "resource-server"); + confApp.setSecret("password"); + confApp.setServiceAccountsEnabled(Boolean.TRUE); + testRealms.add(realm); + } + + private UserRepresentation findUser(RealmRepresentation testRealm, String userName) { + for (UserRepresentation user : testRealm.getUsers()) { + if (user.getUsername().equals(userName)) return user; + } + + return null; + } + + @Test + public void accessTokenFalseIntrospectionTrueTest() throws IOException { + ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, true); + try { + oauth.nonce("123456"); + oauth.scope("address"); + oauth.clientId("test-app"); + OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password").tokenResponse; + String accessToken = response.getAccessToken(); + System.out.println("accessToken:" + accessToken); + assertAccessToken(oauth.verifyToken(accessToken), true, false); + + oauth.clientId("resource-server"); + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken); + System.out.println("tokenResponse:" + tokenResponse); + assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, true, false); + } finally { + deleteProtocolMappers(protocolMappers); + } + } + + @Test + public void accessTokenTrueIntrospectionFalseTest() throws IOException { + ProtocolMappersResource protocolMappers = setProtocolMappers(true, false, true); + try { + oauth.nonce("123456"); + oauth.scope("address"); + oauth.clientId("test-app"); + OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password").tokenResponse; + String accessToken = response.getAccessToken(); + System.out.println("accessToken:" + accessToken); + assertAccessToken(oauth.verifyToken(accessToken), true, true); + + oauth.clientId("resource-server"); + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken); + System.out.println("tokenResponse:" + tokenResponse); + // Most of the claims should not be included in introspectionResponse as introspectionMapper was disabled + assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, false, false); + } finally { + deleteProtocolMappers(protocolMappers); + } + } + + @Test + public void accessTokenTrueIntrospectionTrueTest() throws IOException { + ProtocolMappersResource protocolMappers = setProtocolMappers(true, true, true); + try { + oauth.nonce("123456"); + oauth.scope("address"); + oauth.clientId("test-app"); + OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password").tokenResponse; + String accessToken = response.getAccessToken(); + System.out.println("accessToken:" + accessToken); + assertAccessToken(oauth.verifyToken(accessToken), true, true); + + oauth.clientId("resource-server"); + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken); + System.out.println("tokenResponse:" + tokenResponse); + assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, true, false); + } finally { + deleteProtocolMappers(protocolMappers); + } + } + + @Test + public void offlineTokenTest() throws IOException { + ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, true); + try { + oauth.nonce("123456"); + oauth.scope("openid address offline_access"); + + oauth.clientId("test-app"); + TokenResponseContext ctx = browserLogin("password", "test-user@localhost", "password"); + OAuthClient.AccessTokenResponse response = ctx.tokenResponse; + String accessToken = response.getAccessToken(); + System.out.println("accessToken:" + accessToken); + System.out.println("idtoken:" + response.getIdToken()); + assertAccessToken(oauth.verifyToken(accessToken), true, false); + + oauth.clientId("resource-server"); + removeSession(ctx.userSessionId); + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken); + System.out.println("tokenResponse:" + tokenResponse); + assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, true, false); + } finally { + deleteProtocolMappers(protocolMappers); + } + } + + private void removeSession(final String sessionId) { + testingClient.testing().removeExpired("test"); + try { + testingClient.testing().removeUserSession("test", sessionId); + } catch (NotFoundException nfe) { + // Ignore + } + } + + @Test + public void clientCredentialTest() throws Exception { + ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, false); + try { + oauth.nonce("123456"); + oauth.scope("address"); + + oauth.clientId("test-app"); + OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest("password"); + String accessToken = response.getAccessToken(); + System.out.println("accessToken:" + accessToken); + assertAccessToken(oauth.verifyToken(accessToken), false, false); + + oauth.clientId("resource-server"); + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", accessToken); + System.out.println("tokenResponse:" + tokenResponse); + assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), false); + } finally { + deleteProtocolMappers(protocolMappers); + } + } + + @Test + public void exchangeTest() throws Exception { + ProtocolMappersResource protocolMappers = setProtocolMappers(false, true, true); + try { + oauth.nonce("123456"); + oauth.scope("address"); + + oauth.clientId("test-app"); + OAuthClient.AccessTokenResponse response = browserLogin("password", "test-user@localhost", "password").tokenResponse; + String accessToken = response.getAccessToken(); + System.out.println("accessToken:" + accessToken); + assertAccessToken(oauth.verifyToken(accessToken), true, false); + response = oauth.doTokenExchange(TEST, accessToken, null, "test-app", "password"); + String exchangedTokenString = response.getAccessToken(); + System.out.println("exchangedTokenString:" + exchangedTokenString); + + oauth.clientId("resource-server"); + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("resource-server", "password", exchangedTokenString); + System.out.println("tokenResponse:" + tokenResponse); + assertTokenIntrospectionResponse(JsonSerialization.readValue(tokenResponse, AccessToken.class), true, true, true); + } finally { + deleteProtocolMappers(protocolMappers); + } + } + + private void assertMapperClaims(AccessToken token, boolean isAddMapperResponseFlag, boolean isAuthCodeFlow) { + if (isAddMapperResponseFlag) { + if (isAuthCodeFlow) { + Assert.assertNotNull(token.getName()); + Assert.assertNotNull(token.getGivenName()); + Assert.assertNotNull(token.getFamilyName()); + Assert.assertNotNull(token.getAddress()); + Assert.assertNotNull(token.getEmail()); + Assert.assertNotNull(token.getOtherClaims().get("user-session-note")); + Assert.assertNotNull(token.getOtherClaims().get("test-claim")); + Assert.assertNotNull(token.getOtherClaims().get("group-name")); + } + Assert.assertNotNull(token.getAudience()); + Assert.assertNotNull(token.getAcr()); + Assert.assertNotNull(token.getAllowedOrigins()); + Assert.assertNotNull(token.getRealmAccess()); + Assert.assertNotNull(token.getResourceAccess()); + Assert.assertNotNull(token.getEmailVerified()); + Assert.assertNotNull(token.getPreferredUsername()); + } else { + if (isAuthCodeFlow) { + Assert.assertTrue(token.getName() == null); + Assert.assertTrue(token.getGivenName() == null); + Assert.assertTrue(token.getFamilyName() == null); + Assert.assertTrue(token.getAddress() == null); + Assert.assertTrue(token.getEmail() == null); + Assert.assertTrue(token.getOtherClaims().get("user-session-note") == null); + Assert.assertTrue(token.getOtherClaims().get("test-claim") == null); + Assert.assertTrue(token.getOtherClaims().get("group-name") == null); + } + Assert.assertTrue(token.getAcr() == null); + Assert.assertTrue(token.getAllowedOrigins() == null); + Assert.assertTrue(token.getRealmAccess() == null); + Assert.assertTrue(token.getResourceAccess().isEmpty()); + Assert.assertTrue(token.getEmailVerified() == null); + Assert.assertTrue(token.getPreferredUsername() == null); + } + } + + private void assertInitClaims(AccessToken token, boolean isAuthCodeFlow) { + Assert.assertNotNull(token.getExp()); + Assert.assertNotNull(token.getIat()); + Assert.assertNotNull(token.getId()); + Assert.assertNotNull(token.getType()); + if (isAuthCodeFlow) { + Assert.assertNotNull(token.getSessionId()); + Assert.assertNotNull(token.getAuth_time()); + } else { + Assert.assertTrue(token.getSessionId() == null); + Assert.assertTrue(token.getAuth_time() == null); + } + Assert.assertNotNull(token.getIssuedFor()); + Assert.assertNotNull(token.getScope()); + Assert.assertNotNull(token.getIssuer()); + } + + private void assertIntrospectClaims(AccessToken token) { + Assert.assertNotNull(token.getOtherClaims().get("client_id")); + Assert.assertNotNull(token.getOtherClaims().get("active")); + Assert.assertNotNull(token.getOtherClaims().get("token_type")); + } + + private void assertNonce(AccessToken token, boolean isAuthCodeFlow, boolean exchangeToken) { + if (isAuthCodeFlow && !exchangeToken) { + Assert.assertNotNull(token.getNonce()); + } else { + Assert.assertTrue(token.getNonce() == null); + } + } + + private void assertAccessToken(AccessToken token, boolean isAuthCodeFlow, boolean isAddToAccessToken) { + assertNonce(token, isAuthCodeFlow, false); + assertMapperClaims(token, isAddToAccessToken, isAuthCodeFlow); + assertInitClaims(token, isAuthCodeFlow); + } + + private void assertTokenIntrospectionResponse(AccessToken token, boolean isAuthCodeFlow) { + assertTokenIntrospectionResponse(token, isAuthCodeFlow, true, false); + } + + private void assertTokenIntrospectionResponse(AccessToken token, boolean isAuthCodeFlow, boolean isAddToIntrospect, boolean exchangeToken) { + assertNonce(token, isAuthCodeFlow, exchangeToken); + assertMapperClaims(token, isAddToIntrospect, isAuthCodeFlow); + assertInitClaims(token, isAuthCodeFlow); + assertIntrospectClaims(token); + } + + protected RealmResource testRealm() { + return adminClient.realm("test"); + } + + private void setScopeProtocolMappers(boolean isIncludeAccessToken, boolean isIncludeIntrospection) { + setScopeProtocolMapper(ACR_SCOPE, ACR, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(PROFILE_CLAIM, FULL_NAME, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(EMAIL, EMAIL, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(EMAIL, EMAIL_VERIFIED, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(PROFILE_CLAIM, GIVEN_NAME, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(PROFILE_CLAIM, FAMILY_NAME, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(PROFILE_CLAIM, USERNAME, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(WEB_ORIGINS_SCOPE, ALLOWED_WEB_ORIGINS, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(ROLES_SCOPE, REALM_ROLES, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(ROLES_SCOPE, CLIENT_ROLES, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(ROLES_SCOPE, AUDIENCE_RESOLVE, isIncludeAccessToken, isIncludeIntrospection); + setScopeProtocolMapper(ADDRESS, ADDRESS, isIncludeAccessToken, isIncludeIntrospection); + } + + private void setScopeProtocolMapper(String scopeName, String mapperName, boolean isIncludeAccessToken, boolean isIncludeIntrospection) { + ClientScopeResource scope = ApiUtil.findClientScopeByName(testRealm(), scopeName); + ProtocolMapperRepresentation protocolMapper = ApiUtil.findProtocolMapperByName(scope, mapperName); + Map config = protocolMapper.getConfig(); + if (isIncludeAccessToken) { + config.put(INCLUDE_IN_ACCESS_TOKEN, "true"); + } else { + config.put(INCLUDE_IN_ACCESS_TOKEN, "false"); + } + if (isIncludeIntrospection) { + config.put(INCLUDE_IN_INTROSPECTION, "true"); + } else { + config.put(INCLUDE_IN_INTROSPECTION, "false"); + } + scope.getProtocolMappers().update(protocolMapper.getId(), protocolMapper); + } + + private ProtocolMappersResource setProtocolMappers(boolean isIncludeAccessToken, boolean isIncludeIntrospection, boolean setPairWise) { + setScopeProtocolMappers(isIncludeAccessToken, isIncludeIntrospection); + List protocolMapperList = new ArrayList<>(); + setExistingProtocolMappers(protocolMapperList, isIncludeAccessToken, isIncludeIntrospection, setPairWise); + ProtocolMappersResource protocolMappers = ApiUtil.findClientResourceByClientId(adminClient.realm("test"), "test-app").getProtocolMappers(); + protocolMappers.createMapper(protocolMapperList); + return protocolMappers; + } + + private void setExistingProtocolMappers(List protocolMapperList, boolean isIncludeAccessToken, boolean isIncludeIntrospection, boolean setPairWise) { + Map config = new HashMap<>(); + if (isIncludeAccessToken) { + config.put(INCLUDE_IN_ACCESS_TOKEN, "true"); + } else { + config.put(INCLUDE_IN_ACCESS_TOKEN, "false"); + } + + if (isIncludeIntrospection) { + config.put(INCLUDE_IN_INTROSPECTION, "true"); + } else { + config.put(INCLUDE_IN_INTROSPECTION, "false"); + } + + ProtocolMapperRepresentation audienceProtocolMapper = createClaimMapper("audience", AudienceProtocolMapper.PROVIDER_ID, new HashMap<>(config) {{ + put(INCLUDED_CLIENT_AUDIENCE, "account-console"); + }}); + protocolMapperList.add(audienceProtocolMapper); + ProtocolMapperRepresentation roleNameMapper = createClaimMapper("role-name", RoleNameMapper.PROVIDER_ID, new HashMap<>(config) {{ + put(ROLE_CONFIG, "user"); + put(NEW_ROLE_NAME, "new-role"); + }}); + protocolMapperList.add(roleNameMapper); + ProtocolMapperRepresentation groupMembershipMapper = createClaimMapper("group-member", GroupMembershipMapper.PROVIDER_ID, new HashMap<>(config) {{ + put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "group-name"); + }}); + protocolMapperList.add(groupMembershipMapper); + ProtocolMapperRepresentation hardcodedClaim = createClaimMapper("hardcoded-claim", HardcodedClaim.PROVIDER_ID, new HashMap<>(config) {{ + put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "test-claim"); + put(CLAIM_VALUE, "test-value"); + }}); + protocolMapperList.add(hardcodedClaim); + ProtocolMapperRepresentation hardcodedRole = createClaimMapper("hardcoded-role", HardcodedRole.PROVIDER_ID, new HashMap<>(config) {{ + put(ROLE_CONFIG, "hardcoded-role"); + }}); + protocolMapperList.add(hardcodedRole); + ProtocolMapperRepresentation userSessionNoteMapper = createClaimMapper("user-session-note", UserSessionNoteMapper.PROVIDER_ID, new HashMap<>(config) {{ + put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "user-session-note"); + put(USER_SESSION_NOTE, "AUTH_TIME"); + }}); + protocolMapperList.add(userSessionNoteMapper); + if (setPairWise) { + ProtocolMapperRepresentation pairwiseSubMapper = createClaimMapper("pairwise-sub-mapper", "oidc-" + SHA256PairwiseSubMapper.PROVIDER_ID + PROVIDER_ID_SUFFIX, new HashMap<>(config) {{ + put(PAIRWISE_SUB_ALGORITHM_SALT, "abc"); + }}); + protocolMapperList.add(pairwiseSubMapper); + } + } + + private static ProtocolMapperRepresentation createClaimMapper(String name, String providerId, Map config) { + ProtocolMapperModel mapper = new ProtocolMapperModel(); + mapper.setName(name); + mapper.setProtocolMapper(providerId); + mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + mapper.setConfig(config); + return ModelToRepresentation.toRepresentation(mapper); + } + + private void deleteProtocolMappers(ProtocolMappersResource protocolMappers) { + List mapperNames = new ArrayList<>(Arrays.asList("reference", "audience", "role-name", "group-member", "hardcoded-claim", "hardcoded-role", "user-session-note", "pairwise-sub-mapper")); + List mappers = new ArrayList<>(); + for (String mapperName : mapperNames) { + mappers.add(ProtocolMapperUtil.getMapperByNameAndProtocol(protocolMappers, OIDCLoginProtocol.LOGIN_PROTOCOL, mapperName)); + } + + for (ProtocolMapperRepresentation mapper : mappers) { + if (mapper != null) { + protocolMappers.delete(mapper.getId()); + } + } + } + + private TokenResponseContext browserLogin(String clientSecret, String username, String password) { + OAuthClient.AuthorizationEndpointResponse authsEndpointResponse = oauth.doLogin(username, password); + String userSessionId = authsEndpointResponse.getSessionState(); + OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(authsEndpointResponse.getCode(), clientSecret); + return new TokenResponseContext(userSessionId, tokenResponse); + } + + private class TokenResponseContext { + + private final String userSessionId; + private final OAuthClient.AccessTokenResponse tokenResponse; + + public TokenResponseContext(String userSessionId, OAuthClient.AccessTokenResponse tokenResponse) { + this.userSessionId = userSessionId; + this.tokenResponse = tokenResponse; + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java index 5f357b5ca8..af0134fd7d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java @@ -92,7 +92,7 @@ public class DeployedScriptMapperTest extends AbstractTestRealmKeycloakTest { ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); ProtocolMapperRepresentation mapper = createScriptMapper("test-script-mapper1", "computed-via-script", - "computed-via-script", "String", true, true, "'hello_' + user.username", false); + "computed-via-script", "String", true, true, true, "'hello_' + user.username", false); mapper.setProtocolMapper("script-mapper-a.js"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/UndeployedScriptMapperNotAvailableTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/UndeployedScriptMapperNotAvailableTest.java index ca7c87726a..df03132bbc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/UndeployedScriptMapperNotAvailableTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/UndeployedScriptMapperNotAvailableTest.java @@ -95,7 +95,7 @@ public class UndeployedScriptMapperNotAvailableTest extends AbstractTestRealmKey ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); { ProtocolMapperRepresentation mapper = createScriptMapper("test-script-mapper1", "computed-via-script", - "computed-via-script", "String", true, true, "'hello_' + user.username", false); + "computed-via-script", "String", true, true, true, "'hello_' + user.username", false); mapper.setProtocolMapper("script-mapper-a.js"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java index e636ad86f2..371758f643 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ProtocolMapperUtil.java @@ -47,8 +47,8 @@ public class ProtocolMapperUtil { * @param accessToken * @return */ - public static ProtocolMapperRepresentation createAddressMapper(boolean idToken, boolean accessToken, boolean userInfo) { - return ModelToRepresentation.toRepresentation(AddressMapper.createAddressMapper(idToken, accessToken, userInfo)); + public static ProtocolMapperRepresentation createAddressMapper(boolean idToken, boolean accessToken, boolean userInfo, boolean introspectionEndpoint) { + return ModelToRepresentation.toRepresentation(AddressMapper.createAddressMapper(idToken, accessToken, userInfo, introspectionEndpoint)); } /** @@ -65,9 +65,9 @@ public class ProtocolMapperUtil { public static ProtocolMapperRepresentation createHardcodedClaim(String name, String hardcodedName, String hardcodedValue, String claimType, - boolean accessToken, boolean idToken) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { return ModelToRepresentation.toRepresentation(HardcodedClaim.create(name, hardcodedName, hardcodedValue, - claimType, accessToken, idToken)); + claimType, accessToken, idToken, introspectionEndpoint)); } /** @@ -85,64 +85,64 @@ public class ProtocolMapperUtil { public static ProtocolMapperRepresentation createClaimMapper(String name, String userAttribute, String tokenClaimName, String claimType, - boolean accessToken, boolean idToken, boolean multivalued) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint, boolean multivalued) { return ModelToRepresentation.toRepresentation(UserAttributeMapper.createClaimMapper(name, userAttribute, tokenClaimName, - claimType, accessToken, idToken, multivalued, false)); + claimType, accessToken, idToken, introspectionEndpoint, multivalued, false)); } public static ProtocolMapperRepresentation createClaimMapper(String name, String userAttribute, String tokenClaimName, String claimType, - boolean accessToken, boolean idToken, + boolean accessToken, boolean idToken, boolean introspectionEndpoint, boolean multivalued, boolean aggregateAttrs) { return ModelToRepresentation.toRepresentation(UserAttributeMapper.createClaimMapper(name, userAttribute, tokenClaimName, - claimType, accessToken, idToken, multivalued, aggregateAttrs)); + claimType, accessToken, idToken, introspectionEndpoint, multivalued, aggregateAttrs)); } public static ProtocolMapperRepresentation createClaimMapper(String name, String userSessionNote, String tokenClaimName, String jsonType, - boolean accessToken, boolean idToken) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { return ModelToRepresentation.toRepresentation(UserSessionNoteMapper.createClaimMapper(name, userSessionNote, tokenClaimName, jsonType, - accessToken, idToken)); + accessToken, idToken, introspectionEndpoint)); } public static ProtocolMapperRepresentation createUserRealmRoleMappingMapper(String realmRolePrefix, String name, String tokenClaimName, - boolean accessToken, boolean idToken) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { - return createUserRealmRoleMappingMapper(realmRolePrefix, name, tokenClaimName, accessToken, idToken, true); + return createUserRealmRoleMappingMapper(realmRolePrefix, name, tokenClaimName, accessToken, idToken, introspectionEndpoint, true); } public static ProtocolMapperRepresentation createUserRealmRoleMappingMapper(String realmRolePrefix, String name, String tokenClaimName, - boolean accessToken, boolean idToken, boolean multiValued) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint, boolean multiValued) { - return ModelToRepresentation.toRepresentation(UserRealmRoleMappingMapper.create(realmRolePrefix, name, tokenClaimName, accessToken, idToken, multiValued)); + return ModelToRepresentation.toRepresentation(UserRealmRoleMappingMapper.create(realmRolePrefix, name, tokenClaimName, accessToken, idToken, introspectionEndpoint, multiValued)); } public static ProtocolMapperRepresentation createUserClientRoleMappingMapper(String clientId, String clientRolePrefix, String name, String tokenClaimName, - boolean accessToken, boolean idToken) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { - return createUserClientRoleMappingMapper(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken, true); + return createUserClientRoleMappingMapper(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken, introspectionEndpoint, true); } public static ProtocolMapperRepresentation createUserClientRoleMappingMapper(String clientId, String clientRolePrefix, String name, String tokenClaimName, - boolean accessToken, boolean idToken, boolean multiValued) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint, boolean multiValued) { - return ModelToRepresentation.toRepresentation(UserClientRoleMappingMapper.create(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken, multiValued)); + return ModelToRepresentation.toRepresentation(UserClientRoleMappingMapper.create(clientId, clientRolePrefix, name, tokenClaimName, accessToken, idToken, introspectionEndpoint, multiValued)); } public static ProtocolMapperRepresentation getMapperByNameAndProtocol(ProtocolMappersResource protocolMappers, String protocol, String name) { @@ -160,11 +160,12 @@ public class ProtocolMapperUtil { String claimType, boolean accessToken, boolean idToken, + boolean introspectionEndpoint, String script, boolean multiValued) { return ModelToRepresentation.toRepresentation( - ScriptBasedOIDCProtocolMapper.create(name, userAttribute, tokenClaimName, claimType, accessToken, idToken, script, multiValued) + ScriptBasedOIDCProtocolMapper.create(name, userAttribute, tokenClaimName, claimType, accessToken, idToken, introspectionEndpoint, script, multiValued) ); } @@ -175,10 +176,10 @@ public class ProtocolMapperUtil { public static ProtocolMapperRepresentation createAudienceMapper(String name, String includedClientAudience, String includedCustomAudience, - boolean accessToken, boolean idToken) { + boolean accessToken, boolean idToken, boolean introspectionEndpoint) { return ModelToRepresentation.toRepresentation( - AudienceProtocolMapper.createClaimMapper(name, includedClientAudience, includedCustomAudience, accessToken, idToken) + AudienceProtocolMapper.createClaimMapper(name, includedClientAudience, includedCustomAudience, accessToken, idToken, introspectionEndpoint) ); } }