From 9804b53f3b68b4d89135f58ef9cbbea8f6b5654b Mon Sep 17 00:00:00 2001 From: Ricardo Martin Date: Tue, 26 Aug 2025 16:29:46 +0200 Subject: [PATCH] Use back keycloak-js instead of initiate login in the backend for account (#42035) Closes #40463 Signed-off-by: rmartinc (cherry picked from commit 360ff7050c290939d529e68b461ba61c7c11404a) --- .../account/messages/messages_en.properties | 20 +++- .../admin/messages/messages_en.properties | 18 ++++ js/libs/ui-shared/src/context/ErrorPage.tsx | 17 ++- .../ui-shared/src/context/KeycloakContext.tsx | 12 +-- .../resources/account/AccountConsole.java | 84 --------------- .../testsuite/account/AccountConsoleTest.java | 102 ------------------ .../actions/TermsAndConditionsTest.java | 2 +- .../broker/AbstractBaseBrokerTest.java | 2 +- .../policies/ClientPoliciesExecutorTest.java | 14 ++- .../testsuite/forms/AccountConsoleTest.java | 82 ++++++++++++++ .../admin/OrganizationInvitationLinkTest.java | 2 +- 11 files changed, 144 insertions(+), 211 deletions(-) delete mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountConsoleTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AccountConsoleTest.java diff --git a/js/apps/account-ui/maven-resources/theme/keycloak.v3/account/messages/messages_en.properties b/js/apps/account-ui/maven-resources/theme/keycloak.v3/account/messages/messages_en.properties index 138e7458555..e190a08c75f 100644 --- a/js/apps/account-ui/maven-resources/theme/keycloak.v3/account/messages/messages_en.properties +++ b/js/apps/account-ui/maven-resources/theme/keycloak.v3/account/messages/messages_en.properties @@ -215,4 +215,22 @@ organizationList=List of organizations domains=Domains refresh=Refresh termsAndConditionsDeclined=You need to accept the Terms and Conditions to continue -defaultLocale=Choose... \ No newline at end of file +defaultLocale=Choose... +# standard error responses OAuth +invalid_request=Invalid request +unauthorized_client=Unauthorized client +access_denied=Access denied +unsupported_response_type=Unsupported response type +invalid_scope=Invalid scope +server_error=Server error +temporarily_unavailable=Temporarily unavailable +# standard error responses OIDC +interaction_required=Interaction required +login_required=Login required +account_selection_required=Account selection required +consent_required=Consent required +invalid_request_uri=Invalid request uri +invalid_request_object=Invalid request object +request_not_supported=Request not supported +request_uri_not_supported=Request uri not supported +registration_not_supported=Registration not supported diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index 9f99d3011ae..04548671021 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -3502,3 +3502,21 @@ givenNameClaim=Given name Claim givenNameClaimHelp=The name of the claim from the JSON document returned by the user profile endpoint representing the user's given name. If not provided, defaults to `given_name`. familyNameClaim=Family name Claim familyNameClaimHelp=The name of the claim from the JSON document returned by the user profile endpoint representing the user's family name. If not provided, defaults to `family_name`. +# standard error responses OAuth +invalid_request=Invalid request +unauthorized_client=Unauthorized client +access_denied=Access denied +unsupported_response_type=Unsupported response type +invalid_scope=Invalid scope +server_error=Server error +temporarily_unavailable=Temporarily unavailable +# standard error responses OIDC +interaction_required=Interaction required +login_required=Login required +account_selection_required=Account selection required +consent_required=Consent required +invalid_request_uri=Invalid request uri +invalid_request_object=Invalid request object +request_not_supported=Request not supported +request_uri_not_supported=Request uri not supported +registration_not_supported=Registration not supported diff --git a/js/libs/ui-shared/src/context/ErrorPage.tsx b/js/libs/ui-shared/src/context/ErrorPage.tsx index 5035e7ec8c7..248bf94480a 100644 --- a/js/libs/ui-shared/src/context/ErrorPage.tsx +++ b/js/libs/ui-shared/src/context/ErrorPage.tsx @@ -7,18 +7,17 @@ import { TextContent, } from "@patternfly/react-core"; import { useTranslation } from "react-i18next"; -import { getNetworkErrorDescription } from "../utils/errors"; +import { getNetworkErrorMessage } from "../utils/errors"; type ErrorPageProps = { error?: unknown; }; export const ErrorPage = (props: ErrorPageProps) => { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const error = props.error; - const errorMessage = - getErrorMessage(error) || - getNetworkErrorDescription(error)?.replace(/\+/g, " "); + const errorMessage = getErrorMessage(error); + const networkErrorMessage = getNetworkErrorMessage(error); console.error(error); function onRetry() { @@ -29,7 +28,7 @@ export const ErrorPage = (props: ErrorPageProps) => { { {errorMessage ? ( {t(errorMessage)} + ) : networkErrorMessage && i18n.exists(networkErrorMessage) ? ( + {t(networkErrorMessage)} ) : ( {t("somethingWentWrongDescription")} )} @@ -52,10 +53,6 @@ export const ErrorPage = (props: ErrorPageProps) => { }; function getErrorMessage(error: unknown): string | null { - if (typeof error === "string") { - return error; - } - if (error instanceof Error) { return error.message; } diff --git a/js/libs/ui-shared/src/context/KeycloakContext.tsx b/js/libs/ui-shared/src/context/KeycloakContext.tsx index 060015e0f40..5746c6aefb1 100644 --- a/js/libs/ui-shared/src/context/KeycloakContext.tsx +++ b/js/libs/ui-shared/src/context/KeycloakContext.tsx @@ -67,7 +67,7 @@ export const KeycloakProvider = ({ const init = () => keycloak.init({ - onLoad: "check-sso", + onLoad: "login-required", pkceMethod: "S256", responseMode: "query", scope: environment.scope, @@ -80,14 +80,8 @@ export const KeycloakProvider = ({ calledOnce.current = true; }, [keycloak]); - const searchParams = new URLSearchParams(window.location.search); - - if (error || searchParams.get("error_description")) { - return ( - - ); + if (error) { + return ; } if (!init) { diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java index a9c8789451d..84ab3e8a838 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java @@ -3,13 +3,9 @@ package org.keycloak.services.resources.account; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.ServerErrorException; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.Status; -import jakarta.ws.rs.core.UriBuilder; import org.jboss.resteasy.reactive.NoCache; -import org.keycloak.OAuth2Constants; import org.keycloak.authentication.requiredactions.DeleteAccount; import org.keycloak.common.Profile; import org.keycloak.common.Version; @@ -18,7 +14,6 @@ import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderStorageProvider; import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.protocol.oidc.utils.PkceUtils; import org.keycloak.utils.SecureContextResolver; import org.keycloak.models.AccountRoles; import org.keycloak.models.ClientModel; @@ -28,7 +23,6 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; -import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.services.Urls; import org.keycloak.services.managers.AppAuthManager; @@ -46,18 +40,15 @@ import org.keycloak.theme.freemarker.FreeMarkerProvider; import org.keycloak.urls.UrlType; import org.keycloak.util.JsonSerialization; import org.keycloak.utils.MediaType; -import org.keycloak.utils.StringUtil; import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Scanner; -import java.util.UUID; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -110,10 +101,6 @@ public class AccountConsole implements AccountResourceProvider { @Path("{path:.*}") public Response getMainPage(@PathParam("path") String path) throws IOException, FreeMarkerException { - if (auth == null) { - return redirectToLogin(path); - } - return renderAccountConsole(); } @@ -232,77 +219,6 @@ public class AccountConsole implements AccountResourceProvider { return builder.build(); } - private Response redirectToLogin(String path) { - UriBuilder consoleUriBuilder = Urls.accountBase(session.getContext().getUri().getBaseUri()); - if (!StringUtil.isNullOrEmpty(path)) { - consoleUriBuilder.path(path); - } - var queryParameters = session.getContext().getUri().getQueryParameters(); - if (!queryParameters.isEmpty()) { - String referrer = queryParameters.getFirst("referrer"); - if (referrer != null) { - consoleUriBuilder.queryParam("referrer", referrer); - } - - String referrerUri = queryParameters.getFirst("referrer_uri"); - if (referrerUri != null) { - consoleUriBuilder.queryParam("referrer_uri", referrerUri); - } - } - URI targetUri = consoleUriBuilder.build(realm.getName()); - - String pkceChallenge; - try { - // Add PKCE parameters as it is required for the account-console client. - // Because the account console configuration requires PKCE, we need to send this with the redirect in order to not fail validations. - // The real PKCE challenge will be sent by the account-console OIDC client JavaScript integration. - String codeVerifier = UUID.randomUUID().toString(); - pkceChallenge = PkceUtils.generateS256CodeChallenge(codeVerifier); - } catch (Exception e) { - // this should never happen - throw new RuntimeException(e); - } - - UriBuilder uriBuilder = UriBuilder.fromUri(OIDCLoginProtocolService.authUrl(session.getContext().getUri()).build(realm.getName()).toString()) - .queryParam(OAuth2Constants.CLIENT_ID, Constants.ACCOUNT_CONSOLE_CLIENT_ID) - .queryParam(OAuth2Constants.REDIRECT_URI, targetUri) - // dummy state param to make it usable with secure-session client policy. - // Once bootstrapped the account-console frontend will send the actual state with the authorize request. - .queryParam(OAuth2Constants.STATE, UUID.randomUUID().toString()) - .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) - .queryParam(OAuth2Constants.CODE_CHALLENGE, pkceChallenge) - .queryParam(OAuth2Constants.CODE_CHALLENGE_METHOD, OAuth2Constants.PKCE_METHOD_S256); - - if (!queryParameters.isEmpty()) { - String error = queryParameters.getFirst(OAuth2Constants.ERROR); - if (error != null) { - String state = queryParameters.getFirst(OAuth2Constants.STATE); - if (state != null) { - // Omit the "state" parameter to make sure that account console displays the error (it may not be shown due the keycloak.js, which will not be able to find the "callback data" in the browser callbackStorage) - URI url = session.getContext().getUri(UrlType.FRONTEND) - .getRequestUriBuilder() - .replaceQueryParam(OAuth2Constants.STATE, null) - .build(); - return Response.status(302).location(url).build(); - } else { - try { - return renderAccountConsole(); - } catch (IOException | FreeMarkerException e) { - throw new ServerErrorException(Status.INTERNAL_SERVER_ERROR); - } - } - } - String scope = queryParameters.getFirst(OIDCLoginProtocol.SCOPE_PARAM); - if (StringUtil.isNotBlank(scope)) { - uriBuilder.queryParam(OIDCLoginProtocol.SCOPE_PARAM, scope); - } - } - - URI url = uriBuilder.build(); - - return Response.status(302).location(url).build(); - } - private Map supportedLocales(Properties messages) { return realm.getSupportedLocalesStream() .collect(Collectors.toMap(Function.identity(), l -> messages.getProperty("locale_" + l, l))); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountConsoleTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountConsoleTest.java deleted file mode 100644 index 03bdaa7cc94..00000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountConsoleTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.keycloak.testsuite.account; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URLDecoder; - -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.impl.client.HttpClientBuilder; -import org.jboss.arquillian.graphene.page.Page; -import org.junit.Test; -import org.keycloak.models.Constants; -import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; -import org.keycloak.testsuite.Assert; -import org.keycloak.testsuite.pages.LoginPage; - -public class AccountConsoleTest extends AbstractTestRealmKeycloakTest { - - @Page - protected LoginPage loginPage; - - @Override - public void configureTestRealm(RealmRepresentation testRealm) { - - } - - @Test - public void redirectToLoginIfNotAuthenticated() { - assertRedirectLocation(getAccount()); - } - - @Test - public void testScopesPresentInAuthorizationRequest() { - String expectedScopes = "phone address"; - String redirectLocation = URLDecoder.decode(assertRedirectLocation(getAccount(expectedScopes))); - Assert.assertTrue(redirectLocation.contains(expectedScopes)); - expectedScopes = "phone"; - redirectLocation = URLDecoder.decode(assertRedirectLocation(getAccount(expectedScopes))); - Assert.assertTrue(redirectLocation.contains(expectedScopes)); - Assert.assertTrue(!redirectLocation.contains("address")); - - // should render the account with the phone scope - driver.navigate().to(redirectLocation); - loginPage.login("test-user@localhost", "password"); - Assert.assertTrue(driver.getPageSource().contains("\"scope\": \"" + expectedScopes + "\"")); - - // should render the account with the address scope only - expectedScopes = "address"; - redirectLocation = URLDecoder.decode(assertRedirectLocation(getAccount(expectedScopes))); - driver.navigate().to(redirectLocation); - Assert.assertTrue(driver.getPageSource().contains("\"scope\": \"" + expectedScopes + "\"")); - - // should render the account with the phone and address scopes - expectedScopes = "phone address"; - redirectLocation = URLDecoder.decode(assertRedirectLocation(getAccount(expectedScopes))); - driver.navigate().to(redirectLocation); - Assert.assertTrue(driver.getPageSource().contains("\"scope\": \"" + expectedScopes + "\"")); - - // should keep previously requested scopes when not setting the scope parameter - redirectLocation = URLDecoder.decode(assertRedirectLocation(getAccount())); - driver.navigate().to(redirectLocation); - Assert.assertTrue(driver.getPageSource().contains("\"scope\": \"" + expectedScopes + "\"")); - } - - private CloseableHttpResponse getAccount() { - return getAccount(null); - } - - private CloseableHttpResponse getAccount(String scope) { - try { - var uriBuilder = new URIBuilder(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/test/account"); - - if (scope != null) { - uriBuilder.setParameter(OIDCLoginProtocol.SCOPE_PARAM, scope); - } - - var request = new HttpGet(uriBuilder.build()); - - try (var client = HttpClientBuilder.create().disableRedirectHandling().build()) { - return client.execute(request); - } - } catch (URISyntaxException | IOException e) { - throw new RuntimeException(e); - } - } - - private String assertRedirectLocation(CloseableHttpResponse Account) { - try (var response = Account) { - int statusCode = response.getStatusLine().getStatusCode(); - Assert.assertEquals(302, statusCode); - String expectedLoginUrlPart = "/realms/" + oauth.getRealm() + "/protocol/openid-connect/auth?client_id=" + Constants.ACCOUNT_CONSOLE_CLIENT_ID; - String redirectLocation = response.getFirstHeader("Location").getValue(); - Assert.assertTrue(redirectLocation.contains(expectedLoginUrlPart)); - return redirectLocation; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java index 09fe384e8f2..12cad5177e8 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java @@ -187,7 +187,7 @@ public class TermsAndConditionsTest extends AbstractChangeImportedUserPasswordsT attributes.get(TermsAndConditions.USER_ATTRIBUTE)); } assertThat(DroneUtils.getCurrentDriver().getTitle(), equalTo("Account Management")); - Assert.assertTrue(DroneUtils.getCurrentDriver().getPageSource().contains("You need to accept the Terms and Conditions to continue")); + Assert.assertTrue(DroneUtils.getCurrentDriver().getPageSource().contains("Access denied")); Assert.assertFalse(DroneUtils.getCurrentDriver().getPageSource().contains("An unexpected error occurred")); WebElement tryAgainButton = DroneUtils.getCurrentDriver().findElement(By.tagName("button")); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java index e0547eb0c00..bae2ef2e133 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java @@ -345,7 +345,7 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest { // Completely logout from realm and confirm logout if present protected void logoutFromRealm(String contextRoot, String realm, String initiatingIdp, String idTokenHint, String clientId, String redirectUri) { - final String defaultRedirectUri = redirectUri != null ? redirectUri : getAccountUrl(contextRoot, realm); + final String defaultRedirectUri = redirectUri != null ? redirectUri : oauth.loginForm().build(); final String defaultClientId = (idTokenHint == null && clientId == null) ? "test-app" : clientId; executeLogoutFromRealm(contextRoot, realm, initiatingIdp, idTokenHint, defaultClientId, defaultRedirectUri); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExecutorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExecutorTest.java index f5419fade09..251a7f6b5c0 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExecutorTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExecutorTest.java @@ -73,6 +73,7 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.utils.OIDCResponseMode; import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.AuthorizationResponseToken; import org.keycloak.representations.IDToken; @@ -115,7 +116,9 @@ import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder; import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse; +import org.keycloak.testsuite.util.oauth.OAuthClient; import org.keycloak.testsuite.util.oauth.ParResponse; +import org.keycloak.testsuite.util.oauth.PkceGenerator; import org.keycloak.testsuite.util.RoleBuilder; import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.util.JsonSerialization; @@ -933,9 +936,16 @@ public class ClientPoliciesExecutorTest extends AbstractClientPoliciesTest { updatePolicies(json); // Test account-console is loaded successfully when "secure-session-enforce" executor is present - appPage.open(); - appPage.openAccount(); + oauth.client(Constants.ACCOUNT_CONSOLE_CLIENT_ID) + .redirectUri(OAuthClient.AUTH_SERVER_ROOT + "/realms/test/account/") + .responseMode(OIDCResponseMode.QUERY.value()) + .loginForm() + .state(KeycloakModelUtils.generateId()) + .nonce(KeycloakModelUtils.generateId()) + .codeChallenge(PkceGenerator.s256()) + .open(); loginPage.assertCurrent(); + Assert.assertEquals("Sign in to your account", loginPage.getTitleText()); } @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AccountConsoleTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AccountConsoleTest.java new file mode 100644 index 00000000000..7bc3bb27fc4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/AccountConsoleTest.java @@ -0,0 +1,82 @@ +package org.keycloak.testsuite.forms; + +import java.net.URISyntaxException; + +import org.apache.http.client.utils.URIBuilder; +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Test; +import org.keycloak.models.Constants; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.testsuite.AbstractChangeImportedUserPasswordsTest; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.arquillian.annotation.IgnoreBrowserDriver; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.util.WaitUtils; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.firefox.FirefoxDriver; + +// real browser needed as it needs the account console and keycloak-js +@IgnoreBrowserDriver(value={ChromeDriver.class, FirefoxDriver.class}, negate=true) +public class AccountConsoleTest extends AbstractChangeImportedUserPasswordsTest { + + @Page + protected LoginPage loginPage; + + @Test + public void redirectToLoginIfNotAuthenticated() { + driver.navigate().to(getAccount()); + WaitUtils.waitForPageToLoad(); + loginPage.assertCurrent(); + Assert.assertTrue(driver.getCurrentUrl().contains("client_id=" + Constants.ACCOUNT_CONSOLE_CLIENT_ID)); + } + + @Test + public void testScopesPresentInAuthorizationRequest() { + String expectedScopes = "openid phone"; + String redirectLocation = getAccount(expectedScopes); + + // should render the account with the phone scope + driver.navigate().to(redirectLocation); + WaitUtils.waitForPageToLoad(); + loginPage.login("test-user@localhost", getPassword("test-user@localhost")); + Assert.assertTrue(driver.getPageSource().contains("\"scope\": \"" + expectedScopes + "\"")); + + // should render the account with the address scope only + expectedScopes = "openid address"; + redirectLocation = getAccount(expectedScopes); + driver.navigate().to(redirectLocation); + WaitUtils.waitForPageToLoad(); + Assert.assertTrue(driver.getPageSource().contains("\"scope\": \"" + expectedScopes + "\"")); + + // should render the account with the phone and address scopes + expectedScopes = "openid phone address"; + redirectLocation = getAccount(expectedScopes); + driver.navigate().to(redirectLocation); + WaitUtils.waitForPageToLoad(); + Assert.assertTrue(driver.getPageSource().contains("\"scope\": \"" + expectedScopes + "\"")); + + // should keep previously requested scopes when not setting the scope parameter + redirectLocation = getAccount(); + driver.navigate().to(redirectLocation); + WaitUtils.waitForPageToLoad(); + Assert.assertTrue(driver.getPageSource().contains("\"scope\": \"" + expectedScopes + "\"")); + } + + private String getAccount() { + return getAccount(null); + } + + private String getAccount(String scope) { + try { + var uriBuilder = new URIBuilder(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/test/account"); + + if (scope != null) { + uriBuilder.setParameter(OIDCLoginProtocol.SCOPE_PARAM, scope); + } + + return uriBuilder.build().toString(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationInvitationLinkTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationInvitationLinkTest.java index 5936a53f85e..0be69caf4b1 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationInvitationLinkTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/organization/admin/OrganizationInvitationLinkTest.java @@ -424,7 +424,7 @@ public class OrganizationInvitationLinkTest extends AbstractOrganizationTest { } private void acceptInvitation(OrganizationResource organization, UserRepresentation user) throws MessagingException, IOException { - acceptInvitation(organization, user, "Sign in to"); + acceptInvitation(organization, user, "Account Management"); } private void acceptInvitation(OrganizationResource organization, UserRepresentation user, String pageTitle) throws MessagingException, IOException {