From b70468341e41c2edc4d2396c90564b0eef0ef847 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 28 Aug 2018 14:29:40 +0200 Subject: [PATCH] KEYCLOAK-7470 Ability to order client scopes --- .../org/keycloak/models/ClientScopeModel.java | 12 +++- .../org/keycloak/models/OrderedModel.java | 51 +++++++++++++++++ .../model/AccountFederatedIdentityBean.java | 52 +++-------------- .../freemarker/model/ApplicationsBean.java | 16 ++++-- .../model/IdentityProviderBean.java | 56 ++++-------------- .../freemarker/model/OAuthGrantBean.java | 22 +++++-- .../model/IdentityProviderBeanTest.java | 49 +++++++++++----- .../testsuite/oauth/OAuthGrantTest.java | 57 +++++++++++++++++++ .../messages/admin-messages_en.properties | 2 + .../partials/client-scope-detail.html | 7 +++ .../resources/partials/client-scope-list.html | 2 + 11 files changed, 214 insertions(+), 112 deletions(-) create mode 100644 server-spi/src/main/java/org/keycloak/models/OrderedModel.java diff --git a/server-spi/src/main/java/org/keycloak/models/ClientScopeModel.java b/server-spi/src/main/java/org/keycloak/models/ClientScopeModel.java index 28c098760b..1384271a2a 100755 --- a/server-spi/src/main/java/org/keycloak/models/ClientScopeModel.java +++ b/server-spi/src/main/java/org/keycloak/models/ClientScopeModel.java @@ -23,7 +23,7 @@ import java.util.Map; * @author Bill Burke * @version $Revision: 1 $ */ -public interface ClientScopeModel extends ProtocolMapperContainerModel, ScopeContainerModel { +public interface ClientScopeModel extends ProtocolMapperContainerModel, ScopeContainerModel, OrderedModel { String getId(); String getName(); @@ -48,6 +48,7 @@ public interface ClientScopeModel extends ProtocolMapperContainerModel, ScopeCon String DISPLAY_ON_CONSENT_SCREEN = "display.on.consent.screen"; String CONSENT_SCREEN_TEXT = "consent.screen.text"; + String GUI_ORDER = "gui.order"; default boolean isDisplayOnConsentScreen() { String displayVal = getAttribute(DISPLAY_ON_CONSENT_SCREEN); @@ -71,5 +72,14 @@ public interface ClientScopeModel extends ProtocolMapperContainerModel, ScopeCon setAttribute(CONSENT_SCREEN_TEXT, consentScreenText); } + @Override + default String getGuiOrder() { + return getAttribute(GUI_ORDER); + } + + default void setGuiOrder(String guiOrder) { + setAttribute(GUI_ORDER, guiOrder); + } + } diff --git a/server-spi/src/main/java/org/keycloak/models/OrderedModel.java b/server-spi/src/main/java/org/keycloak/models/OrderedModel.java new file mode 100644 index 0000000000..3134ea5c7b --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/OrderedModel.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 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.models; + +import java.util.Comparator; + +/** + * @author Marek Posolda + */ +public interface OrderedModel { + + String getGuiOrder(); + + + class OrderedModelComparator implements Comparator { + + @Override + public int compare(OM o1, OM o2) { + int o1order = parseOrder(o1); + int o2order = parseOrder(o2); + + return o1order - o2order; + } + + private int parseOrder(OM model) { + if (model != null && model.getGuiOrder() != null) { + try { + return Integer.parseInt(model.getGuiOrder()); + } catch (NumberFormatException e) { + // ignore it and use default + } + } + return 10000; + } + } +} diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java index e46264e96d..9cdb3ac851 100755 --- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java +++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java @@ -20,19 +20,16 @@ package org.keycloak.forms.account.freemarker.model; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OrderedModel; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.services.resources.account.AccountFormService; -import org.keycloak.services.Urls; -import javax.ws.rs.core.UriBuilder; import java.net.URI; -import java.util.Comparator; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.TreeSet; /** * @author Marek Posolda @@ -40,7 +37,9 @@ import java.util.TreeSet; */ public class AccountFederatedIdentityBean { - private final List identities; + private static OrderedModel.OrderedModelComparator IDP_COMPARATOR_INSTANCE = new OrderedModel.OrderedModelComparator<>(); + + private final List identities = new ArrayList<>(); private final boolean removeLinkPossible; private final KeycloakSession session; @@ -50,7 +49,6 @@ public class AccountFederatedIdentityBean { List identityProviders = realm.getIdentityProviders(); Set identities = session.users().getFederatedIdentities(user, realm); - Set orderedSet = new TreeSet<>(IdentityProviderComparator.INSTANCE); int availableIdentities = 0; if (identityProviders != null && !identityProviders.isEmpty()) { for (IdentityProviderModel provider : identityProviders) { @@ -68,11 +66,11 @@ public class AccountFederatedIdentityBean { String displayName = KeycloakModelUtils.getIdentityProviderDisplayName(session, provider); FederatedIdentityEntry entry = new FederatedIdentityEntry(identity, displayName, provider.getAlias(), provider.getAlias(), provider.getConfig() != null ? provider.getConfig().get("guiOrder") : null); - orderedSet.add(entry); + this.identities.add(entry); } } - this.identities = new LinkedList(orderedSet); + this.identities.sort(IDP_COMPARATOR_INSTANCE); // Removing last social provider is not possible if you don't have other possibility to authenticate this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountFormService.isPasswordSet(session, realm, user); @@ -95,7 +93,7 @@ public class AccountFederatedIdentityBean { return removeLinkPossible; } - public class FederatedIdentityEntry { + public class FederatedIdentityEntry implements OrderedModel { private FederatedIdentityModel federatedIdentityModel; private final String providerId; @@ -132,6 +130,7 @@ public class AccountFederatedIdentityBean { return federatedIdentityModel != null; } + @Override public String getGuiOrder() { return guiOrder; } @@ -141,38 +140,5 @@ public class AccountFederatedIdentityBean { } } - - public static class IdentityProviderComparator implements Comparator { - public static IdentityProviderComparator INSTANCE = new IdentityProviderComparator(); - - private IdentityProviderComparator() { - - } - - @Override - public int compare(FederatedIdentityEntry o1, FederatedIdentityEntry o2) { - - int o1order = parseOrder(o1); - int o2order = parseOrder(o2); - - if (o1order > o2order) - return 1; - else if (o1order < o2order) - return -1; - - return 1; - } - - private int parseOrder(FederatedIdentityEntry ip) { - if (ip != null && ip.getGuiOrder() != null) { - try { - return Integer.parseInt(ip.getGuiOrder()); - } catch (NumberFormatException e) { - // ignore it and use defaulr - } - } - return 10000; - } - } } diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java index 0e20559810..625580d575 100755 --- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java +++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java @@ -18,10 +18,12 @@ package org.keycloak.forms.account.freemarker.model; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.forms.login.freemarker.model.OAuthGrantBean; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OrderedModel; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; @@ -36,6 +38,8 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; /** * @author Marek Posolda @@ -72,18 +76,18 @@ public class ApplicationsBean { MultivaluedHashMap resourceRolesAvailable = new MultivaluedHashMap(); processRoles(availableRoles, realmRolesAvailable, resourceRolesAvailable); - List clientScopesGranted = new LinkedList(); + List orderedScopes = new ArrayList<>(); if (client.isConsentRequired()) { UserConsentModel consent = session.users().getConsentByClient(realm, user.getId(), client.getId()); if (consent != null) { - - for (ClientScopeModel clientScope : consent.getGrantedClientScopes()) { - String consentText = clientScope.getConsentScreenText(); - clientScopesGranted.add(consentText); - } + orderedScopes.addAll(consent.getGrantedClientScopes()); + orderedScopes.sort(new OrderedModel.OrderedModelComparator<>()); } } + List clientScopesGranted = orderedScopes.stream() + .map(ClientScopeModel::getConsentScreenText) + .collect(Collectors.toList()); List additionalGrants = new ArrayList<>(); if (offlineClients.contains(client)) { diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java index 986d0efc21..125c875f26 100755 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/IdentityProviderBean.java @@ -18,18 +18,15 @@ package org.keycloak.forms.login.freemarker.model; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OrderedModel; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.services.Urls; -import javax.ws.rs.core.UriInfo; import java.net.URI; -import java.util.Comparator; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.TreeSet; /** * @author Stian Thorgersen @@ -37,6 +34,8 @@ import java.util.TreeSet; */ public class IdentityProviderBean { + public static OrderedModel.OrderedModelComparator IDP_COMPARATOR_INSTANCE = new OrderedModel.OrderedModelComparator<>(); + private boolean displaySocial; private List providers; private RealmModel realm; @@ -47,21 +46,22 @@ public class IdentityProviderBean { this.session = session; if (!identityProviders.isEmpty()) { - Set orderedSet = new TreeSet<>(IdentityProviderComparator.INSTANCE); + List orderedList = new ArrayList<>(); for (IdentityProviderModel identityProvider : identityProviders) { if (identityProvider.isEnabled() && !identityProvider.isLinkOnly()) { - addIdentityProvider(orderedSet, realm, baseURI, identityProvider); + addIdentityProvider(orderedList, realm, baseURI, identityProvider); } } - if (!orderedSet.isEmpty()) { - providers = new LinkedList<>(orderedSet); + if (!orderedList.isEmpty()) { + orderedList.sort(IDP_COMPARATOR_INSTANCE); + providers = orderedList; displaySocial = true; } } } - private void addIdentityProvider(Set orderedSet, RealmModel realm, URI baseURI, IdentityProviderModel identityProvider) { + private void addIdentityProvider(List orderedSet, RealmModel realm, URI baseURI, IdentityProviderModel identityProvider) { String loginUrl = Urls.identityProviderAuthnRequest(baseURI, identityProvider.getAlias(), realm.getName()).toString(); String displayName = KeycloakModelUtils.getIdentityProviderDisplayName(session, identityProvider); Map config = identityProvider.getConfig(); @@ -81,7 +81,7 @@ public class IdentityProviderBean { return realm.isRegistrationAllowed() || displaySocial; } - public static class IdentityProvider { + public static class IdentityProvider implements OrderedModel { private final String alias; private final String providerId; // This refer to providerType (facebook, google, etc.) @@ -109,6 +109,7 @@ public class IdentityProviderBean { return providerId; } + @Override public String getGuiOrder() { return guiOrder; } @@ -118,37 +119,4 @@ public class IdentityProviderBean { } } - public static class IdentityProviderComparator implements Comparator { - - public static IdentityProviderComparator INSTANCE = new IdentityProviderComparator(); - - private IdentityProviderComparator() { - - } - - @Override - public int compare(IdentityProvider o1, IdentityProvider o2) { - - int o1order = parseOrder(o1); - int o2order = parseOrder(o2); - - if (o1order > o2order) - return 1; - else if (o1order < o2order) - return -1; - - return 1; - } - - private int parseOrder(IdentityProvider ip) { - if (ip != null && ip.getGuiOrder() != null) { - try { - return Integer.parseInt(ip.getGuiOrder()); - } catch (NumberFormatException e) { - // ignore it and use defaulr - } - } - return 10000; - } - } } diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/OAuthGrantBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/OAuthGrantBean.java index 97d8e4455c..4c5d9d5cea 100755 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/OAuthGrantBean.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/OAuthGrantBean.java @@ -18,16 +18,22 @@ package org.keycloak.forms.login.freemarker.model; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientScopeModel; +import org.keycloak.models.OrderedModel; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Set; +import java.util.TreeSet; /** * @author Viliam Rockai */ public class OAuthGrantBean { - private List clientScopesRequested = new LinkedList<>(); + private static OrderedModel.OrderedModelComparator COMPARATOR_INSTANCE = new OrderedModel.OrderedModelComparator<>(); + + private List clientScopesRequested = new ArrayList<>(); private String code; private ClientModel client; @@ -36,8 +42,9 @@ public class OAuthGrantBean { this.client = client; for (ClientScopeModel clientScope : clientScopesRequested) { - this.clientScopesRequested.add(new ClientScopeEntry(clientScope.getConsentScreenText())); + this.clientScopesRequested.add(new ClientScopeEntry(clientScope.getConsentScreenText(), clientScope.getGuiOrder())); } + this.clientScopesRequested.sort(COMPARATOR_INSTANCE); } public String getCode() { @@ -56,16 +63,23 @@ public class OAuthGrantBean { // Converting ClientScopeModel due the freemarker limitations. It's not able to read "getConsentScreenText" default method defined on interface - public static class ClientScopeEntry { + public static class ClientScopeEntry implements OrderedModel { private final String consentScreenText; + private final String guiOrder; - private ClientScopeEntry(String consentScreenText) { + private ClientScopeEntry(String consentScreenText, String guiOrder) { this.consentScreenText = consentScreenText; + this.guiOrder = guiOrder; } public String getConsentScreenText() { return consentScreenText; } + + @Override + public String getGuiOrder() { + return guiOrder; + } } } diff --git a/services/src/test/java/org/keycloak/test/login/freemarker/model/IdentityProviderBeanTest.java b/services/src/test/java/org/keycloak/test/login/freemarker/model/IdentityProviderBeanTest.java index 0598ef635c..653228dd12 100755 --- a/services/src/test/java/org/keycloak/test/login/freemarker/model/IdentityProviderBeanTest.java +++ b/services/src/test/java/org/keycloak/test/login/freemarker/model/IdentityProviderBeanTest.java @@ -16,10 +16,14 @@ */ package org.keycloak.test.login.freemarker.model; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import org.junit.Assert; import org.junit.Test; +import org.keycloak.forms.login.freemarker.model.IdentityProviderBean; import org.keycloak.forms.login.freemarker.model.IdentityProviderBean.IdentityProvider; -import org.keycloak.forms.login.freemarker.model.IdentityProviderBean.IdentityProviderComparator; /** * Unit test for {@link org.keycloak.forms.login.freemarker.model.IdentityProviderBean} @@ -35,32 +39,49 @@ public class IdentityProviderBeanTest { IdentityProvider o1 = new IdentityProvider("alias1", "displayName1", "id1", "ur1", null); IdentityProvider o2 = new IdentityProvider("alias2", "displayName2", "id2", "ur2", null); - // guiOrder not defined at any object - first is always lower - Assert.assertEquals(1, IdentityProviderComparator.INSTANCE.compare(o1, o2)); - Assert.assertEquals(1, IdentityProviderComparator.INSTANCE.compare(o2, o1)); + // guiOrder not defined at any object + Assert.assertEquals(0, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o1, o2)); + Assert.assertEquals(0, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o2, o1)); - // guiOrder is not a number so it is same as not defined - first is always lower + // guiOrder is not a number so it is same as not defined o1 = new IdentityProvider("alias1", "displayName1", "id1", "ur1", "not a number"); - Assert.assertEquals(1, IdentityProviderComparator.INSTANCE.compare(o1, o2)); - Assert.assertEquals(1, IdentityProviderComparator.INSTANCE.compare(o2, o1)); + Assert.assertEquals(0, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o1, o2)); + Assert.assertEquals(0, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o2, o1)); // guiOrder is defined for one only to it is always first o1 = new IdentityProvider("alias1", "displayName1", "id1", "ur1", "0"); - Assert.assertEquals(-1, IdentityProviderComparator.INSTANCE.compare(o1, o2)); - Assert.assertEquals(1, IdentityProviderComparator.INSTANCE.compare(o2, o1)); + Assert.assertEquals(-10000, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o1, o2)); + Assert.assertEquals(10000, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o2, o1)); - // guiOrder is defined for both but is same - first is always lower + // guiOrder is defined for both but is same o1 = new IdentityProvider("alias1", "displayName1", "id1", "ur1", "0"); o2 = new IdentityProvider("alias2", "displayName2", "id2", "ur2", "0"); - Assert.assertEquals(1, IdentityProviderComparator.INSTANCE.compare(o1, o2)); - Assert.assertEquals(1, IdentityProviderComparator.INSTANCE.compare(o2, o1)); + Assert.assertEquals(0, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o1, o2)); + Assert.assertEquals(0, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o2, o1)); // guiOrder is reflected o1 = new IdentityProvider("alias1", "displayName1", "id1", "ur1", "0"); o2 = new IdentityProvider("alias2", "displayName2", "id2", "ur2", "1"); - Assert.assertEquals(-1, IdentityProviderComparator.INSTANCE.compare(o1, o2)); - Assert.assertEquals(1, IdentityProviderComparator.INSTANCE.compare(o2, o1)); + Assert.assertEquals(-1, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o1, o2)); + Assert.assertEquals(1, IdentityProviderBean.IDP_COMPARATOR_INSTANCE.compare(o2, o1)); } + + @Test + public void testIdentityProviderComparatorForEqualObjects() { + IdentityProvider o1 = new IdentityProvider("alias1", "displayName1", "id1", "ur1", null); + IdentityProvider o2 = new IdentityProvider("alias2", "displayName2", "id2", "ur2", null); + + // Gui order is not specified on the objects, but those are 2 different objects. Assert we have 2 items in the list and first is lower + List idp2 = new ArrayList<>(); + idp2.add(o1); + idp2.add(o2); + idp2.sort(IdentityProviderBean.IDP_COMPARATOR_INSTANCE); + Assert.assertEquals(2, idp2.size()); + Iterator itr2 = idp2.iterator(); + Assert.assertEquals("alias1", itr2.next().getAlias()); + Assert.assertEquals("alias2", itr2.next().getAlias()); + } + } 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 c90c748ff0..e956c2032f 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 @@ -23,11 +23,13 @@ import org.junit.Rule; import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.ClientScopeResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.common.constants.KerberosConstants; import org.keycloak.events.Details; import org.keycloak.events.EventType; +import org.keycloak.models.ClientScopeModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.ClientRepresentation; @@ -402,4 +404,59 @@ public class OAuthGrantTest extends AbstractKeycloakTest { Assert.assertEquals(backToAppLink, thirdParty.getBaseUrl()); } + + // KEYCLOAK-7470 + @Test + public void oauthGrantOrderedClientScopes() throws Exception { + // Add GUI Order to client scopes --- email=1, profile=2 + RealmResource appRealm = adminClient.realm(REALM_NAME); + + ClientScopeResource emailScope = ApiUtil.findClientScopeByName(appRealm, "email"); + ClientScopeRepresentation emailRep = emailScope.toRepresentation(); + emailRep.getAttributes().put(ClientScopeModel.GUI_ORDER, "1"); + emailScope.update(emailRep); + + ClientScopeResource profileScope = ApiUtil.findClientScopeByName(appRealm, "profile"); + ClientScopeRepresentation profileRep = profileScope.toRepresentation(); + profileRep.getAttributes().put(ClientScopeModel.GUI_ORDER, "2"); + profileScope.update(profileRep); + + // Display consent screen --- assert email, then profile + oauth.clientId(THIRD_PARTY_APP); + oauth.doLoginGrant("test-user@localhost", "password"); + + grantPage.assertCurrent(); + List displayedScopes = grantPage.getDisplayedGrants(); + Assert.assertEquals("Email address", displayedScopes.get(0)); + Assert.assertEquals("User profile", displayedScopes.get(1)); + grantPage.accept(); + + // Display account mgmt --- assert email, then profile + accountAppsPage.open(); + displayedScopes = accountAppsPage.getApplications().get(THIRD_PARTY_APP).getClientScopesGranted(); + Assert.assertEquals("Email address", displayedScopes.get(0)); + Assert.assertEquals("User profile", displayedScopes.get(1)); + + + // Update GUI Order --- email=3 + emailRep = emailScope.toRepresentation(); + emailRep.getAttributes().put(ClientScopeModel.GUI_ORDER, "3"); + emailScope.update(emailRep); + + + // Display account mgmt --- assert profile, then email + accountAppsPage.open(); + displayedScopes = accountAppsPage.getApplications().get(THIRD_PARTY_APP).getClientScopesGranted(); + Assert.assertEquals("User profile", displayedScopes.get(0)); + Assert.assertEquals("Email address", displayedScopes.get(1)); + + // Revoke grant and display consent screen --- assert profile, then email + accountAppsPage.revokeGrant(THIRD_PARTY_APP); + oauth.openLoginForm(); + grantPage.assertCurrent(); + displayedScopes = grantPage.getDisplayedGrants(); + Assert.assertEquals("User profile", displayedScopes.get(0)); + Assert.assertEquals("Email address", displayedScopes.get(1)); + } + } diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index ea78b5e121..8eb6ead960 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -829,6 +829,8 @@ client-scope.display-on-consent-screen=Display On Consent Screen client-scope.display-on-consent-screen.tooltip=If on, and this client scope is added to some client with consent required, then the text specified by 'Consent Screen Text' will be displayed on consent screen. If off, then this client scope won't be displayed on consent screen client-scope.consent-screen-text=Consent Screen Text client-scope.consent-screen-text.tooltip=Text, which will be shown on consent screen when this client scope is added to some client with consent required. Defaults to name of client scope if it's not filled +client-scope.gui-order=GUI order +client-scope.gui-order.tooltip=Specify order of the provider in GUI (e.g. in Consent page) as integer add-user-federation-provider=Add user federation provider add-user-storage-provider=Add user storage provider diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-detail.html index 59b1dd9e1d..dcd2826228 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-detail.html @@ -52,6 +52,13 @@ {{:: 'client-scope.consent-screen-text.tooltip' | translate}} +
+ +
+ +
+ {{:: 'client-scope.gui-order.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-list.html index c930fcca59..1f945fcc2b 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scope-list.html @@ -37,6 +37,7 @@ {{:: 'name' | translate}} {{:: 'protocol' | translate}} + {{:: 'gui-order' | translate}} {{:: 'actions' | translate}} @@ -44,6 +45,7 @@ {{clientScope.name}} {{clientScope.protocol}} + {{clientScope.attributes['gui.order']}} {{:: 'edit' | translate}} {{:: 'delete' | translate}}