diff --git a/core/src/main/java/org/keycloak/OAuth2Constants.java b/core/src/main/java/org/keycloak/OAuth2Constants.java
index 6de35b8b67..2e585c3278 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 0000000000..d2c6b67672
--- /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 Bill Burke
+ * @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();
+ 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/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 0581aecef2..6a26c692d5 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/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 28923dfd03..fef17c6ca0 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 (AdminPermissions.management(session, realm).clients().canExchangeFrom(client, audClient)) {
+ exchangeFromAllowed = 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");
- }
- }
-
- } 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/services/resources/admin/permissions/AdminPermissionManagement.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/AdminPermissionManagement.java
index 7df5b5e073..d8eb94a4b4 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 8a6b76dd8e..ccf9679609 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 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 2b1e2340c4..bbb7bf4d29 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 getPermissions(ClientModel client) {
- Map 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 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 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> getBaseAttributes() {
+ Map> 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 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> getBaseAttributes() {
+ Map> 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) {
@@ -463,6 +601,20 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
return root.evaluatePermission(resource, scope, server);
}
+ @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);
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 a7e9f374be..722ea1c7a7 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 getPermissions(GroupModel group) {
- Map scopes = new HashMap<>();
+ initialize(group);
+ Map 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 449530cdf4..fe4a11fe93 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 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 951e724e7e..0e12861929 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 getPermissions(RoleModel role) {
- Map scopes = new HashMap<>();
+ initialize(role);
+ Map 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 b1e1b750a4..3ac26ed5fa 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 getPermissions() {
- Map scopes = new HashMap<>();
- scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission().getId());
+ initialize();
+ Map 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 ff82166f63..2889118efd 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);
+ 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);
- 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);
+ 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);
- 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 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();
+ TokenVerifier 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", "realm-exchanger", "secret");
+ {
+ response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret");
- String exchangedTokenString = response.getAccessToken();
- TokenVerifier verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
- AccessToken exchangedToken = verifier.parse().getToken();
- Assert.assertEquals(exchangedToken.getPreferredUsername(), "user");
- Assert.assertTrue(exchangedToken.getRealmAccess().isUserInRole("example"));
+ String exchangedTokenString = response.getAccessToken();
+ TokenVerifier 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 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 a2c8d5bbb9..f261105214 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