KEYCLOAK-8175 Possibility of clientScope not being used if user doesn't have a role
This commit is contained in:
parent
cfeb56e18a
commit
ffcd8e09e7
9 changed files with 247 additions and 79 deletions
|
@ -32,6 +32,9 @@ public interface ClientSessionContext {
|
|||
|
||||
Set<ClientScopeModel> getClientScopes();
|
||||
|
||||
/**
|
||||
* @return expanded roles (composite roles already applied)
|
||||
*/
|
||||
Set<RoleModel> getRoles();
|
||||
|
||||
Set<ProtocolMapperModel> getProtocolMappers();
|
||||
|
|
|
@ -19,11 +19,13 @@ package org.keycloak.models.utils;
|
|||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
|
@ -105,30 +107,67 @@ public class RoleUtils {
|
|||
/**
|
||||
* Recursively expands composite roles into their composite.
|
||||
* @param role
|
||||
* @param visited Track roles, which were already visited. Those will be ignored and won't be added to the stream. Besides that,
|
||||
* the "visited" set itself will be updated as a result of this method call and all the tracked roles will be added to it
|
||||
* @return Stream of containing all of the composite roles and their components.
|
||||
*/
|
||||
public static Stream<RoleModel> expandCompositeRolesStream(RoleModel role) {
|
||||
private static Stream<RoleModel> expandCompositeRolesStream(RoleModel role, Set<RoleModel> visited) {
|
||||
Stream.Builder<RoleModel> sb = Stream.builder();
|
||||
Set<RoleModel> roles = new HashSet<>();
|
||||
|
||||
Deque<RoleModel> stack = new ArrayDeque<>();
|
||||
stack.add(role);
|
||||
if (!visited.contains(role)) {
|
||||
Deque<RoleModel> stack = new ArrayDeque<>();
|
||||
stack.add(role);
|
||||
|
||||
while (! stack.isEmpty()) {
|
||||
RoleModel current = stack.pop();
|
||||
sb.add(current);
|
||||
while (!stack.isEmpty()) {
|
||||
RoleModel current = stack.pop();
|
||||
sb.add(current);
|
||||
|
||||
if (current.isComposite()) {
|
||||
current.getComposites().stream()
|
||||
.filter(r -> ! roles.contains(r))
|
||||
.forEach(r -> {
|
||||
roles.add(r);
|
||||
stack.add(r);
|
||||
});
|
||||
if (current.isComposite()) {
|
||||
current.getComposites().stream()
|
||||
.filter(r -> !visited.contains(r))
|
||||
.forEach(r -> {
|
||||
visited.add(r);
|
||||
stack.add(r);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param roles
|
||||
* @return new set with composite roles expanded
|
||||
*/
|
||||
public static Set<RoleModel> expandCompositeRoles(Set<RoleModel> roles) {
|
||||
Set<RoleModel> visited = new HashSet<>();
|
||||
|
||||
return roles.stream()
|
||||
.flatMap(roleModel -> RoleUtils.expandCompositeRolesStream(roleModel, visited))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param user
|
||||
* @return all user role mappings including all groups of user. Composite roles will be expanded
|
||||
*/
|
||||
public static Set<RoleModel> getDeepUserRoleMappings(UserModel user) {
|
||||
Set<RoleModel> roleMappings = new HashSet<>(user.getRoleMappings());
|
||||
for (GroupModel group : user.getGroups()) {
|
||||
addGroupRoles(group, roleMappings);
|
||||
}
|
||||
|
||||
return expandCompositeRoles(roleMappings);
|
||||
}
|
||||
|
||||
|
||||
private static void addGroupRoles(GroupModel group, Set<RoleModel> roleMappings) {
|
||||
roleMappings.addAll(group.getRoleMappings());
|
||||
if (group.getParentId() == null) return;
|
||||
addGroupRoles(group.getParent(), roleMappings);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,9 +35,7 @@ import org.keycloak.models.AuthenticatedClientSessionModel;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.ClientSessionContext;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
|
@ -46,13 +44,13 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.protocol.ProtocolMapper;
|
||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.UserInfoTokenMapper;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
|
@ -86,20 +84,6 @@ public class TokenManager {
|
|||
private static final Logger logger = Logger.getLogger(TokenManager.class);
|
||||
private static final String JWT = "JWT";
|
||||
|
||||
public static void applyScope(RoleModel role, RoleModel scope, Set<RoleModel> visited, Set<RoleModel> requested) {
|
||||
if (visited.contains(scope)) return;
|
||||
visited.add(scope);
|
||||
if (role.hasRole(scope)) {
|
||||
requested.add(scope);
|
||||
return;
|
||||
}
|
||||
if (!scope.isComposite()) return;
|
||||
|
||||
for (RoleModel contained : scope.getComposites()) {
|
||||
applyScope(role, contained, visited, requested);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TokenValidation {
|
||||
public final UserModel user;
|
||||
public final UserSessionModel userSession;
|
||||
|
@ -468,28 +452,14 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
|
||||
private static void addGroupRoles(GroupModel group, Set<RoleModel> roleMappings) {
|
||||
roleMappings.addAll(group.getRoleMappings());
|
||||
if (group.getParentId() == null) return;
|
||||
addGroupRoles(group.getParent(), roleMappings);
|
||||
}
|
||||
|
||||
|
||||
public static Set<RoleModel> getAccess(UserModel user, ClientModel client, Set<ClientScopeModel> clientScopes) {
|
||||
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
||||
|
||||
Set<RoleModel> mappings = user.getRoleMappings();
|
||||
Set<RoleModel> roleMappings = new HashSet<>();
|
||||
roleMappings.addAll(mappings);
|
||||
for (GroupModel group : user.getGroups()) {
|
||||
addGroupRoles(group, roleMappings);
|
||||
}
|
||||
Set<RoleModel> roleMappings = RoleUtils.getDeepUserRoleMappings(user);
|
||||
|
||||
if (client.isFullScopeAllowed()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Using full scope for client %s", client.getClientId());
|
||||
}
|
||||
requestedRoles = roleMappings;
|
||||
return roleMappings;
|
||||
} else {
|
||||
Set<RoleModel> scopeMappings = new HashSet<>();
|
||||
|
||||
|
@ -504,15 +474,14 @@ public class TokenManager {
|
|||
scopeMappings.addAll(clientScope.getScopeMappings());
|
||||
}
|
||||
|
||||
for (RoleModel role : roleMappings) {
|
||||
for (RoleModel desiredRole : scopeMappings) {
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
applyScope(role, desiredRole, visited, requestedRoles);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3 - Expand scope mappings
|
||||
scopeMappings = RoleUtils.expandCompositeRoles(scopeMappings);
|
||||
|
||||
return requestedRoles;
|
||||
// Intersection of expanded user roles and expanded scopeMappings
|
||||
roleMappings.retainAll(scopeMappings);
|
||||
|
||||
return roleMappings;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.models.ClientSessionContext;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.protocol.ProtocolMapper;
|
||||
|
@ -32,6 +33,7 @@ import org.keycloak.provider.ProviderConfigProperty;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -144,7 +146,6 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
|
|||
|
||||
List<String> allRoleNames = clientSessionCtx.getRoles().stream()
|
||||
// todo need a role mapping
|
||||
.flatMap(RoleUtils::expandCompositeRolesStream)
|
||||
.map(roleModel -> roleNameMappers.stream()
|
||||
.map(entry -> entry.mapper.mapName(entry.model, roleModel))
|
||||
.filter(Objects::nonNull)
|
||||
|
|
|
@ -131,13 +131,7 @@ public class UserSessionManager {
|
|||
}
|
||||
|
||||
// Check if offline_access is allowed here. Even through composite roles
|
||||
for (RoleModel role : clientSessionCtx.getRoles()) {
|
||||
if (role.hasRole(offlineAccessRole)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return clientSessionCtx.getRoles().contains(offlineAccessRole);
|
||||
}
|
||||
|
||||
private UserSessionModel createOfflineUserSession(UserModel user, UserSessionModel userSession) {
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.keycloak.models.ProtocolMapperModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
@ -48,9 +48,14 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
|||
private final Set<String> clientScopeIds;
|
||||
|
||||
private Set<ClientScopeModel> clientScopes;
|
||||
|
||||
//
|
||||
private Set<RoleModel> roles;
|
||||
private Set<ProtocolMapperModel> protocolMappers;
|
||||
|
||||
// All roles of user expanded. It doesn't yet take into account permitted clientScopes
|
||||
private Set<RoleModel> userRoles;
|
||||
|
||||
private DefaultClientSessionContext(AuthenticatedClientSessionModel clientSession, Set<String> clientScopeIds) {
|
||||
this.clientSession = clientSession;
|
||||
this.clientScopeIds = clientScopeIds;
|
||||
|
@ -82,9 +87,7 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
|||
clientScopeIds.add(clientScope.getId());
|
||||
}
|
||||
|
||||
DefaultClientSessionContext ctx = new DefaultClientSessionContext(clientSession, clientScopeIds);
|
||||
ctx.clientScopes = new HashSet<>(clientScopes);
|
||||
return ctx;
|
||||
return new DefaultClientSessionContext(clientSession, clientScopeIds);
|
||||
}
|
||||
|
||||
|
||||
|
@ -122,7 +125,7 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
|||
|
||||
@Override
|
||||
public Set<ProtocolMapperModel> getProtocolMappers() {
|
||||
// Load roles if not yet present
|
||||
// Load protocolMappers if not yet present
|
||||
if (protocolMappers == null) {
|
||||
protocolMappers = loadProtocolMappers();
|
||||
}
|
||||
|
@ -130,6 +133,15 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
|||
}
|
||||
|
||||
|
||||
private Set<RoleModel> getUserRoles() {
|
||||
// Load userRoles if not yet present
|
||||
if (userRoles == null) {
|
||||
userRoles = loadUserRoles();
|
||||
}
|
||||
return userRoles;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getScopeString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
@ -172,13 +184,42 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
|||
for (String scopeId : clientScopeIds) {
|
||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(clientSession.getClient().getRealm(), scopeId);
|
||||
if (clientScope != null) {
|
||||
clientScopes.add(clientScope);
|
||||
if (isClientScopePermittedForUser(clientScope)) {
|
||||
clientScopes.add(clientScope);
|
||||
} else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("User '%s' not permitted to have client scope '%s'",
|
||||
clientSession.getUserSession().getUser().getUsername(), clientScope.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return clientScopes;
|
||||
}
|
||||
|
||||
|
||||
// Return true if clientScope can be used by the user.
|
||||
private boolean isClientScopePermittedForUser(ClientScopeModel clientScope) {
|
||||
if (clientScope instanceof ClientModel) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Set<RoleModel> clientScopeRoles = clientScope.getScopeMappings();
|
||||
|
||||
// Client scope is automatically permitted if it doesn't have any role scope mappings
|
||||
if (clientScopeRoles.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Expand (resolve composite roles)
|
||||
clientScopeRoles = RoleUtils.expandCompositeRoles(clientScopeRoles);
|
||||
|
||||
// Check if expanded roles of clientScope has any intersection with expanded roles of user. If not, it is not permitted
|
||||
clientScopeRoles.retainAll(getUserRoles());
|
||||
return !clientScopeRoles.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
private Set<RoleModel> loadRoles() {
|
||||
UserModel user = clientSession.getUserSession().getUser();
|
||||
ClientModel client = clientSession.getClient();
|
||||
|
@ -212,4 +253,10 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
|||
return protocolMappers;
|
||||
}
|
||||
|
||||
|
||||
private Set<RoleModel> loadUserRoles() {
|
||||
UserModel user = clientSession.getUserSession().getUser();
|
||||
return RoleUtils.getDeepUserRoleMappings(user);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.util.DefaultClientSessionContext;
|
||||
|
||||
/**
|
||||
* Helper class to ensure that all the user's permitted roles (including composite roles) are loaded just once per request.
|
||||
|
@ -113,13 +112,13 @@ public class RoleResolveUtil {
|
|||
Set<RoleModel> requestedRoles = clientSessionCtx.getRoles();
|
||||
AccessToken token = new AccessToken();
|
||||
for (RoleModel role : requestedRoles) {
|
||||
addComposites(token, role);
|
||||
addToToken(token, role);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
private static void addComposites(AccessToken token, RoleModel role) {
|
||||
private static void addToToken(AccessToken token, RoleModel role) {
|
||||
AccessToken.Access access = null;
|
||||
if (role.getContainer() instanceof RealmModel) {
|
||||
access = token.getRealmAccess();
|
||||
|
@ -139,12 +138,6 @@ public class RoleResolveUtil {
|
|||
|
||||
}
|
||||
access.addRole(role.getName());
|
||||
if (!role.isComposite()) return;
|
||||
|
||||
for (RoleModel composite : role.getComposites()) {
|
||||
addComposites(token, composite);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1004,7 +1004,7 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
|
|||
Assert.assertEquals("http://localhost:8180/auth/realms/test/account", accountEntry.getHref());
|
||||
|
||||
AccountApplicationsPage.AppEntry testAppEntry = apps.get("test-app");
|
||||
Assert.assertEquals(5, testAppEntry.getRolesAvailable().size());
|
||||
Assert.assertEquals(6, testAppEntry.getRolesAvailable().size());
|
||||
Assert.assertTrue(testAppEntry.getRolesAvailable().contains("Offline access"));
|
||||
Assert.assertTrue(testAppEntry.getClientScopesGranted().contains("Full Access"));
|
||||
Assert.assertEquals("http://localhost:8180/auth/realms/master/app/auth", testAppEntry.getHref());
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.testsuite.oidc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -44,6 +45,7 @@ import org.keycloak.representations.RefreshToken;
|
|||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
@ -54,6 +56,7 @@ import org.keycloak.testsuite.pages.OAuthGrantPage;
|
|||
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RoleBuilder;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -104,6 +107,51 @@ public class OIDCScopeTest extends AbstractOIDCScopeTest {
|
|||
RoleRepresentation role2 = new RoleRepresentation();
|
||||
role2.setName("role-2");
|
||||
testRealm.getRoles().getRealm().add(role2);
|
||||
|
||||
RoleRepresentation roleParent = RoleBuilder.create()
|
||||
.name("role-parent")
|
||||
.realmComposite("role-1")
|
||||
.build();
|
||||
testRealm.getRoles().getRealm().add(roleParent);
|
||||
|
||||
// Add sample group
|
||||
GroupRepresentation group = new GroupRepresentation();
|
||||
group.setName("group-role-1");
|
||||
group.setRealmRoles(Collections.singletonList("role-1"));
|
||||
testRealm.getGroups().add(group);
|
||||
|
||||
// Add more sample users
|
||||
user = UserBuilder.create()
|
||||
.username("role-1-user")
|
||||
.enabled(true)
|
||||
.password("password")
|
||||
.addRoles("role-1")
|
||||
.build();
|
||||
testRealm.getUsers().add(user);
|
||||
|
||||
user = UserBuilder.create()
|
||||
.username("role-2-user")
|
||||
.enabled(true)
|
||||
.password("password")
|
||||
.addRoles("role-2")
|
||||
.build();
|
||||
testRealm.getUsers().add(user);
|
||||
|
||||
user = UserBuilder.create()
|
||||
.username("role-parent-user")
|
||||
.enabled(true)
|
||||
.password("password")
|
||||
.addRoles("role-parent")
|
||||
.build();
|
||||
testRealm.getUsers().add(user);
|
||||
|
||||
user = UserBuilder.create()
|
||||
.username("group-role-1-user")
|
||||
.enabled(true)
|
||||
.password("password")
|
||||
.addGroups("group-role-1")
|
||||
.build();
|
||||
testRealm.getUsers().add(user);
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -534,4 +582,78 @@ public class OIDCScopeTest extends AbstractOIDCScopeTest {
|
|||
testApp.removeOptionalClientScope(scope2Id);
|
||||
}
|
||||
|
||||
|
||||
// Test that clientScope is NOT applied in case that user is not member of any role scoped to the clientScope (including composite roles)
|
||||
@Test
|
||||
public void testClientScopesPermissions() {
|
||||
// Add 2 client scopes. Each with scope to 1 realm role
|
||||
ClientScopeRepresentation clientScope1 = new ClientScopeRepresentation();
|
||||
clientScope1.setName("scope-role-1");
|
||||
clientScope1.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
Response response = testRealm().clientScopes().create(clientScope1);
|
||||
String scope1Id = ApiUtil.getCreatedId(response);
|
||||
getCleanup().addClientScopeId(scope1Id);
|
||||
response.close();
|
||||
|
||||
ClientScopeRepresentation clientScopeParent = new ClientScopeRepresentation();
|
||||
clientScopeParent.setName("scope-role-parent");
|
||||
clientScopeParent.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
response = testRealm().clientScopes().create(clientScopeParent);
|
||||
String scopeParentId = ApiUtil.getCreatedId(response);
|
||||
getCleanup().addClientScopeId(scopeParentId);
|
||||
response.close();
|
||||
|
||||
RoleRepresentation role1 = testRealm().roles().get("role-1").toRepresentation();
|
||||
testRealm().clientScopes().get(scope1Id).getScopeMappings().realmLevel().add(Arrays.asList(role1));
|
||||
|
||||
RoleRepresentation roleParent = testRealm().roles().get("role-parent").toRepresentation();
|
||||
testRealm().clientScopes().get(scopeParentId).getScopeMappings().realmLevel().add(Arrays.asList(roleParent));
|
||||
|
||||
// Add client scopes to our client
|
||||
ClientResource testApp = ApiUtil.findClientByClientId(testRealm(), "test-app");
|
||||
ClientRepresentation testAppRep = testApp.toRepresentation();
|
||||
testApp.update(testAppRep);
|
||||
testApp.addDefaultClientScope(scope1Id);
|
||||
testApp.addDefaultClientScope(scopeParentId);
|
||||
|
||||
// role-1-user will have clientScope "scope-role-1" and also "scope-role-parent" due the composite role
|
||||
testLoginAndClientScopesPermissions("role-1-user", "scope-role-1 scope-role-parent", "role-1");
|
||||
|
||||
// role-2-user won't have any of the "scope-role-1" or "scope-role-parent" applied as he is not member of "role-1" nor "role-parent"
|
||||
testLoginAndClientScopesPermissions("role-2-user", "", "role-2");
|
||||
|
||||
// role-parent-user will have clientScope "scope-role-1" (due the composite role) and also "scope-role-parent"
|
||||
testLoginAndClientScopesPermissions("role-parent-user", "scope-role-1 scope-role-parent", "role-1", "role-parent");
|
||||
|
||||
// group-role-1-user will have clientScope "scope-role-1" and also "scope-role-parent" due the composite role and due the fact that he is member of group
|
||||
testLoginAndClientScopesPermissions("group-role-1-user", "scope-role-1 scope-role-parent", "role-1");
|
||||
|
||||
|
||||
// Revert
|
||||
testApp.removeOptionalClientScope(scope1Id);
|
||||
testApp.removeOptionalClientScope(scopeParentId);
|
||||
}
|
||||
|
||||
|
||||
private void testLoginAndClientScopesPermissions(String username, String expectedRoleScopes, String... expectedRoles) {
|
||||
String userId = ApiUtil.findUserByUsername(testRealm(), username).getId();
|
||||
|
||||
oauth.openLoginForm();
|
||||
oauth.doLogin(username, "password");
|
||||
EventRepresentation loginEvent = events.expectLogin()
|
||||
.user(userId)
|
||||
.assertEvent();
|
||||
|
||||
Tokens tokens = sendTokenRequest(loginEvent, userId,"openid email profile " + expectedRoleScopes, "test-app");
|
||||
Assert.assertNames(tokens.accessToken.getRealmAccess().getRoles(), expectedRoles);
|
||||
|
||||
oauth.doLogout(tokens.refreshToken, "password");
|
||||
events.expectLogout(tokens.idToken.getSessionState())
|
||||
.client("test-app")
|
||||
.user(userId)
|
||||
.removeDetail(Details.REDIRECT_URI).assertEvent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue