Merge pull request #4377 from patriot1burke/master
token exchange permissions
This commit is contained in:
commit
cc9fb4c12e
13 changed files with 410 additions and 103 deletions
|
@ -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";
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 tokenAllowed = false;
|
||||
boolean exchangeFromAllowed = false;
|
||||
for (String aud : authResult.getToken().getAudience()) {
|
||||
ClientModel audClient = realm.getClientByClientId(aud);
|
||||
if (audClient == null) continue;
|
||||
if (audClient.equals(client)) {
|
||||
tokenAllowed = true;
|
||||
exchangeFromAllowed = true;
|
||||
break;
|
||||
}
|
||||
RoleModel audExchanger = audClient.getRole(OAuth2Constants.TOKEN_EXCHANGER);
|
||||
if (audExchanger != null && serviceAccount.hasRole(audExchanger)) {
|
||||
tokenAllowed = true;
|
||||
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)) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<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();
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue