New default client scope for 'basic' claims with 'auth_time' protocol mapper

Closes #27623

Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
Giuseppe Graziano 2024-03-26 16:59:58 +01:00 committed by Marek Posolda
parent 96672a2a6d
commit fe06df67c2
22 changed files with 196 additions and 33 deletions

View file

@ -75,7 +75,7 @@ Further, when the resource server acquires the PII removed from the access token
Information that cannot be removed from a lightweight access token::
Protocol mappers can controls which information is put onto an access token and the lightweight access token use the protocol mappers. Therefore, the following information cannot be removed from the lightweight access. +
`exp`, `iat`, `auth_time`, `jti`, `iss`, `sub`, `typ`, `azp`, `nonce`, `session_state`, `sid`, `scope`, `cnf`
`exp`, `iat`, `jti`, `iss`, `sub`, `typ`, `azp`, `nonce`, `session_state`, `sid`, `scope`, `cnf`
Using a lightweight access token in {project_name}::
By applying `use-lightweight-access-token` executor of <<_client_policies, client policies>> to a client, the client can receive a lightweight access token instead of an access token. The lightweight access token contains a claim controlled by a protocol mapper where its setting `Add to lightweight access token`(default OFF) is turned ON. Also, by turning ON its setting `Add to token introspection` of the protocol mapper, the client can obtain the claim by sending the access token to {project_name}'s token introspection endpoint.

View file

@ -68,3 +68,13 @@ How kcadm parses and handles options and parameters has changed. Error messages
= Removing custom user attribute indexes
When searching for users by user attribute, Keycloak no longer searches for user attribute names forcing lower case comparisons. This means Keycloak's native index on the user attribute table will now be used when searching. If you have created your own index based on `lower(name)`to speed up searches, you can now remove it.
= New default client scope `basic`
The new client scope named `basic` is added as a realm "default" client scope and hence will be added to all newly created clients. The client scope is also automatically added to all existing clients during migration.
This scope contains preconfigured protocol mappers for the following claims:
* `auth_time`
This helps to reduce even more the number of claims in a lightweight access token, but also gives the chance to configure claims that were always added automatically.

View file

@ -0,0 +1,64 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.migration.migrators;
import org.jboss.logging.Logger;
import org.keycloak.migration.MigrationProvider;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.RealmRepresentation;
/**
* @author <a href="mailto:ggrazian@redhat.com">Giuseppe Graziano</a>
*/
public class MigrateTo25_0_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("25.0.0");
private static final Logger LOG = Logger.getLogger(MigrateTo25_0_0.class);
@Override
public ModelVersion getVersion() {
return VERSION;
}
@Override
public void migrate(KeycloakSession session) {
session.realms().getRealmsStream().forEach(realm -> migrateRealm(session, realm));
}
@Override
public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) {
migrateRealm(session, realm);
}
protected void migrateRealm(KeycloakSession session, RealmModel realm) {
MigrationProvider migrationProvider = session.getProvider(MigrationProvider.class);
// create 'basic' client scope in the realm.
ClientScopeModel basicScope = migrationProvider.addOIDCBasicClientScope(realm);
//add basic scope to existing clients
realm.getClientsStream().forEach(c-> c.addClientScope(basicScope, true));
}
}

View file

@ -38,6 +38,7 @@ import org.keycloak.migration.migrators.MigrateTo21_0_0;
import org.keycloak.migration.migrators.MigrateTo22_0_0;
import org.keycloak.migration.migrators.MigrateTo23_0_0;
import org.keycloak.migration.migrators.MigrateTo24_0_0;
import org.keycloak.migration.migrators.MigrateTo25_0_0;
import org.keycloak.migration.migrators.MigrateTo2_0_0;
import org.keycloak.migration.migrators.MigrateTo2_1_0;
import org.keycloak.migration.migrators.MigrateTo2_2_0;
@ -113,7 +114,8 @@ public class DefaultMigrationManager implements MigrationManager {
new MigrateTo21_0_0(),
new MigrateTo22_0_0(),
new MigrateTo23_0_0(),
new MigrateTo24_0_0()
new MigrateTo24_0_0(),
new MigrateTo25_0_0()
};
private final KeycloakSession session;

View file

@ -77,4 +77,12 @@ public interface MigrationProvider extends Provider {
* @return created or already existing client scope 'acr'
*/
void addOIDCAcrClientScope(RealmModel realm);
/**
* Add 'basic' client scope or return it if already exists
*
* @param realm
* @return created or already existing client scope 'basic'
*/
ClientScopeModel addOIDCBasicClientScope(RealmModel realm);
}

View file

@ -164,7 +164,6 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi
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.

View file

@ -48,6 +48,7 @@ import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.AuthenticationManager;
import java.util.HashMap;
import java.util.HashSet;
@ -97,6 +98,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
public static final String WEB_ORIGINS_SCOPE = "web-origins";
public static final String MICROPROFILE_JWT_SCOPE = "microprofile-jwt";
public static final String ACR_SCOPE = "acr";
public static final String BASIC_SCOPE = "basic";
public static final String PROFILE_SCOPE_CONSENT_TEXT = "${profileScopeConsentText}";
public static final String EMAIL_SCOPE_CONSENT_TEXT = "${emailScopeConsentText}";
@ -220,6 +222,11 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
model = AcrProtocolMapper.create(ACR, true, true, true);
builtins.put(ACR, model);
}
model = UserSessionNoteMapper.createClaimMapper(IDToken.AUTH_TIME, AuthenticationManager.AUTH_TIME,
IDToken.AUTH_TIME, "long",
true, true, false, true);
builtins.put(BASIC_SCOPE, model);
}
private void createUserAttributeMapper(String name, String attrName, String claimName, String type) {
@ -298,6 +305,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
addWebOriginsClientScope(newRealm);
addMicroprofileJWTClientScope(newRealm);
addAcrClientScope(newRealm);
addBasicClientScope(newRealm);
if (Profile.isFeatureEnabled(Profile.Feature.ORGANIZATION)) {
ClientScopeModel organizationScope = newRealm.addClientScope(OAuth2Constants.ORGANIZATION);
@ -404,6 +412,25 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
}
}
public ClientScopeModel addBasicClientScope(RealmModel newRealm) {
ClientScopeModel basicScope = KeycloakModelUtils.getClientScopeByName(newRealm, BASIC_SCOPE);
if (basicScope == null) {
basicScope = newRealm.addClientScope(BASIC_SCOPE);
basicScope.setDescription("OpenID Connect scope for add all basic claims to the token");
basicScope.setDisplayOnConsentScreen(false);
basicScope.setIncludeInTokenScope(false);
basicScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
basicScope.addProtocolMapper(builtins.get(BASIC_SCOPE));
newRealm.addDefaultClientScope(basicScope, true);
logger.debugf("Client scope '%s' created in the realm '%s'.", BASIC_SCOPE, newRealm.getName());
} else {
logger.debugf("Client scope '%s' already exists in realm '%s'. Skip creating it.", BASIC_SCOPE, newRealm.getName());
}
return basicScope;
}
@Override
protected void addDefaults(ClientModel client) {
}

View file

@ -991,12 +991,6 @@ public class TokenManager {
token.setAcr(acr);
}
String authTime = session.getNote(AuthenticationManager.AUTH_TIME);
if (authTime != null) {
token.setAuthTime(Integer.parseInt(authTime));
}
token.setSessionState(session.getId());
ClientScopeModel offlineAccessScope = KeycloakModelUtils.getClientScopeByName(realm, OAuth2Constants.OFFLINE_ACCESS);
boolean offlineTokenRequested = offlineAccessScope == null ? false
@ -1195,7 +1189,6 @@ public class TokenManager {
idToken.issuedFor(accessToken.getIssuedFor());
idToken.issuer(accessToken.getIssuer());
idToken.setNonce(clientSessionCtx.getAttribute(OIDCLoginProtocol.NONCE_PARAM, String.class));
idToken.setAuthTime(accessToken.getAuthTime());
idToken.setSessionState(accessToken.getSessionState());
idToken.expiration(accessToken.getExpiration());

View file

@ -110,6 +110,13 @@ public class OIDCAttributeMapperHelper {
tmpToken.put(IDToken.ACR, (claim, mapperName, token, value) -> {
token.setAcr(value.toString());
});
tmpToken.put(IDToken.AUTH_TIME, (claim, mapperName, token, value) -> {
try {
token.setAuth_time(Long.parseLong(value.toString()));
} catch (NumberFormatException ignored){
}
});
tmpToken.put("aud", (claim, mapperName, token, value) -> {
if (value instanceof Collection) {
String[] audiences = ((Collection<?>) value).stream().map(Object::toString).toArray(String[]::new);
@ -129,7 +136,6 @@ public class OIDCAttributeMapperHelper {
tmpToken.put("iss", notAllowedInToken);
tmpToken.put("scope", notAllowedInToken);
tmpToken.put(IDToken.NONCE, notAllowedInToken);
tmpToken.put(IDToken.AUTH_TIME, notAllowedInToken);
tmpToken.put(IDToken.SESSION_STATE, notAllowedInToken);
tokenPropertySetters = Collections.unmodifiableMap(tmpToken);

View file

@ -107,6 +107,11 @@ public class DefaultMigrationProvider implements MigrationProvider {
getOIDCLoginProtocolFactory().addAcrClientScope(realm);
}
@Override
public ClientScopeModel addOIDCBasicClientScope(RealmModel realm) {
return getOIDCLoginProtocolFactory().addBasicClientScope(realm);
}
@Override
public void close() {
}

View file

@ -724,6 +724,7 @@ public class ExportImportUtil {
OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE,
OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE,
OIDCLoginProtocolFactory.ACR_SCOPE,
OIDCLoginProtocolFactory.BASIC_SCOPE,
SamlProtocolFactory.SCOPE_ROLE_LIST
));
@ -745,7 +746,8 @@ public class ExportImportUtil {
OAuth2Constants.SCOPE_EMAIL,
OIDCLoginProtocolFactory.ROLES_SCOPE,
OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE,
OIDCLoginProtocolFactory.ACR_SCOPE
OIDCLoginProtocolFactory.ACR_SCOPE,
OIDCLoginProtocolFactory.BASIC_SCOPE
));
Set<String> optionalClientScopes = realm.getDefaultOptionalClientScopes()

View file

@ -418,6 +418,11 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
}
}
protected void testMigrationTo25_0_0() {
// check that all expected scopes exist in the migrated realm.
testRealmDefaultClientScopes(migrationRealm);
}
protected void testDeleteAccount(RealmResource realm) {
ClientRepresentation accountClient = realm.clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
ClientResource accountResource = realm.clients().get(accountClient.getId());
@ -1110,6 +1115,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testMigrationTo24_0_0(testUserProfileMigration, testLdapUseTruststoreSpiMigration);
}
protected void testMigrationTo25_x() {
testMigrationTo25_0_0();
}
protected void testMigrationTo7_x(boolean supportedAuthzServices) {
if (supportedAuthzServices) {
testDecisionStrategySetOnResourceServer();

View file

@ -68,6 +68,7 @@ public class JsonFileImport1903MigrationTest extends AbstractJsonFileImportMigra
testMigrationTo22_x();
testMigrationTo23_x(true);
testMigrationTo24_x(true, true);
testMigrationTo25_0_0();
}
@Test

View file

@ -79,6 +79,7 @@ public class JsonFileImport198MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo22_x();
testMigrationTo23_x(false);
testMigrationTo24_x(false);
testMigrationTo25_0_0();
}
@Override

View file

@ -73,6 +73,7 @@ public class JsonFileImport255MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo22_x();
testMigrationTo23_x(false);
testMigrationTo24_x(false);
testMigrationTo25_0_0();
}
}

View file

@ -68,6 +68,7 @@ public class JsonFileImport343MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo22_x();
testMigrationTo23_x(false);
testMigrationTo24_x(false);
testMigrationTo25_0_0();
}
}

View file

@ -62,6 +62,7 @@ public class JsonFileImport483MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo22_x();
testMigrationTo23_x(false);
testMigrationTo24_x(false);
testMigrationTo25_0_0();
}
}

View file

@ -55,6 +55,7 @@ public class JsonFileImport903MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo22_x();
testMigrationTo23_x(false);
testMigrationTo24_x(false);
testMigrationTo25_0_0();
}
}

View file

@ -70,5 +70,6 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo22_x();
testMigrationTo23_x(true);
testMigrationTo24_x(true, true);
testMigrationTo25_0_0();
}
}

View file

@ -137,7 +137,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
OAuthClient.AccessTokenResponse response = browserLogin(TEST_CLIENT_SECRET, TEST_USER_NAME, TEST_USER_PASSWORD).tokenResponse;
String accessToken = response.getAccessToken();
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, false);
assertAccessToken(oauth.verifyToken(accessToken), true, false, false);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
@ -158,7 +158,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
OAuthClient.AccessTokenResponse response = browserLogin(TEST_CLIENT_SECRET, TEST_USER_NAME, TEST_USER_PASSWORD).tokenResponse;
String accessToken = response.getAccessToken();
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, true);
assertAccessToken(oauth.verifyToken(accessToken), true, true, false);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
@ -180,7 +180,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
OAuthClient.AccessTokenResponse response = browserLogin(TEST_CLIENT_SECRET, TEST_USER_NAME, TEST_USER_PASSWORD).tokenResponse;
String accessToken = response.getAccessToken();
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, true);
assertAccessToken(oauth.verifyToken(accessToken), true, true, false);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
@ -204,7 +204,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
String accessToken = response.getAccessToken();
logger.debug("accessToken:" + accessToken);
logger.debug("idtoken:" + response.getIdToken());
assertAccessToken(oauth.verifyToken(accessToken), true, false);
assertAccessToken(oauth.verifyToken(accessToken), true, false, false);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
removeSession(ctx.userSessionId);
@ -227,7 +227,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest(TEST_CLIENT_SECRET);
String accessToken = response.getAccessToken();
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), false, false);
assertAccessToken(oauth.verifyToken(accessToken), false, false, true);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String tokenResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
@ -249,7 +249,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
OAuthClient.AccessTokenResponse response = browserLogin(TEST_CLIENT_SECRET, TEST_USER_NAME, TEST_USER_PASSWORD).tokenResponse;
String accessToken = response.getAccessToken();
logger.debug("accessToken:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, false);
assertAccessToken(oauth.verifyToken(accessToken), true, false, false);
response = oauth.doTokenExchange(TEST, accessToken, null, TEST_CLIENT, TEST_CLIENT_SECRET);
String exchangedTokenString = response.getAccessToken();
logger.debug("exchangedTokenString:" + exchangedTokenString);
@ -276,7 +276,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
OAuthClient.AuthorizationEndpointResponse authsEndpointResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(authsEndpointResponse.getCode(), TEST_CLIENT_SECRET);
String accessToken = tokenResponse.getAccessToken();
assertAccessToken(oauth.verifyToken(accessToken), true, false);
assertAccessToken(oauth.verifyToken(accessToken), true, false, true);
logger.debug("lightweight access token:" + accessToken);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
@ -292,7 +292,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
tokenResponse = oauth.doAccessTokenRequest(authsEndpointResponse.getCode(), TEST_CLIENT_SECRET);
accessToken = tokenResponse.getAccessToken();
logger.debug("access token:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, true);
assertAccessToken(oauth.verifyToken(accessToken), true, true, false);
} finally {
deleteProtocolMappers(protocolMappers);
}
@ -312,7 +312,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(authsEndpointResponse.getCode(), TEST_CLIENT_SECRET);
String accessToken = tokenResponse.getAccessToken();
logger.debug("access token:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, true);
assertAccessToken(oauth.verifyToken(accessToken), true, true, true);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String introspectResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
@ -337,7 +337,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
OAuthClient.AuthorizationEndpointResponse authsEndpointResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(authsEndpointResponse.getCode(), TEST_CLIENT_SECRET);
String accessToken = tokenResponse.getAccessToken();
assertAccessToken(oauth.verifyToken(accessToken), true, false);
assertAccessToken(oauth.verifyToken(accessToken), true, false, true);
logger.debug("lightweight access token:" + accessToken);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
@ -353,7 +353,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
tokenResponse = oauth.doAccessTokenRequest(authsEndpointResponse.getCode(), TEST_CLIENT_SECRET);
accessToken = tokenResponse.getAccessToken();
logger.debug("access token:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, true);
assertAccessToken(oauth.verifyToken(accessToken), true, true, false);
} finally {
deleteProtocolMappers(protocolMappers);
}
@ -373,7 +373,7 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(authsEndpointResponse.getCode(), TEST_CLIENT_SECRET);
String accessToken = tokenResponse.getAccessToken();
logger.debug("access token:" + accessToken);
assertAccessToken(oauth.verifyToken(accessToken), true, true);
assertAccessToken(oauth.verifyToken(accessToken), true, true, true);
oauth.clientId(RESOURCE_SERVER_CLIENT_ID);
String introspectResponse = oauth.introspectAccessTokenWithClientCredential(RESOURCE_SERVER_CLIENT_ID, RESOURCE_SERVER_CLIENT_PASSWORD, accessToken);
@ -441,10 +441,8 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
Assert.assertNotNull(token.getType());
if (isAuthCodeFlow) {
Assert.assertNotNull(token.getSessionId());
Assert.assertNotNull(token.getAuth_time());
} else {
Assert.assertNull(token.getSessionId());
Assert.assertNull(token.getAuth_time());
}
Assert.assertNotNull(token.getIssuedFor());
Assert.assertNotNull(token.getScope());
@ -452,16 +450,25 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
Assert.assertNotNull(token.getSubject());
}
private void assertBasicClaims(AccessToken token, boolean missing) {
if (missing) {
Assert.assertNull(token.getAuth_time());
} else {
Assert.assertNotNull(token.getAuth_time());
}
}
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 assertAccessToken(AccessToken token, boolean isAuthCodeFlow, boolean isAddToAccessToken) {
private void assertAccessToken(AccessToken token, boolean isAuthCodeFlow, boolean isAddToAccessToken, boolean missingBasicClaims) {
Assert.assertNull(token.getNonce());
assertMapperClaims(token, isAddToAccessToken, isAuthCodeFlow);
assertInitClaims(token, isAuthCodeFlow);
assertBasicClaims(token, missingBasicClaims);
}
private void assertTokenIntrospectionResponse(AccessToken token, boolean isAuthCodeFlow) {

View file

@ -421,7 +421,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
private void assertScopesSupportedMatchesWithRealm(OIDCConfigurationRepresentation oidcConfig) {
Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS,
OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS, OIDCLoginProtocolFactory.ACR_SCOPE,
OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS, OIDCLoginProtocolFactory.ACR_SCOPE, OIDCLoginProtocolFactory.BASIC_SCOPE,
OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE);
}

View file

@ -1158,8 +1158,32 @@
"consentRequired" : false,
"config" : { }
} ]
} ],
"defaultDefaultClientScopes" : [ "roles", "profile", "role_list", "email", "web-origins" ],
}, {
"id": "6e26ea6e-8d9e-4bb4-a335-df23baea8a89",
"name": "acr",
"description": "OpenID Connect scope for add acr (authentication context class reference) to the token",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "false",
"display.on.consent.screen": "false"
},
"protocolMappers": [
{
"id": "7b7b4a03-9b67-4a0c-a154-e36d8b2e251c",
"name": "acr loa level",
"protocol": "openid-connect",
"protocolMapper": "oidc-acr-mapper",
"consentRequired": false,
"config": {
"id.token.claim": "true",
"introspection.token.claim": "true",
"access.token.claim": "true"
}
}
]
}
],
"defaultDefaultClientScopes" : [ "roles", "profile", "role_list", "email", "web-origins", "acr" ],
"defaultOptionalClientScopes" : [ "phone", "offline_access", "address", "microprofile-jwt" ],
"browserSecurityHeaders" : {
"contentSecurityPolicyReportOnly" : "",
@ -2892,7 +2916,7 @@
"config" : { }
} ]
} ],
"defaultDefaultClientScopes" : [ "web-origins", "roles", "role_list", "email", "profile" ],
"defaultDefaultClientScopes" : [ "web-origins", "roles", "role_list", "email", "profile", "acr" ],
"defaultOptionalClientScopes" : [ "address", "offline_access", "microprofile-jwt", "phone" ],
"browserSecurityHeaders" : {
"contentSecurityPolicyReportOnly" : "",
@ -2953,7 +2977,7 @@
"id": "88cef18c-bcd8-40d2-9e7d-d257298317f2",
"providerId": "declarative-user-profile",
"subComponents": {},
"config": {
"config": {
"config-piece-0" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}}},{\"name\":\"email\",\"displayName\":\"${email}\",\"permissions\":{\"edit\":[\"admin\",\"user\"],\"view\":[\"admin\",\"user\"]},\"validations\":{\"email\":{},\"length\":{\"max\":255},\"pattern\":{\"pattern\":\"[a-zA-Z0-9!#$%&'*+\/=?^_`{|}~.-]+@example.nl\",\"error-message\":\"Invalid domain selected\"}},\"annotations\":{\"\":\"\"},\"required\":{\"roles\":[\"user\"]},\"group\":null},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}}},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}}}]}" ],
"config-pieces-count" : [ "1" ]
}
@ -5140,7 +5164,7 @@
}
} ]
} ],
"defaultDefaultClientScopes" : [ "profile", "role_list", "roles", "web-origins", "email" ],
"defaultDefaultClientScopes" : [ "profile", "role_list", "roles", "web-origins", "email", "acr" ],
"defaultOptionalClientScopes" : [ "offline_access", "phone", "microprofile-jwt", "address" ],
"browserSecurityHeaders" : {
"contentSecurityPolicyReportOnly" : "",