Details
diff --git a/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java b/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java
index a34d4c8..bd8bfab 100755
--- a/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java
@@ -1,6 +1,7 @@
package org.keycloak.models;
import java.util.List;
+import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -13,7 +14,7 @@ public interface RoleContainerModel {
boolean removeRoleById(String id);
- List<RoleModel> getRoles();
+ Set<RoleModel> getRoles();
RoleModel getRoleById(String id);
}
diff --git a/model/api/src/main/java/org/keycloak/models/RoleModel.java b/model/api/src/main/java/org/keycloak/models/RoleModel.java
index e4cd7f5..1cacaff 100755
--- a/model/api/src/main/java/org/keycloak/models/RoleModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RoleModel.java
@@ -29,6 +29,6 @@ public interface RoleModel {
RoleContainerModel getContainer();
-
boolean hasRole(RoleModel role);
+
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index 29dfdbd..8f650c6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -143,8 +143,8 @@ public class ApplicationAdapter implements ApplicationModel {
}
@Override
- public List<RoleModel> getRoles() {
- ArrayList<RoleModel> list = new ArrayList<RoleModel>();
+ public Set<RoleModel> getRoles() {
+ Set<RoleModel> list = new HashSet<RoleModel>();
Collection<ApplicationRoleEntity> roles = application.getRoles();
if (roles == null) return list;
for (RoleEntity entity : roles) {
@@ -264,6 +264,7 @@ public class ApplicationAdapter implements ApplicationModel {
public boolean equals(Object o) {
if (o == null) return false;
+ if (o == this) return true;
if (!(o instanceof ApplicationAdapter)) return false;
ApplicationAdapter app = (ApplicationAdapter)o;
return app.getId().equals(getId());
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index f65a59f..6d14601 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -8,6 +8,7 @@ import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.Collection;
@@ -17,16 +18,17 @@ import java.util.Collection;
* @version $Revision: 1 $
*/
@Entity
+@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class RoleEntity {
@Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @GeneratedValue(strategy = GenerationType.AUTO)
private String id;
private String name;
private String description;
private boolean composite;
- @OneToMany(fetch = FetchType.LAZY, cascade = {}, orphanRemoval = false)
- @JoinTable(name = "COMPOSITE_ROLE")
+ @ManyToMany(fetch = FetchType.LAZY, cascade = {})
+ //@JoinTable(name = "COMPOSITE_ROLE")
private Collection<RoleEntity> compositeRoles = new ArrayList<RoleEntity>();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 82a95b5..b14fa97 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -880,8 +880,8 @@ public class RealmAdapter implements RealmModel {
}
@Override
- public List<RoleModel> getRoles() {
- ArrayList<RoleModel> list = new ArrayList<RoleModel>();
+ public Set<RoleModel> getRoles() {
+ Set<RoleModel> list = new HashSet<RoleModel>();
Collection<RealmRoleEntity> roles = realm.getRoles();
if (roles == null) return list;
for (RoleEntity entity : roles) {
@@ -1000,7 +1000,6 @@ public class RealmAdapter implements RealmModel {
entity.setUser(((UserAdapter) agent).getUser());
entity.setRole(((RoleAdapter)role).getRole());
em.persist(entity);
- em.flush();
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
index 529fc7d..69aa3ae 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
@@ -3,7 +3,6 @@ package org.keycloak.models.jpa;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.entities.ApplicationRoleEntity;
import org.keycloak.models.jpa.entities.RealmRoleEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
@@ -23,6 +22,7 @@ public class RoleAdapter implements RoleModel {
protected RealmModel realm;
public RoleAdapter(RealmModel realm, EntityManager em, RoleEntity role) {
+ this.em = em;
this.realm = realm;
this.role = role;
}
@@ -77,6 +77,7 @@ public class RoleAdapter implements RoleModel {
if (composite.equals(entity)) return;
}
getRole().getCompositeRoles().add(entity);
+ em.flush();
}
@Override
@@ -98,25 +99,27 @@ public class RoleAdapter implements RoleModel {
return set;
}
- public static boolean searchCompositeFor(RoleModel role, RoleModel composite, Set<RoleModel> visited) {
+ public static boolean searchFor(RoleModel role, RoleModel composite, Set<RoleModel> visited) {
if (visited.contains(composite)) return false;
visited.add(composite);
Set<RoleModel> composites = composite.getComposites();
if (composites.contains(role)) return true;
for (RoleModel contained : composites) {
if (!contained.isComposite()) continue;
- if (searchCompositeFor(role, contained, visited)) return true;
+ if (searchFor(role, contained, visited)) return true;
}
return false;
}
+
+
@Override
public boolean hasRole(RoleModel role) {
if (this.equals(role)) return true;
if (!isComposite()) return false;
Set<RoleModel> visited = new HashSet<RoleModel>();
- return searchCompositeFor(role, this, visited);
+ return searchFor(role, this, visited);
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index 8cdb690..e447ab1 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -62,6 +62,21 @@ public class TokenManager {
return scope == null || scope.isEmpty();
}
+ public static void addScopes(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()) {
+ addScopes(role, contained, visited, requested);
+ }
+ }
+
+
public AccessCodeEntry createAccessCode(String scopeParam, String state, String redirect, RealmModel realm, UserModel client, UserModel user) {
AccessCodeEntry code = new AccessCodeEntry();
@@ -73,21 +88,17 @@ public class TokenManager {
Set<RoleModel> roleMappings = realm.getRoleMappings(user);
Set<RoleModel> scopeMappings = realm.getScopeMappings(client);
+ ApplicationModel clientApp = realm.getApplicationByName(client.getLoginName());
+ Set<RoleModel> clientAppRoles = clientApp == null ? null : clientApp.getRoles();
+ if (clientAppRoles != null) scopeMappings.addAll(clientAppRoles);
+
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
for (RoleModel role : roleMappings) {
+ if (clientApp != null && role.getContainer().equals(clientApp)) requestedRoles.add(role);
for (RoleModel desiredRole : scopeMappings) {
- if (desiredRole.equals(role)) {
- requestedRoles.add(role);
- } else if (desiredRole.hasRole(role)) {
- requestedRoles.add(role);
- } else if (role.hasRole(desiredRole)) {
- requestedRoles.add(desiredRole);
- } else if (role.getContainer() instanceof ApplicationModel) {
- if (((ApplicationModel)role.getContainer()).getApplicationUser().getLoginName().equals(client.getLoginName())) {
- requestedRoles.add(role);
- }
- }
+ Set<RoleModel> visited = new HashSet<RoleModel>();
+ addScopes(role, desiredRole, visited, requestedRoles);
}
}
@@ -98,6 +109,36 @@ public class TokenManager {
ApplicationModel app = (ApplicationModel)role.getContainer();
if (desiresScope(scopeMap, app.getName(), role.getName())) {
resourceRolesRequested.add(app.getName(), role);
+
+ }
+ }
+ }
+
+
+
+
+ Set<RoleModel> realmRoleMappings = realm.getRealmRoleMappings(user);
+
+ for (RoleModel role : realmRoleMappings) {
+ if (!desiresScope(scopeMap, "realm", role.getName())) continue;
+ for (RoleModel desiredRole : scopeMappings) {
+ if (desiredRole.hasRole(role)) {
+ realmRolesRequested.add(role);
+ } else if (role.hasRole(desiredRole)) {
+ realmRolesRequested.add(desiredRole);
+ }
+ }
+ }
+
+ for (ApplicationModel application : realm.getApplications()) {
+ if (!desiresScopeGroup(scopeMap, application.getName())) continue;
+ Set<RoleModel> appRoleMappings = application.getApplicationRoleMappings(user);
+ for (RoleModel role : appRoleMappings) {
+ if (!desiresScope(scopeMap, application.getName(), role.getName())) continue;
+ for (RoleModel desiredRole : scopeMappings) {
+ if (!application.getApplicationUser().getLoginName().equals(client.getLoginName())
+ && !desiredRole.hasRole(role)) continue;
+ resourceRolesRequested.add(application.getName(), role);
}
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
index 3d43178..1971b31 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
@@ -34,7 +34,7 @@ public class RoleContainerResource {
@NoCache
@Produces("application/json")
public List<RoleRepresentation> getRoles() {
- List<RoleModel> roleModels = roleContainer.getRoles();
+ Set<RoleModel> roleModels = roleContainer.getRoles();
List<RoleRepresentation> roles = new ArrayList<RoleRepresentation>();
for (RoleModel roleModel : roleModels) {
if (!roleModel.getName().startsWith(Constants.INTERNAL_ROLE)) {
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index 3c49cd4..9f49a98 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -448,7 +448,7 @@ public class AdapterTest extends AbstractKeycloakTest {
test1CreateRealm();
realmModel.addRole("admin");
realmModel.addRole("user");
- List<RoleModel> roles = realmModel.getRoles();
+ Set<RoleModel> roles = realmModel.getRoles();
Assert.assertEquals(5, roles.size());
UserModel user = realmModel.addUser("bburke");
RoleModel role = realmModel.getRole("user");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java
index d21d85f..3cc1e40 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java
@@ -97,6 +97,46 @@ public class CompositeRoleTest {
realm.updateCredential(realmRole1Application.getApplicationUser(), UserCredentialModel.password("password"));
+ final ApplicationModel appRoleApplication = new ApplicationManager(manager).createApplication(realm, "APP_ROLE_APPLICATION");
+ appRoleApplication.setEnabled(true);
+ appRoleApplication.setBaseUrl("http://localhost:8081/app");
+ appRoleApplication.setManagementUrl("http://localhost:8081/app/logout");
+ realm.updateCredential(appRoleApplication.getApplicationUser(), UserCredentialModel.password("password"));
+ final RoleModel appRole1 = appRoleApplication.addRole("APP_ROLE_1");
+ final RoleModel appRole2 = appRoleApplication.addRole("APP_ROLE_2");
+
+ final RoleModel realmAppCompositeRole = realm.addRole("REALM_APP_COMPOSITE_ROLE");
+ realmAppCompositeRole.setComposite(true);
+ realmAppCompositeRole.addCompositeRole(appRole1);
+
+ final UserModel realmAppCompositeUser = realm.addUser("REALM_APP_COMPOSITE_USER");
+ realmAppCompositeUser.setEnabled(true);
+ realm.updateCredential(realmAppCompositeUser, UserCredentialModel.password("password"));
+ realm.grantRole(realmAppCompositeUser, realmAppCompositeRole);
+
+ final UserModel realmAppRoleUser = realm.addUser("REALM_APP_ROLE_USER");
+ realmAppRoleUser.setEnabled(true);
+ realm.updateCredential(realmAppRoleUser, UserCredentialModel.password("password"));
+ realm.grantRole(realmAppRoleUser, appRole2);
+
+ final ApplicationModel appCompositeApplication = new ApplicationManager(manager).createApplication(realm, "APP_COMPOSITE_APPLICATION");
+ appCompositeApplication.setEnabled(true);
+ appCompositeApplication.setBaseUrl("http://localhost:8081/app");
+ appCompositeApplication.setManagementUrl("http://localhost:8081/app/logout");
+ realm.updateCredential(appCompositeApplication.getApplicationUser(), UserCredentialModel.password("password"));
+ final RoleModel appCompositeRole = appCompositeApplication.addRole("APP_COMPOSITE_ROLE");
+ appCompositeRole.setComposite(true);
+ appCompositeApplication.addScope(appRole2);
+ appCompositeRole.addCompositeRole(realmRole1);
+ appCompositeRole.addCompositeRole(realmRole2);
+ appCompositeRole.addCompositeRole(realmRole3);
+ appCompositeRole.addCompositeRole(appRole1);
+
+ final UserModel appCompositeUser = realm.addUser("APP_COMPOSITE_USER");
+ appCompositeUser.setEnabled(true);
+ realm.updateCredential(appCompositeUser, UserCredentialModel.password("password"));
+ realm.grantRole(appCompositeUser, realmAppCompositeRole);
+ realm.grantRole(appCompositeUser, realmComposite1);
deployServlet("app", "/app", ApplicationServlet.class);
@@ -116,6 +156,55 @@ public class CompositeRoleTest {
protected LoginPage loginPage;
@Test
+ public void testAppCompositeUser() throws Exception {
+ oauth.realm("Test");
+ oauth.realmPublicKey(realmPublicKey);
+ oauth.clientId("APP_COMPOSITE_APPLICATION");
+ oauth.doLogin("APP_COMPOSITE_USER", "password");
+
+ String code = oauth.getCurrentQuery().get("code");
+ AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+
+ Assert.assertEquals(200, response.getStatusCode());
+
+ Assert.assertEquals("bearer", response.getTokenType());
+
+ SkeletonKeyToken token = oauth.verifyToken(response.getAccessToken());
+
+ Assert.assertEquals("APP_COMPOSITE_USER", token.getSubject());
+
+ Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
+ Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
+ Assert.assertTrue(token.getResourceAccess("APP_ROLE_APPLICATION").isUserInRole("APP_ROLE_1"));
+ Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_ROLE_1"));
+ }
+
+
+ @Test
+ public void testRealmAppCompositeUser() throws Exception {
+ oauth.realm("Test");
+ oauth.realmPublicKey(realmPublicKey);
+ oauth.clientId("APP_ROLE_APPLICATION");
+ oauth.doLogin("REALM_APP_COMPOSITE_USER", "password");
+
+ String code = oauth.getCurrentQuery().get("code");
+ AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+
+ Assert.assertEquals(200, response.getStatusCode());
+
+ Assert.assertEquals("bearer", response.getTokenType());
+
+ SkeletonKeyToken token = oauth.verifyToken(response.getAccessToken());
+
+ Assert.assertEquals("REALM_APP_COMPOSITE_USER", token.getSubject());
+
+ Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
+ Assert.assertTrue(token.getResourceAccess("APP_ROLE_APPLICATION").isUserInRole("APP_ROLE_1"));
+ }
+
+
+
+ @Test
public void testRealmOnlyWithUserCompositeAppComposite() throws Exception {
oauth.realm("Test");
oauth.realmPublicKey(realmPublicKey);