keycloak-uncached

Merge pull request #4377 from patriot1burke/master token

8/9/2017 1:31:03 PM

Details

diff --git a/core/src/main/java/org/keycloak/OAuth2Constants.java b/core/src/main/java/org/keycloak/OAuth2Constants.java
index 6de35b8..2e585c3 100644
--- a/core/src/main/java/org/keycloak/OAuth2Constants.java
+++ b/core/src/main/java/org/keycloak/OAuth2Constants.java
@@ -101,7 +101,6 @@ public interface OAuth2Constants {
     String REFRESH_TOKEN_TYPE="urn:ietf:params:oauth:token-type:refresh_token";
     String JWT_TOKEN_TYPE="urn:ietf:params:oauth:token-type:jwt";
     String ID_TOKEN_TYPE="urn:ietf:params:oauth:token-type:id_token";
-    String TOKEN_EXCHANGER ="token-exchanger";
 
 
 }
diff --git a/services/src/main/java/org/keycloak/authorization/common/ClientModelIdentity.java b/services/src/main/java/org/keycloak/authorization/common/ClientModelIdentity.java
new file mode 100644
index 0000000..d2c6b67
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authorization/common/ClientModelIdentity.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016 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.authorization.common;
+
+import org.keycloak.authorization.attribute.Attributes;
+import org.keycloak.authorization.identity.Identity;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ClientModelIdentity implements Identity {
+    protected RealmModel realm;
+    protected ClientModel client;
+    protected UserModel serviceAccount;
+
+    public ClientModelIdentity(KeycloakSession session, ClientModel client) {
+        this.realm = client.getRealm();
+        this.client = client;
+        this.serviceAccount = session.users().getServiceAccount(client);
+    }
+
+    @Override
+    public String getId() {
+        return client.getId();
+    }
+
+    @Override
+    public Attributes getAttributes() {
+        MultivaluedHashMap map = new MultivaluedHashMap<String, String>();
+        if (serviceAccount != null) map.addAll(serviceAccount.getAttributes());
+        return Attributes.from(map);
+    }
+
+    @Override
+    public boolean hasRealmRole(String roleName) {
+        if (serviceAccount == null) return false;
+        RoleModel role = realm.getRole(roleName);
+        if (role == null) return false;
+        return serviceAccount.hasRole(role);
+    }
+
+    @Override
+    public boolean hasClientRole(String clientId, String roleName) {
+        if (serviceAccount == null) return false;
+        ClientModel client = realm.getClientByClientId(clientId);
+        RoleModel role = client.getRole(roleName);
+        if (role == null) return false;
+        return serviceAccount.hasRole(role);
+    }
+
+    @Override
+    public boolean hasRole(String roleName) {
+        throw new RuntimeException("Should not execute");
+    }
+
+    @Override
+    public boolean hasClientRole(String roleName) {
+        throw new RuntimeException("Should not execute");
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 28923df..fef17c6 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -593,45 +593,28 @@ public class TokenEndpoint {
             throw new ErrorResponseException(OAuthErrorException.INVALID_CLIENT, "Client requires user consent", Response.Status.BAD_REQUEST);
         }
 
-        boolean allowed = false;
-        UserModel serviceAccount = session.users().getServiceAccount(client);
-        if (serviceAccount != null) {
-            if (authResult.getToken().getAudience() == null) {
-                logger.debug("Client doesn't have service account");
+        boolean exchangeFromAllowed = false;
+        for (String aud : authResult.getToken().getAudience()) {
+            ClientModel audClient = realm.getClientByClientId(aud);
+            if (audClient == null) continue;
+            if (audClient.equals(client)) {
+                exchangeFromAllowed = true;
+                break;
             }
-            boolean tokenAllowed = false;
-            for (String aud : authResult.getToken().getAudience()) {
-                ClientModel audClient = realm.getClientByClientId(aud);
-                if (audClient == null) continue;
-                if (audClient.equals(client)) {
-                    tokenAllowed = true;
-                    break;
-                }
-                RoleModel audExchanger = audClient.getRole(OAuth2Constants.TOKEN_EXCHANGER);
-                if (audExchanger != null && serviceAccount.hasRole(audExchanger)) {
-                    tokenAllowed = true;
-                    break;
-                }
-            }
-            if (!tokenAllowed) {
-                logger.debug("Client does not have exchange rights for audience of token");
-            } else {
-                RoleModel targetExchangable = targetClient.getRole(OAuth2Constants.TOKEN_EXCHANGER);
-                RoleModel realmExchangeable = AdminPermissions.management(session, realm).getRealmManagementClient().getRole(OAuth2Constants.TOKEN_EXCHANGER);
-                allowed = (targetExchangable != null && serviceAccount.hasRole(targetExchangable)) || (realmExchangeable != null && serviceAccount.hasRole(realmExchangeable));
-                if (!allowed) {
-                    logger.debug("Client does not have exchange rights for target audience");
-                }
+            if (AdminPermissions.management(session, realm).clients().canExchangeFrom(client, audClient)) {
+                exchangeFromAllowed = true;
+                break;
             }
-
-        } else {
-            logger.debug("Client doesn't have service account");
         }
-
-        if (!allowed) {
+        if (!exchangeFromAllowed) {
+            logger.debug("Client does not have exchange rights for audience of provided token");
+            event.error(Errors.NOT_ALLOWED);
+            throw new ErrorResponseException(OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);
+        }
+        if (!AdminPermissions.management(session, realm).clients().canExchangeTo(client, targetClient)) {
+            logger.debug("Client does not have exchange rights for target audience");
             event.error(Errors.NOT_ALLOWED);
             throw new ErrorResponseException(OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);
-
         }
 
         AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, targetClient, false);
@@ -656,6 +639,8 @@ public class TokenEndpoint {
         TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, session, userSession, clientSession)
                 .generateAccessToken()
                 .generateRefreshToken();
+        responseBuilder.getAccessToken().issuedFor(client.getClientId());
+        responseBuilder.getRefreshToken().issuedFor(client.getClientId());
 
         String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE);
         if (TokenUtil.isOIDCRequest(scopeParam)) {
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 0581aec..6a26c69 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -683,6 +683,18 @@ public class TokenManager {
             this.clientSession = clientSession;
         }
 
+        public AccessToken getAccessToken() {
+            return accessToken;
+        }
+
+        public RefreshToken getRefreshToken() {
+            return refreshToken;
+        }
+
+        public IDToken getIdToken() {
+            return idToken;
+        }
+
         public AccessTokenResponseBuilder accessToken(AccessToken accessToken) {
             this.accessToken = accessToken;
             return this;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
index 7df5b5e..d8eb94a 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
@@ -27,6 +27,8 @@ import org.keycloak.models.ClientModel;
 public interface AdminPermissionManagement {
     public static final String MANAGE_SCOPE = "manage";
     public static final String VIEW_SCOPE = "view";
+    public static final String EXCHANGE_FROM_SCOPE="exchange-from";
+    public static final String EXCHANGE_TO_SCOPE="exchange-to";
 
     ClientModel getRealmManagementClient();
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java
index 8a6b76d..ccf9679 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissionManagement.java
@@ -41,6 +41,14 @@ public interface ClientPermissionManagement {
 
     Map<String, String> getPermissions(ClientModel client);
 
+    boolean canExchangeFrom(ClientModel authorizedClient, ClientModel from);
+
+    boolean canExchangeTo(ClientModel authorizedClient, ClientModel to);
+
+    Policy exchangeFromPermission(ClientModel client);
+
+    Policy exchangeToPermission(ClientModel client);
+
     Policy mapRolesPermission(ClientModel client);
 
     Policy mapRolesClientScopePermission(ClientModel client);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
index 2b1e234..bbb7bf4 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
@@ -18,23 +18,35 @@ package org.keycloak.services.resources.admin.permissions;
 
 import org.jboss.logging.Logger;
 import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.attribute.Attributes;
+import org.keycloak.authorization.common.ClientModelIdentity;
+import org.keycloak.authorization.common.DefaultEvaluationContext;
+import org.keycloak.authorization.identity.Identity;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.model.Resource;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.policy.evaluation.EvaluationContext;
 import org.keycloak.models.AdminRoles;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientTemplateModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.representations.AccessToken;
 import org.keycloak.services.ForbiddenException;
 
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
+import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.EXCHANGE_FROM_SCOPE;
+import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.EXCHANGE_TO_SCOPE;
+
 /**
  * Manages default policies for all users.
  *
@@ -79,6 +91,14 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
         return MAP_ROLES_COMPOSITE_SCOPE + ".permission.client." + client.getId();
     }
 
+    private String getExchangeToPermissionName(ClientModel client) {
+        return EXCHANGE_TO_SCOPE + ".permission.client." + client.getId();
+    }
+
+    private String getExchangeFromPermissionName(ClientModel client) {
+        return EXCHANGE_FROM_SCOPE + ".permission.client." + client.getId();
+    }
+
     private void initialize(ClientModel client) {
         ResourceServer server = root.findOrCreateResourceServer(client);
         Scope manageScope = manageScope(server);
@@ -93,18 +113,11 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
         if (mapRoleScope == null) {
             mapRoleScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_SCOPE, server);
         }
-        Scope mapRoleClientScope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_CLIENT_SCOPE, server.getId());
-        if (mapRoleClientScope == null) {
-            mapRoleClientScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_CLIENT_SCOPE, server);
-        }
-        Scope mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_COMPOSITE_SCOPE, server.getId());
-        if (mapRoleCompositeScope == null) {
-            mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_COMPOSITE_SCOPE, server);
-        }
-        Scope configureScope = authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId());
-        if (configureScope == null) {
-            configureScope = authz.getStoreFactory().getScopeStore().create(CONFIGURE_SCOPE, server);
-        }
+        Scope mapRoleClientScope = root.initializeScope(MAP_ROLES_CLIENT_SCOPE, server);
+        Scope mapRoleCompositeScope = root.initializeScope(MAP_ROLES_COMPOSITE_SCOPE, server);
+        Scope configureScope = root.initializeScope(CONFIGURE_SCOPE, server);
+        Scope exchangeFromScope = root.initializeScope(EXCHANGE_FROM_SCOPE, server);
+        Scope exchangeToScope = root.initializeScope(EXCHANGE_TO_SCOPE, server);
 
         String resourceName = getResourceName(client);
         Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
@@ -118,6 +131,8 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
             scopeset.add(mapRoleScope);
             scopeset.add(mapRoleClientScope);
             scopeset.add(mapRoleCompositeScope);
+            scopeset.add(exchangeFromScope);
+            scopeset.add(exchangeToScope);
             resource.updateScopes(scopeset);
         }
         String managePermissionName = getManagePermissionName(client);
@@ -150,6 +165,16 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
         if (mapRoleCompositePermission == null) {
             Helper.addEmptyScopePermission(authz, server, mapRoleCompositePermissionName, resource, mapRoleCompositeScope);
         }
+        String exchangeToPermissionName = getExchangeToPermissionName(client);
+        Policy exchangeToPermission = authz.getStoreFactory().getPolicyStore().findByName(exchangeToPermissionName, server.getId());
+        if (exchangeToPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, exchangeToPermissionName, resource, exchangeToScope);
+        }
+        String exchangeFromPermissionName = getExchangeFromPermissionName(client);
+        Policy exchangeFromPermission = authz.getStoreFactory().getPolicyStore().findByName(exchangeFromPermissionName, server.getId());
+        if (exchangeFromPermission == null) {
+            Helper.addEmptyScopePermission(authz, server, exchangeFromPermissionName, resource, exchangeFromScope);
+        }
     }
 
     private void deletePolicy(String name, ResourceServer server) {
@@ -169,6 +194,8 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
         deletePolicy(getMapRolesClientScopePermissionName(client), server);
         deletePolicy(getMapRolesCompositePermissionName(client), server);
         deletePolicy(getConfigurePermissionName(client), server);
+        deletePolicy(getExchangeToPermissionName(client), server);
+        deletePolicy(getExchangeFromPermissionName(client), server);
         Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());;
         if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId());
     }
@@ -196,6 +223,14 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
         return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.MANAGE_SCOPE, server.getId());
     }
 
+    private Scope exchangeFromScope(ResourceServer server) {
+        return authz.getStoreFactory().getScopeStore().findByName(EXCHANGE_FROM_SCOPE, server.getId());
+    }
+
+    private Scope exchangeToScope(ResourceServer server) {
+        return authz.getStoreFactory().getScopeStore().findByName(EXCHANGE_TO_SCOPE, server.getId());
+    }
+
     private Scope configureScope(ResourceServer server) {
         return authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId());
     }
@@ -271,16 +306,119 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
 
     @Override
     public Map<String, String> getPermissions(ClientModel client) {
-        Map<String, String> scopes = new HashMap<>();
-        scopes.put(MAP_ROLES_SCOPE,  mapRolesPermission(client).getId());
-        scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(client).getId());
-        scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId());
+        initialize(client);
+        Map<String, String> scopes = new LinkedHashMap<>();
         scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(client).getId());
         scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(client).getId());
         scopes.put(CONFIGURE_SCOPE, configurePermission(client).getId());
+        scopes.put(MAP_ROLES_SCOPE,  mapRolesPermission(client).getId());
+        scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(client).getId());
+        scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId());
+        scopes.put(EXCHANGE_FROM_SCOPE, exchangeFromPermission(client).getId());
+        scopes.put(EXCHANGE_TO_SCOPE, exchangeToPermission(client).getId());
         return scopes;
     }
 
+    @Override
+    public boolean canExchangeFrom(ClientModel authorizedClient, ClientModel from) {
+        if (!authorizedClient.equals(from)) {
+            ResourceServer server = resourceServer(from);
+            if (server == null) {
+                logger.debug("No resource server set up for target client");
+                return false;
+            }
+
+            Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getResourceName(from), server.getId());
+            if (resource == null) {
+                logger.debug("No resource object set up for target client");
+                return false;
+            }
+
+            Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getExchangeFromPermissionName(from), server.getId());
+            if (policy == null) {
+                logger.debug("No permission object set up for target client");
+                return false;
+            }
+
+            Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+            // if no policies attached to permission then just do default behavior
+            if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+                logger.debug("No policies set up for permission on target client");
+                return false;
+            }
+
+            Scope scope = exchangeFromScope(server);
+            if (scope == null) {
+                logger.debug(EXCHANGE_FROM_SCOPE + " not initialized");
+                return false;
+            }
+            ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient);
+            EvaluationContext context = new DefaultEvaluationContext(identity, session) {
+                @Override
+                public Map<String, Collection<String>> getBaseAttributes() {
+                    Map<String, Collection<String>> attributes = super.getBaseAttributes();
+                    attributes.put("kc.client.id", Arrays.asList(authorizedClient.getClientId()));
+                    return attributes;
+                }
+
+            };
+            return root.evaluatePermission(resource, scope, server, context);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean canExchangeTo(ClientModel authorizedClient, ClientModel to) {
+
+        if (!authorizedClient.equals(to)) {
+            ResourceServer server = resourceServer(to);
+            if (server == null) {
+                logger.debug("No resource server set up for target client");
+                return false;
+            }
+
+            Resource resource =  authz.getStoreFactory().getResourceStore().findByName(getResourceName(to), server.getId());
+            if (resource == null) {
+                logger.debug("No resource object set up for target client");
+                return false;
+            }
+
+            Policy policy = authz.getStoreFactory().getPolicyStore().findByName(getExchangeToPermissionName(to), server.getId());
+            if (policy == null) {
+                logger.debug("No permission object set up for target client");
+                return false;
+            }
+
+            Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+            // if no policies attached to permission then just do default behavior
+            if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+                logger.debug("No policies set up for permission on target client");
+                return false;
+            }
+
+            Scope scope = exchangeToScope(server);
+            if (scope == null) {
+                logger.debug(EXCHANGE_TO_SCOPE + " not initialized");
+                return false;
+            }
+            ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient);
+            EvaluationContext context = new DefaultEvaluationContext(identity, session) {
+                @Override
+                public Map<String, Collection<String>> getBaseAttributes() {
+                    Map<String, Collection<String>> attributes = super.getBaseAttributes();
+                    attributes.put("kc.client.id", Arrays.asList(authorizedClient.getClientId()));
+                    return attributes;
+                }
+
+            };
+            return root.evaluatePermission(resource, scope, server, context);
+        }
+        return true;
+    }
+
+
+
+
 
     @Override
     public boolean canManage(ClientModel client) {
@@ -464,6 +602,20 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
     }
 
     @Override
+    public Policy exchangeFromPermission(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return null;
+        return authz.getStoreFactory().getPolicyStore().findByName(getExchangeFromPermissionName(client), server.getId());
+    }
+
+    @Override
+    public Policy exchangeToPermission(ClientModel client) {
+        ResourceServer server = resourceServer(client);
+        if (server == null) return null;
+        return authz.getStoreFactory().getPolicyStore().findByName(getExchangeToPermissionName(client), server.getId());
+    }
+
+    @Override
     public Policy mapRolesPermission(ClientModel client) {
         ResourceServer server = resourceServer(client);
         if (server == null) return null;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
index a7e9f37..722ea1c 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
@@ -31,6 +31,7 @@ import org.keycloak.services.ForbiddenException;
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
@@ -242,11 +243,12 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
 
     @Override
     public Map<String, String> getPermissions(GroupModel group) {
-        Map<String, String> scopes = new HashMap<>();
+        initialize(group);
+        Map<String, String> scopes = new LinkedHashMap<>();
         scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(group).getId());
         scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(group).getId());
-        scopes.put(MANAGE_MEMBERS_SCOPE, manageMembersPermission(group).getId());
         scopes.put(VIEW_MEMBERS_SCOPE, viewMembersPermission(group).getId());
+        scopes.put(MANAGE_MEMBERS_SCOPE, manageMembersPermission(group).getId());
         scopes.put(MANAGE_MEMBERSHIP_SCOPE, manageMembershipPermission(group).getId());
         return scopes;
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
index 449530c..fe4a11f 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
@@ -277,6 +277,14 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
         return scope;
     }
 
+    public Scope initializeScope(String name, ResourceServer server) {
+        Scope scope  = authz.getStoreFactory().getScopeStore().findByName(name, server.getId());
+        if (scope == null) {
+            scope = authz.getStoreFactory().getScopeStore().create(name, server);
+        }
+        return scope;
+    }
+
 
 
     public Scope realmManageScope() {
@@ -307,10 +315,14 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
     }
 
     public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer, Identity identity) {
+        EvaluationContext context = new DefaultEvaluationContext(identity, session);
+        return evaluatePermission(resource, scope, resourceServer, context);
+    }
+
+    public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer, EvaluationContext context) {
         RealmModel oldRealm = session.getContext().getRealm();
         try {
             session.getContext().setRealm(realm);
-            EvaluationContext context = new DefaultEvaluationContext(identity, session);
             DecisionResult decisionCollector = new DecisionResult();
             List<ResourcePermission> permissions = Permissions.permission(resourceServer, resource, scope);
             PermissionEvaluator from = authz.evaluators().from(permissions, context);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
index 951e724..0e12861 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
@@ -36,6 +36,7 @@ import org.keycloak.services.ForbiddenException;
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
@@ -87,7 +88,8 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
 
     @Override
     public Map<String, String> getPermissions(RoleModel role) {
-        Map<String, String> scopes = new HashMap<>();
+        initialize(role);
+        Map<String, String> scopes = new LinkedHashMap<>();
         scopes.put(RolePermissionManagement.MAP_ROLE_SCOPE, mapRolePermission(role).getId());
         scopes.put(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, mapClientScopePermission(role).getId());
         scopes.put(RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE, mapCompositePermission(role).getId());
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
index b1e1b75..3ac26ed 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
@@ -34,6 +34,7 @@ import org.keycloak.services.ForbiddenException;
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
@@ -121,9 +122,10 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
 
     @Override
     public Map<String, String> getPermissions() {
-        Map<String, String> scopes = new HashMap<>();
-        scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission().getId());
+        initialize();
+        Map<String, String> scopes = new LinkedHashMap<>();
         scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission().getId());
+        scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission().getId());
         scopes.put(MAP_ROLES_SCOPE, mapRolesPermission().getId());
         scopes.put(MANAGE_GROUP_MEMBERSHIP_SCOPE, manageGroupMembershipPermission().getId());
         scopes.put(IMPERSONATE_SCOPE, adminImpersonatingPermission().getId());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenExchangeTest.java
index ff82166..2889118 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenExchangeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenExchangeTest.java
@@ -23,6 +23,8 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.TokenVerifier;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -32,6 +34,9 @@ import org.keycloak.models.UserModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
 import org.keycloak.services.resources.admin.permissions.AdminPermissions;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.Assert;
@@ -68,7 +73,6 @@ public class TokenExchangeTest extends AbstractKeycloakTest {
 
     public static void setupRealm(KeycloakSession session) {
         RealmModel realm = session.realms().getRealmByName(TEST);
-        RoleModel realmExchangeable = AdminPermissions.management(session, realm).getRealmManagementClient().addRole(OAuth2Constants.TOKEN_EXCHANGER);
 
         RoleModel exampleRole = realm.addRole("example");
 
@@ -79,48 +83,62 @@ public class TokenExchangeTest extends AbstractKeycloakTest {
         target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
         target.setFullScopeAllowed(false);
         target.addScopeMapping(exampleRole);
-        RoleModel targetExchangeable = target.addRole(OAuth2Constants.TOKEN_EXCHANGER);
 
-        target = realm.addClient("realm-exchanger");
-        target.setClientId("realm-exchanger");
-        target.setDirectAccessGrantsEnabled(true);
-        target.setEnabled(true);
-        target.setSecret("secret");
-        target.setServiceAccountsEnabled(true);
-        target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
-        target.setFullScopeAllowed(false);
-        new org.keycloak.services.managers.ClientManager(new org.keycloak.services.managers.RealmManager(session)).enableServiceAccount(target);
-        session.users().getServiceAccount(target).grantRole(realmExchangeable);
-
-        target = realm.addClient("client-exchanger");
-        target.setClientId("client-exchanger");
-        target.setDirectAccessGrantsEnabled(true);
-        target.setEnabled(true);
-        target.setSecret("secret");
-        target.setServiceAccountsEnabled(true);
-        target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
-        target.setFullScopeAllowed(false);
-        new org.keycloak.services.managers.ClientManager(new org.keycloak.services.managers.RealmManager(session)).enableServiceAccount(target);
-        session.users().getServiceAccount(target).grantRole(targetExchangeable);
-
-        target = realm.addClient("account-not-allowed");
-        target.setClientId("account-not-allowed");
-        target.setDirectAccessGrantsEnabled(true);
-        target.setEnabled(true);
-        target.setSecret("secret");
-        target.setServiceAccountsEnabled(true);
-        target.setFullScopeAllowed(false);
-        target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
-        new org.keycloak.services.managers.ClientManager(new org.keycloak.services.managers.RealmManager(session)).enableServiceAccount(target);
+        ClientModel clientExchanger = realm.addClient("client-exchanger");
+        clientExchanger.setClientId("client-exchanger");
+        clientExchanger.setPublicClient(false);
+        clientExchanger.setDirectAccessGrantsEnabled(true);
+        clientExchanger.setEnabled(true);
+        clientExchanger.setSecret("secret");
+        clientExchanger.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        clientExchanger.setFullScopeAllowed(false);
+
+        ClientModel illegal = realm.addClient("illegal");
+        illegal.setClientId("illegal");
+        illegal.setPublicClient(false);
+        illegal.setDirectAccessGrantsEnabled(true);
+        illegal.setEnabled(true);
+        illegal.setSecret("secret");
+        illegal.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        illegal.setFullScopeAllowed(false);
+
+        ClientModel illegalTo = realm.addClient("illegal-to");
+        illegalTo.setClientId("illegal-to");
+        illegalTo.setPublicClient(false);
+        illegalTo.setDirectAccessGrantsEnabled(true);
+        illegalTo.setEnabled(true);
+        illegalTo.setSecret("secret");
+        illegalTo.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        illegalTo.setFullScopeAllowed(false);
+
+        ClientModel legal = realm.addClient("legal");
+        legal.setClientId("legal");
+        legal.setPublicClient(false);
+        legal.setDirectAccessGrantsEnabled(true);
+        legal.setEnabled(true);
+        legal.setSecret("secret");
+        legal.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        legal.setFullScopeAllowed(false);
+
+        AdminPermissionManagement management = AdminPermissions.management(session, realm);
+
+        management.clients().setPermissionsEnabled(target, true);
+        ClientPolicyRepresentation clientRep = new ClientPolicyRepresentation();
+        clientRep.setName("to");
+        clientRep.addClient(clientExchanger.getId());
+        clientRep.addClient(legal.getId());
+        ResourceServer server = management.realmResourceServer();
+        Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(clientRep, server);
+        management.clients().exchangeToPermission(target).addAssociatedPolicy(clientPolicy);
+
+        management.clients().setPermissionsEnabled(clientExchanger, true);
+        ClientPolicyRepresentation client2Rep = new ClientPolicyRepresentation();
+        client2Rep.setName("from");
+        client2Rep.addClient(legal.getId());
+        client2Rep.addClient(illegalTo.getId());
+        Policy client2Policy = management.authz().getStoreFactory().getPolicyStore().create(client2Rep, server);
+        management.clients().exchangeFromPermission(clientExchanger).addAssociatedPolicy(client2Policy);
 
-        target = realm.addClient("no-account");
-        target.setClientId("no-account");
-        target.setDirectAccessGrantsEnabled(true);
-        target.setEnabled(true);
-        target.setSecret("secret");
-        target.setServiceAccountsEnabled(true);
-        target.setFullScopeAllowed(false);
-        target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
 
         UserModel user = session.users().addUser(realm, "user");
         user.setEnabled(true);
@@ -140,18 +158,46 @@ public class TokenExchangeTest extends AbstractKeycloakTest {
         testingClient.server().run(TokenExchangeTest::setupRealm);
 
         oauth.realm(TEST);
-        oauth.clientId("realm-exchanger");
+        oauth.clientId("client-exchanger");
 
         OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "user", "password");
         String accessToken = response.getAccessToken();
-
-        response = oauth.doTokenExchange(TEST,accessToken, "target", "realm-exchanger", "secret");
-
-        String exchangedTokenString = response.getAccessToken();
-        TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
-        AccessToken exchangedToken = verifier.parse().getToken();
-        Assert.assertEquals(exchangedToken.getPreferredUsername(), "user");
-        Assert.assertTrue(exchangedToken.getRealmAccess().isUserInRole("example"));
+        TokenVerifier<AccessToken> accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class);
+        AccessToken token = accessTokenVerifier.parse().getToken();
+        Assert.assertEquals(token.getPreferredUsername(), "user");
+        Assert.assertTrue(token.getRealmAccess() == null || !token.getRealmAccess().isUserInRole("example"));
+
+        {
+            response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret");
+
+            String exchangedTokenString = response.getAccessToken();
+            TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
+            AccessToken exchangedToken = verifier.parse().getToken();
+            Assert.assertEquals("client-exchanger", exchangedToken.getIssuedFor());
+            Assert.assertEquals("target", exchangedToken.getAudience()[0]);
+            Assert.assertEquals(exchangedToken.getPreferredUsername(), "user");
+            Assert.assertTrue(exchangedToken.getRealmAccess().isUserInRole("example"));
+        }
+
+        {
+            response = oauth.doTokenExchange(TEST, accessToken, "target", "legal", "secret");
+
+            String exchangedTokenString = response.getAccessToken();
+            TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
+            AccessToken exchangedToken = verifier.parse().getToken();
+            Assert.assertEquals("legal", exchangedToken.getIssuedFor());
+            Assert.assertEquals("target", exchangedToken.getAudience()[0]);
+            Assert.assertEquals(exchangedToken.getPreferredUsername(), "user");
+            Assert.assertTrue(exchangedToken.getRealmAccess().isUserInRole("example"));
+        }
+        {
+            response = oauth.doTokenExchange(TEST, accessToken, "target", "illegal", "secret");
+            Assert.assertEquals(403, response.getStatusCode());
+        }
+        {
+            response = oauth.doTokenExchange(TEST, accessToken, "target", "illegal-to", "secret");
+            Assert.assertEquals(403, response.getStatusCode());
+        }
 
 
     }
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 a2c8d5b..f261105 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
@@ -1340,6 +1340,8 @@ manage-permissions-group.tooltip=Fine grain permssions for admins that want to m
 manage-authz-group-scope-description=Policies that decide if an admin can manage this group
 view-authz-group-scope-description=Policies that decide if an admin can view this group
 view-members-authz-group-scope-description=Policies that decide if an admin can manage the members of this group
+exchange-to-authz-client-scope-description=Policies that decide which clients are allowed exchange tokens for a token that is targeted to this client.
+exchange-from-authz-client-scope-description=Policies that decide which clients are allowed to exchange tokens that were generated for this client.
 manage-authz-client-scope-description=Policies that decide if an admin can manage this client
 configure-authz-client-scope-description=Reduced management permissions for admin.  Cannot set scope, template, or protocol mappers.
 view-authz-client-scope-description=Policies that decide if an admin can view this client