Merge pull request #4377 from patriot1burke/master

token exchange permissions
This commit is contained in:
Bill Burke 2017-08-09 12:31:03 -04:00 committed by GitHub
commit cc9fb4c12e
13 changed files with 410 additions and 103 deletions

View file

@ -101,7 +101,6 @@ public interface OAuth2Constants {
String REFRESH_TOKEN_TYPE="urn:ietf:params:oauth:token-type:refresh_token"; String REFRESH_TOKEN_TYPE="urn:ietf:params:oauth:token-type:refresh_token";
String JWT_TOKEN_TYPE="urn:ietf:params:oauth:token-type:jwt"; String JWT_TOKEN_TYPE="urn:ietf:params:oauth:token-type:jwt";
String ID_TOKEN_TYPE="urn:ietf:params:oauth:token-type:id_token"; String ID_TOKEN_TYPE="urn:ietf:params:oauth:token-type:id_token";
String TOKEN_EXCHANGER ="token-exchanger";
} }

View file

@ -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");
}
}

View file

@ -683,6 +683,18 @@ public class TokenManager {
this.clientSession = clientSession; this.clientSession = clientSession;
} }
public AccessToken getAccessToken() {
return accessToken;
}
public RefreshToken getRefreshToken() {
return refreshToken;
}
public IDToken getIdToken() {
return idToken;
}
public AccessTokenResponseBuilder accessToken(AccessToken accessToken) { public AccessTokenResponseBuilder accessToken(AccessToken accessToken) {
this.accessToken = accessToken; this.accessToken = accessToken;
return this; return this;

View file

@ -593,45 +593,28 @@ public class TokenEndpoint {
throw new ErrorResponseException(OAuthErrorException.INVALID_CLIENT, "Client requires user consent", Response.Status.BAD_REQUEST); throw new ErrorResponseException(OAuthErrorException.INVALID_CLIENT, "Client requires user consent", Response.Status.BAD_REQUEST);
} }
boolean allowed = false; boolean exchangeFromAllowed = false;
UserModel serviceAccount = session.users().getServiceAccount(client);
if (serviceAccount != null) {
if (authResult.getToken().getAudience() == null) {
logger.debug("Client doesn't have service account");
}
boolean tokenAllowed = false;
for (String aud : authResult.getToken().getAudience()) { for (String aud : authResult.getToken().getAudience()) {
ClientModel audClient = realm.getClientByClientId(aud); ClientModel audClient = realm.getClientByClientId(aud);
if (audClient == null) continue; if (audClient == null) continue;
if (audClient.equals(client)) { if (audClient.equals(client)) {
tokenAllowed = true; exchangeFromAllowed = true;
break; break;
} }
RoleModel audExchanger = audClient.getRole(OAuth2Constants.TOKEN_EXCHANGER); if (AdminPermissions.management(session, realm).clients().canExchangeFrom(client, audClient)) {
if (audExchanger != null && serviceAccount.hasRole(audExchanger)) { exchangeFromAllowed = true;
tokenAllowed = true;
break; break;
} }
} }
if (!tokenAllowed) { if (!exchangeFromAllowed) {
logger.debug("Client does not have exchange rights for audience of token"); logger.debug("Client does not have exchange rights for audience of provided token");
} else { event.error(Errors.NOT_ALLOWED);
RoleModel targetExchangable = targetClient.getRole(OAuth2Constants.TOKEN_EXCHANGER); throw new ErrorResponseException(OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);
RoleModel realmExchangeable = AdminPermissions.management(session, realm).getRealmManagementClient().getRole(OAuth2Constants.TOKEN_EXCHANGER); }
allowed = (targetExchangable != null && serviceAccount.hasRole(targetExchangable)) || (realmExchangeable != null && serviceAccount.hasRole(realmExchangeable)); if (!AdminPermissions.management(session, realm).clients().canExchangeTo(client, targetClient)) {
if (!allowed) { logger.debug("Client does not have exchange rights for target audience");
logger.debug("Client does not have exchange rights for target audience");
}
}
} else {
logger.debug("Client doesn't have service account");
}
if (!allowed) {
event.error(Errors.NOT_ALLOWED); event.error(Errors.NOT_ALLOWED);
throw new ErrorResponseException(OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN); throw new ErrorResponseException(OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);
} }
AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, targetClient, false); 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) TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, session, userSession, clientSession)
.generateAccessToken() .generateAccessToken()
.generateRefreshToken(); .generateRefreshToken();
responseBuilder.getAccessToken().issuedFor(client.getClientId());
responseBuilder.getRefreshToken().issuedFor(client.getClientId());
String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE); String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE);
if (TokenUtil.isOIDCRequest(scopeParam)) { if (TokenUtil.isOIDCRequest(scopeParam)) {

View file

@ -27,6 +27,8 @@ import org.keycloak.models.ClientModel;
public interface AdminPermissionManagement { public interface AdminPermissionManagement {
public static final String MANAGE_SCOPE = "manage"; public static final String MANAGE_SCOPE = "manage";
public static final String VIEW_SCOPE = "view"; 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(); ClientModel getRealmManagementClient();

View file

@ -41,6 +41,14 @@ public interface ClientPermissionManagement {
Map<String, String> getPermissions(ClientModel client); 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 mapRolesPermission(ClientModel client);
Policy mapRolesClientScopePermission(ClientModel client); Policy mapRolesClientScopePermission(ClientModel client);

View file

@ -18,23 +18,35 @@ package org.keycloak.services.resources.admin.permissions;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.authorization.AuthorizationProvider; 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.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ForbiddenException; import org.keycloak.services.ForbiddenException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; 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. * Manages default policies for all users.
* *
@ -79,6 +91,14 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
return MAP_ROLES_COMPOSITE_SCOPE + ".permission.client." + client.getId(); 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) { private void initialize(ClientModel client) {
ResourceServer server = root.findOrCreateResourceServer(client); ResourceServer server = root.findOrCreateResourceServer(client);
Scope manageScope = manageScope(server); Scope manageScope = manageScope(server);
@ -93,18 +113,11 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
if (mapRoleScope == null) { if (mapRoleScope == null) {
mapRoleScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_SCOPE, server); mapRoleScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_SCOPE, server);
} }
Scope mapRoleClientScope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_CLIENT_SCOPE, server.getId()); Scope mapRoleClientScope = root.initializeScope(MAP_ROLES_CLIENT_SCOPE, server);
if (mapRoleClientScope == null) { Scope mapRoleCompositeScope = root.initializeScope(MAP_ROLES_COMPOSITE_SCOPE, server);
mapRoleClientScope = authz.getStoreFactory().getScopeStore().create(MAP_ROLES_CLIENT_SCOPE, server); Scope configureScope = root.initializeScope(CONFIGURE_SCOPE, server);
} Scope exchangeFromScope = root.initializeScope(EXCHANGE_FROM_SCOPE, server);
Scope mapRoleCompositeScope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_COMPOSITE_SCOPE, server.getId()); Scope exchangeToScope = root.initializeScope(EXCHANGE_TO_SCOPE, server);
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);
}
String resourceName = getResourceName(client); String resourceName = getResourceName(client);
Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId()); Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
@ -118,6 +131,8 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
scopeset.add(mapRoleScope); scopeset.add(mapRoleScope);
scopeset.add(mapRoleClientScope); scopeset.add(mapRoleClientScope);
scopeset.add(mapRoleCompositeScope); scopeset.add(mapRoleCompositeScope);
scopeset.add(exchangeFromScope);
scopeset.add(exchangeToScope);
resource.updateScopes(scopeset); resource.updateScopes(scopeset);
} }
String managePermissionName = getManagePermissionName(client); String managePermissionName = getManagePermissionName(client);
@ -150,6 +165,16 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
if (mapRoleCompositePermission == null) { if (mapRoleCompositePermission == null) {
Helper.addEmptyScopePermission(authz, server, mapRoleCompositePermissionName, resource, mapRoleCompositeScope); 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) { private void deletePolicy(String name, ResourceServer server) {
@ -169,6 +194,8 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
deletePolicy(getMapRolesClientScopePermissionName(client), server); deletePolicy(getMapRolesClientScopePermissionName(client), server);
deletePolicy(getMapRolesCompositePermissionName(client), server); deletePolicy(getMapRolesCompositePermissionName(client), server);
deletePolicy(getConfigurePermissionName(client), server); deletePolicy(getConfigurePermissionName(client), server);
deletePolicy(getExchangeToPermissionName(client), server);
deletePolicy(getExchangeFromPermissionName(client), server);
Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());; Resource resource = authz.getStoreFactory().getResourceStore().findByName(getResourceName(client), server.getId());;
if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.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()); 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) { private Scope configureScope(ResourceServer server) {
return authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId()); return authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId());
} }
@ -271,16 +306,119 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
@Override @Override
public Map<String, String> getPermissions(ClientModel client) { public Map<String, String> getPermissions(ClientModel client) {
Map<String, String> scopes = new HashMap<>(); initialize(client);
scopes.put(MAP_ROLES_SCOPE, mapRolesPermission(client).getId()); Map<String, String> scopes = new LinkedHashMap<>();
scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(client).getId());
scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId());
scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(client).getId()); scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(client).getId());
scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(client).getId()); scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(client).getId());
scopes.put(CONFIGURE_SCOPE, configurePermission(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; 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 @Override
public boolean canManage(ClientModel client) { public boolean canManage(ClientModel client) {
@ -463,6 +601,20 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
return root.evaluatePermission(resource, scope, server); 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 @Override
public Policy mapRolesPermission(ClientModel client) { public Policy mapRolesPermission(ClientModel client) {
ResourceServer server = resourceServer(client); ResourceServer server = resourceServer(client);

View file

@ -31,6 +31,7 @@ import org.keycloak.services.ForbiddenException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -242,11 +243,12 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
@Override @Override
public Map<String, String> getPermissions(GroupModel group) { 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.VIEW_SCOPE, viewPermission(group).getId());
scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(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(VIEW_MEMBERS_SCOPE, viewMembersPermission(group).getId());
scopes.put(MANAGE_MEMBERS_SCOPE, manageMembersPermission(group).getId());
scopes.put(MANAGE_MEMBERSHIP_SCOPE, manageMembershipPermission(group).getId()); scopes.put(MANAGE_MEMBERSHIP_SCOPE, manageMembershipPermission(group).getId());
return scopes; return scopes;
} }

View file

@ -277,6 +277,14 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
return scope; 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() { public Scope realmManageScope() {
@ -307,10 +315,14 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
} }
public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer, Identity identity) { 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(); RealmModel oldRealm = session.getContext().getRealm();
try { try {
session.getContext().setRealm(realm); session.getContext().setRealm(realm);
EvaluationContext context = new DefaultEvaluationContext(identity, session);
DecisionResult decisionCollector = new DecisionResult(); DecisionResult decisionCollector = new DecisionResult();
List<ResourcePermission> permissions = Permissions.permission(resourceServer, resource, scope); List<ResourcePermission> permissions = Permissions.permission(resourceServer, resource, scope);
PermissionEvaluator from = authz.evaluators().from(permissions, context); PermissionEvaluator from = authz.evaluators().from(permissions, context);

View file

@ -36,6 +36,7 @@ import org.keycloak.services.ForbiddenException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -87,7 +88,8 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
@Override @Override
public Map<String, String> getPermissions(RoleModel role) { 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_SCOPE, mapRolePermission(role).getId());
scopes.put(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, mapClientScopePermission(role).getId()); scopes.put(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, mapClientScopePermission(role).getId());
scopes.put(RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE, mapCompositePermission(role).getId()); scopes.put(RolePermissionManagement.MAP_ROLE_COMPOSITE_SCOPE, mapCompositePermission(role).getId());

View file

@ -34,6 +34,7 @@ import org.keycloak.services.ForbiddenException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -121,9 +122,10 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
@Override @Override
public Map<String, String> getPermissions() { public Map<String, String> getPermissions() {
Map<String, String> scopes = new HashMap<>(); initialize();
scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission().getId()); Map<String, String> scopes = new LinkedHashMap<>();
scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission().getId()); scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission().getId());
scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission().getId());
scopes.put(MAP_ROLES_SCOPE, mapRolesPermission().getId()); scopes.put(MAP_ROLES_SCOPE, mapRolesPermission().getId());
scopes.put(MANAGE_GROUP_MEMBERSHIP_SCOPE, manageGroupMembershipPermission().getId()); scopes.put(MANAGE_GROUP_MEMBERSHIP_SCOPE, manageGroupMembershipPermission().getId());
scopes.put(IMPERSONATE_SCOPE, adminImpersonatingPermission().getId()); scopes.put(IMPERSONATE_SCOPE, adminImpersonatingPermission().getId());

View file

@ -23,6 +23,8 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.TokenVerifier; 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.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -32,6 +34,9 @@ import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.RealmRepresentation; 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.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
@ -68,7 +73,6 @@ public class TokenExchangeTest extends AbstractKeycloakTest {
public static void setupRealm(KeycloakSession session) { public static void setupRealm(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName(TEST); RealmModel realm = session.realms().getRealmByName(TEST);
RoleModel realmExchangeable = AdminPermissions.management(session, realm).getRealmManagementClient().addRole(OAuth2Constants.TOKEN_EXCHANGER);
RoleModel exampleRole = realm.addRole("example"); RoleModel exampleRole = realm.addRole("example");
@ -79,48 +83,62 @@ public class TokenExchangeTest extends AbstractKeycloakTest {
target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
target.setFullScopeAllowed(false); target.setFullScopeAllowed(false);
target.addScopeMapping(exampleRole); target.addScopeMapping(exampleRole);
RoleModel targetExchangeable = target.addRole(OAuth2Constants.TOKEN_EXCHANGER);
target = realm.addClient("realm-exchanger"); ClientModel clientExchanger = realm.addClient("client-exchanger");
target.setClientId("realm-exchanger"); clientExchanger.setClientId("client-exchanger");
target.setDirectAccessGrantsEnabled(true); clientExchanger.setPublicClient(false);
target.setEnabled(true); clientExchanger.setDirectAccessGrantsEnabled(true);
target.setSecret("secret"); clientExchanger.setEnabled(true);
target.setServiceAccountsEnabled(true); clientExchanger.setSecret("secret");
target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); clientExchanger.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
target.setFullScopeAllowed(false); clientExchanger.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"); ClientModel illegal = realm.addClient("illegal");
target.setClientId("client-exchanger"); illegal.setClientId("illegal");
target.setDirectAccessGrantsEnabled(true); illegal.setPublicClient(false);
target.setEnabled(true); illegal.setDirectAccessGrantsEnabled(true);
target.setSecret("secret"); illegal.setEnabled(true);
target.setServiceAccountsEnabled(true); illegal.setSecret("secret");
target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); illegal.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
target.setFullScopeAllowed(false); illegal.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"); ClientModel illegalTo = realm.addClient("illegal-to");
target.setClientId("account-not-allowed"); illegalTo.setClientId("illegal-to");
target.setDirectAccessGrantsEnabled(true); illegalTo.setPublicClient(false);
target.setEnabled(true); illegalTo.setDirectAccessGrantsEnabled(true);
target.setSecret("secret"); illegalTo.setEnabled(true);
target.setServiceAccountsEnabled(true); illegalTo.setSecret("secret");
target.setFullScopeAllowed(false); illegalTo.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); illegalTo.setFullScopeAllowed(false);
new org.keycloak.services.managers.ClientManager(new org.keycloak.services.managers.RealmManager(session)).enableServiceAccount(target);
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"); UserModel user = session.users().addUser(realm, "user");
user.setEnabled(true); user.setEnabled(true);
@ -140,18 +158,46 @@ public class TokenExchangeTest extends AbstractKeycloakTest {
testingClient.server().run(TokenExchangeTest::setupRealm); testingClient.server().run(TokenExchangeTest::setupRealm);
oauth.realm(TEST); oauth.realm(TEST);
oauth.clientId("realm-exchanger"); oauth.clientId("client-exchanger");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "user", "password"); OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "user", "password");
String accessToken = response.getAccessToken(); String accessToken = response.getAccessToken();
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", "realm-exchanger", "secret"); {
response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret");
String exchangedTokenString = response.getAccessToken(); String exchangedTokenString = response.getAccessToken();
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class); TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
AccessToken exchangedToken = verifier.parse().getToken(); AccessToken exchangedToken = verifier.parse().getToken();
Assert.assertEquals("client-exchanger", exchangedToken.getIssuedFor());
Assert.assertEquals("target", exchangedToken.getAudience()[0]);
Assert.assertEquals(exchangedToken.getPreferredUsername(), "user"); Assert.assertEquals(exchangedToken.getPreferredUsername(), "user");
Assert.assertTrue(exchangedToken.getRealmAccess().isUserInRole("example")); 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());
}
} }

View file

@ -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 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-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 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 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. 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 view-authz-client-scope-description=Policies that decide if an admin can view this client