token exchange permissions

This commit is contained in:
Bill Burke 2017-08-09 10:04:14 -04:00
parent ed5e880931
commit 2fa55550f3
12 changed files with 388 additions and 94 deletions

View file

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

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;
}
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;

View file

@ -592,45 +592,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);
@ -655,6 +638,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)) {

View file

@ -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();

View file

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

View file

@ -18,18 +18,26 @@ 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.Map;
@ -79,6 +87,14 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
return MAP_ROLES_COMPOSITE_SCOPE + ".permission.client." + client.getId();
}
private String getExchangeToPermissionName(ClientModel client) {
return AdminPermissionManagement.EXCHANGE_TO_SCOPE + ".permission.client." + client.getId();
}
private String getExchangeFromPermissionName(ClientModel client) {
return AdminPermissionManagement.EXCHANGE_FROM_SCOPE + ".permission.client." + client.getId();
}
private void initialize(ClientModel client) {
ResourceServer server = root.findOrCreateResourceServer(client);
Scope manageScope = manageScope(server);
@ -93,18 +109,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(AdminPermissionManagement.EXCHANGE_FROM_SCOPE, server);
Scope exchangeToScope = root.initializeScope(AdminPermissionManagement.EXCHANGE_TO_SCOPE, server);
String resourceName = getResourceName(client);
Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
@ -118,6 +127,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 +161,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) {
@ -196,6 +217,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(AdminPermissionManagement.EXCHANGE_FROM_SCOPE, server.getId());
}
private Scope exchangeToScope(ResourceServer server) {
return authz.getStoreFactory().getScopeStore().findByName(AdminPermissionManagement.EXCHANGE_TO_SCOPE, server.getId());
}
private Scope configureScope(ResourceServer server) {
return authz.getStoreFactory().getScopeStore().findByName(CONFIGURE_SCOPE, server.getId());
}
@ -271,6 +300,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
@Override
public Map<String, String> getPermissions(ClientModel client) {
initialize(client);
Map<String, String> scopes = new HashMap<>();
scopes.put(MAP_ROLES_SCOPE, mapRolesPermission(client).getId());
scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(client).getId());
@ -281,6 +311,106 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionMa
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(AdminPermissionManagement.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(AdminPermissionManagement.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 +593,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);

View file

@ -242,6 +242,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
@Override
public Map<String, String> getPermissions(GroupModel group) {
initialize(group);
Map<String, String> scopes = new HashMap<>();
scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission(group).getId());
scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission(group).getId());

View file

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

View file

@ -87,6 +87,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
@Override
public Map<String, String> getPermissions(RoleModel role) {
initialize(role);
Map<String, String> scopes = new HashMap<>();
scopes.put(RolePermissionManagement.MAP_ROLE_SCOPE, mapRolePermission(role).getId());
scopes.put(RolePermissionManagement.MAP_ROLE_CLIENT_SCOPE_SCOPE, mapClientScopePermission(role).getId());

View file

@ -121,6 +121,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
@Override
public Map<String, String> getPermissions() {
initialize();
Map<String, String> scopes = new HashMap<>();
scopes.put(AdminPermissionManagement.MANAGE_SCOPE, managePermission().getId());
scopes.put(AdminPermissionManagement.VIEW_SCOPE, viewPermission().getId());

View file

@ -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(exchangedToken.getPreferredUsername(), "user");
Assert.assertTrue(exchangedToken.getRealmAccess().isUserInRole("example"));
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());
}
}