diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseAdvancedSettingsPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseAdvancedSettingsPage.ts index e8395c0383..b328af5bf9 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseAdvancedSettingsPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseAdvancedSettingsPage.ts @@ -55,6 +55,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { private scopesInput = "#scopes"; private storeTokensSwitch = "#storeTokens"; private storedTokensReadable = "#storedTokensReadable"; + private isAccessTokenJWT = "#isAccessTokenJWT"; private acceptsPromptNoneForwardFromClientSwitch = "#acceptsPromptNone"; private advancedSettingsToggle = ".pf-c-expandable-section__toggle"; private passLoginHintSwitch = "#passLoginHint"; @@ -112,6 +113,11 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { return this; } + public clickIsAccessTokenJWTSwitch() { + cy.get(this.isAccessTokenJWT).parent().click(); + return this; + } + public clickAcceptsPromptNoneForwardFromClientSwitch() { cy.get(this.acceptsPromptNoneForwardFromClientSwitch).parent().click(); return this; @@ -219,6 +225,11 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { return this; } + public assertIsAccessTokenJWTTurnedOn(isOn: boolean) { + super.assertSwitchStateOn(cy.get(this.isAccessTokenJWT).parent(), isOn); + return this; + } + public assertAcceptsPromptNoneForwardFromClientSwitchTurnedOn(isOn: boolean) { super.assertSwitchStateOn( cy.get(this.acceptsPromptNoneForwardFromClientSwitch).parent(), @@ -394,6 +405,8 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { this.assertStoreTokensSwitchTurnedOn(true); this.clickStoredTokensReadableSwitch(); this.assertStoredTokensReadableTurnedOn(true); + this.clickIsAccessTokenJWTSwitch(); + this.assertIsAccessTokenJWTTurnedOn(true); this.clickTrustEmailSwitch(); this.assertTrustEmailSwitchTurnedOn(true); this.clickAccountLinkingOnlySwitch(); @@ -406,8 +419,10 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { this.selectSyncModeOption(SyncModeOption.legacy); this.clickRevertBtn(); + cy.get(this.advancedSettingsToggle).scrollIntoView().click(); this.assertStoreTokensSwitchTurnedOn(false); this.assertStoredTokensReadableTurnedOn(false); + this.assertIsAccessTokenJWTTurnedOn(false); this.assertTrustEmailSwitchTurnedOn(false); this.assertAccountLinkingOnlySwitchTurnedOn(false); this.assertHideOnLoginPageSwitchTurnedOn(false); diff --git a/js/apps/admin-ui/public/locales/en/identity-providers-help.json b/js/apps/admin-ui/public/locales/en/identity-providers-help.json index ec5007677c..225b444fd1 100644 --- a/js/apps/admin-ui/public/locales/en/identity-providers-help.json +++ b/js/apps/admin-ui/public/locales/en/identity-providers-help.json @@ -15,6 +15,7 @@ "logoutUrl": "End session endpoint to use to logout user from external IDP.", "backchannelLogout": "Does the external IDP support backchannel logout?", "disableUserInfo": "Disable usage of User Info service to obtain additional user information? Default is to use this OIDC service.", + "isAccessTokenJWT": "The Access Token received from the Identity Provider is a JWT and its claims will be accessible for mappers.", "userInfoUrl": "The User Info Url. This is optional.", "issuer": "The issuer identifier for the issuer of the response. If not provided, no validation will be performed.", "scopes": "The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to 'openid'.", diff --git a/js/apps/admin-ui/public/locales/en/identity-providers.json b/js/apps/admin-ui/public/locales/en/identity-providers.json index 5977d3882a..5839772d57 100644 --- a/js/apps/admin-ui/public/locales/en/identity-providers.json +++ b/js/apps/admin-ui/public/locales/en/identity-providers.json @@ -99,6 +99,7 @@ "logoutUrl": "Logout URL", "backchannelLogout": "Backchannel logout", "disableUserInfo": "Disable user info", + "isAccessTokenJWT": "Access Token is JWT", "userInfoUrl": "User Info URL", "issuer": "Issuer", "scopes": "Scopes", diff --git a/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx index 9dce00dc48..a7c55d40dd 100644 --- a/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx @@ -132,6 +132,9 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => { )} + {isOIDC && ( + + )} params) { String requestedIssuer = params.getFirst(OAuth2Constants.SUBJECT_ISSUER); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcAccessTokenOnlyClaimsUserAttributeMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcAccessTokenOnlyClaimsUserAttributeMapperTest.java new file mode 100644 index 0000000000..aa36a4c0b6 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcAccessTokenOnlyClaimsUserAttributeMapperTest.java @@ -0,0 +1,29 @@ +package org.keycloak.testsuite.broker; + +import java.util.List; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; + +public class KcOidcAccessTokenOnlyClaimsUserAttributeMapperTest extends OidcUserAttributeMapperTest { + + @Override + protected BrokerConfiguration getBrokerConfiguration() { + return new KcOidcBrokerConfiguration() { + @Override + public List createProviderClients() { + List clientsRepList = super.createProviderClients(); + clientsRepList.stream() + .flatMap(clientRepresentation -> clientRepresentation.getProtocolMappers().stream()) + .map(ProtocolMapperRepresentation::getConfig) + .forEach(protocolMapperConfig -> { + protocolMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + protocolMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "false"); + protocolMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "false"); + }); + + return clientsRepList; + } + }; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java index 7d19eacdc8..59ea3a5d43 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java @@ -73,10 +73,10 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration { client.setSecret(CLIENT_SECRET); client.setRedirectUris(Collections.singletonList(getConsumerRoot() + - "/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_OIDC_ALIAS + "/endpoint/*")); + "/auth/realms/" + REALM_CONS_NAME + "/broker/" + getIDPAlias() + "/endpoint/*")); client.setAdminUrl(getConsumerRoot() + - "/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_OIDC_ALIAS + "/endpoint"); + "/auth/realms/" + REALM_CONS_NAME + "/broker/" + getIDPAlias() + "/endpoint"); OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setPostLogoutRedirectUris(Collections.singletonList("+")); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/OidcAccessTokenOnlyClaimsUserAttributeMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/OidcAccessTokenOnlyClaimsUserAttributeMapperTest.java new file mode 100644 index 0000000000..02111ee62e --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/OidcAccessTokenOnlyClaimsUserAttributeMapperTest.java @@ -0,0 +1,57 @@ +package org.keycloak.testsuite.broker; + +import static org.keycloak.testsuite.broker.BrokerTestTools.createIdentityProvider; + +import java.util.List; +import java.util.Map; +import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.broker.oidc.OIDCIdentityProviderFactory; +import org.keycloak.models.IdentityProviderSyncMode; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; + +public class OidcAccessTokenOnlyClaimsUserAttributeMapperTest extends OidcUserAttributeMapperTest { + + @Override + protected BrokerConfiguration getBrokerConfiguration() { + return new KcOidcBrokerConfiguration() { + + private static final String OIDC_IDP_ALIAS = "oidc-idp"; + + @Override + public IdentityProviderRepresentation setUpIdentityProvider( + IdentityProviderSyncMode syncMode) { + final IdentityProviderRepresentation idp = createIdentityProvider(OIDC_IDP_ALIAS, + OIDCIdentityProviderFactory.PROVIDER_ID); + + final Map config = idp.getConfig(); + applyDefaultConfiguration(config, syncMode); + config.put(OIDCIdentityProviderConfig.IS_ACCESS_TOKEN_JWT, "true"); + + return idp; + } + + @Override + public String getIDPAlias() { + return OIDC_IDP_ALIAS; + } + + @Override + public List createProviderClients() { + List clientsRepList = super.createProviderClients(); + clientsRepList.stream() + .flatMap(clientRepresentation -> clientRepresentation.getProtocolMappers().stream()) + .map(ProtocolMapperRepresentation::getConfig) + .forEach(protocolMapperConfig -> { + protocolMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + protocolMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "false"); + protocolMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "false"); + }); + + return clientsRepList; + } + }; + } +}