KEYCLOAK-4003: Slow Infinispan RoleAdapter.hasRole() call.
- Added a session/query cache for the result getComposites() to avoid always hitting the Infinispan cache. - KeycloakModelUtils doesn't rely anymore on a "visited" set as performance seems good without it. - Added test for multiple levels of composite roles. Only one level was covered.
This commit is contained in:
parent
45abaec86e
commit
6fa504489f
6 changed files with 79 additions and 42 deletions
|
@ -38,6 +38,7 @@ public class RoleAdapter implements RoleModel {
|
|||
protected CachedRole cached;
|
||||
protected RealmCacheSession cacheSession;
|
||||
protected RealmModel realm;
|
||||
protected Set<RoleModel> composites;
|
||||
|
||||
public RoleAdapter(CachedRole cached, RealmCacheSession session, RealmModel realm) {
|
||||
this.cached = cached;
|
||||
|
@ -132,15 +133,19 @@ public class RoleAdapter implements RoleModel {
|
|||
@Override
|
||||
public Set<RoleModel> getComposites() {
|
||||
if (isUpdated()) return updated.getComposites();
|
||||
Set<RoleModel> set = new HashSet<RoleModel>();
|
||||
for (String id : cached.getComposites()) {
|
||||
RoleModel role = realm.getRoleById(id);
|
||||
if (role == null) {
|
||||
throw new IllegalStateException("Could not find composite in role " + getName() + ": " + id);
|
||||
|
||||
if (composites == null) {
|
||||
composites = new HashSet<RoleModel>();
|
||||
for (String id : cached.getComposites()) {
|
||||
RoleModel role = realm.getRoleById(id);
|
||||
if (role == null) {
|
||||
throw new IllegalStateException("Could not find composite in role " + getName() + ": " + id);
|
||||
}
|
||||
composites.add(role);
|
||||
}
|
||||
set.add(role);
|
||||
}
|
||||
return set;
|
||||
|
||||
return composites;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -171,11 +176,7 @@ public class RoleAdapter implements RoleModel {
|
|||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (this.equals(role)) return true;
|
||||
if (!isComposite()) return false;
|
||||
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
return KeycloakModelUtils.searchFor(role, this, visited);
|
||||
return this.equals(role) || KeycloakModelUtils.searchFor(role, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -128,11 +128,7 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
|
|||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (this.equals(role)) return true;
|
||||
if (!isComposite()) return false;
|
||||
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
return KeycloakModelUtils.searchFor(role, this, visited);
|
||||
return this.equals(role) || KeycloakModelUtils.searchFor(role, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -172,11 +172,7 @@ public class RoleAdapter extends AbstractMongoAdapter<MongoRoleEntity> implement
|
|||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (this.equals(role)) return true;
|
||||
if (!isComposite()) return false;
|
||||
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
return KeycloakModelUtils.searchFor(role, this, visited);
|
||||
return this.equals(role) || KeycloakModelUtils.searchFor(role, this);
|
||||
}
|
||||
|
||||
public MongoRoleEntity getRole() {
|
||||
|
|
|
@ -177,16 +177,14 @@ public final class KeycloakModelUtils {
|
|||
* @param visited set of already visited roles (used for recursion)
|
||||
* @return true if "role" is descendant of "composite"
|
||||
*/
|
||||
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 (searchFor(role, contained, visited)) return true;
|
||||
}
|
||||
return false;
|
||||
public static boolean searchFor(RoleModel role, RoleModel composite) {
|
||||
return composite.isComposite() && (
|
||||
composite.getComposites().contains(role) ||
|
||||
composite.getComposites().stream()
|
||||
.filter(x -> x.isComposite() && x.hasRole(role))
|
||||
.findFirst()
|
||||
.isPresent()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -66,18 +66,27 @@ public class CompositeRolesModelTest extends AbstractModelTest {
|
|||
@Test
|
||||
public void testComposites() {
|
||||
Set<RoleModel> requestedRoles = getRequestedRoles("APP_COMPOSITE_APPLICATION", "APP_COMPOSITE_USER");
|
||||
Assert.assertEquals(2, requestedRoles.size());
|
||||
Assert.assertEquals(5, requestedRoles.size());
|
||||
assertContains("APP_COMPOSITE_APPLICATION", "APP_COMPOSITE_ROLE", requestedRoles);
|
||||
assertContains("APP_COMPOSITE_APPLICATION", "APP_COMPOSITE_CHILD", requestedRoles);
|
||||
assertContains("APP_COMPOSITE_APPLICATION", "APP_ROLE_2", requestedRoles);
|
||||
assertContains("APP_ROLE_APPLICATION", "APP_ROLE_1", requestedRoles);
|
||||
assertContains("realm", "REALM_ROLE_1", requestedRoles);
|
||||
|
||||
requestedRoles = getRequestedRoles("APP_COMPOSITE_APPLICATION", "REALM_APP_COMPOSITE_USER");
|
||||
Assert.assertEquals(1, requestedRoles.size());
|
||||
Assert.assertEquals(4, requestedRoles.size());
|
||||
assertContains("APP_ROLE_APPLICATION", "APP_ROLE_1", requestedRoles);
|
||||
|
||||
requestedRoles = getRequestedRoles("REALM_COMPOSITE_1_APPLICATION", "REALM_COMPOSITE_1_USER");
|
||||
Assert.assertEquals(1, requestedRoles.size());
|
||||
assertContains("realm", "REALM_COMPOSITE_1", requestedRoles);
|
||||
|
||||
requestedRoles = getRequestedRoles("REALM_COMPOSITE_2_APPLICATION", "REALM_COMPOSITE_1_USER");
|
||||
Assert.assertEquals(3, requestedRoles.size());
|
||||
assertContains("realm", "REALM_COMPOSITE_1", requestedRoles);
|
||||
assertContains("realm", "REALM_COMPOSITE_CHILD", requestedRoles);
|
||||
assertContains("realm", "REALM_ROLE_4", requestedRoles);
|
||||
|
||||
requestedRoles = getRequestedRoles("REALM_ROLE_1_APPLICATION", "REALM_COMPOSITE_1_USER");
|
||||
Assert.assertEquals(1, requestedRoles.size());
|
||||
assertContains("realm", "REALM_ROLE_1", requestedRoles);
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"email" : "test-user1@localhost",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "REALM_COMPOSITE_1" ]
|
||||
},
|
||||
|
@ -31,7 +31,7 @@
|
|||
"email" : "test-user2@localhost",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "REALM_ROLE_1"]
|
||||
},
|
||||
|
@ -41,7 +41,7 @@
|
|||
"email" : "test-user3@localhost",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "REALM_APP_COMPOSITE_ROLE" ]
|
||||
},
|
||||
|
@ -51,7 +51,7 @@
|
|||
"email" : "test-user4@localhost",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
"value" : "password" }
|
||||
],
|
||||
"applicationRoles": {
|
||||
"APP_ROLE_APPLICATION": [ "APP_ROLE_2" ]
|
||||
|
@ -63,7 +63,7 @@
|
|||
"email" : "test-user5@localhost",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": ["REALM_APP_COMPOSITE_ROLE", "REALM_COMPOSITE_1"]
|
||||
}
|
||||
|
@ -80,6 +80,10 @@
|
|||
"client": "REALM_COMPOSITE_1_APPLICATION",
|
||||
"roles": ["REALM_COMPOSITE_1"]
|
||||
},
|
||||
{
|
||||
"client": "REALM_COMPOSITE_2_APPLICATION",
|
||||
"roles": ["REALM_COMPOSITE_1", "REALM_COMPOSITE_CHILD", "REALM_ROLE_4"]
|
||||
},
|
||||
{
|
||||
"client": "REALM_ROLE_1_APPLICATION",
|
||||
"roles": ["REALM_ROLE_1"]
|
||||
|
@ -93,7 +97,15 @@
|
|||
"baseUrl": "http://localhost:8081/app",
|
||||
"adminUrl": "http://localhost:8081/app/logout",
|
||||
"secret": "password"
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "REALM_COMPOSITE_2_APPLICATION",
|
||||
"fullScopeAllowed": false,
|
||||
"enabled": true,
|
||||
"baseUrl": "http://localhost:8081/app",
|
||||
"adminUrl": "http://localhost:8081/app/logout",
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"name": "REALM_ROLE_1_APPLICATION",
|
||||
"fullScopeAllowed": false,
|
||||
|
@ -130,10 +142,19 @@
|
|||
{
|
||||
"name": "REALM_ROLE_3"
|
||||
},
|
||||
{
|
||||
"name": "REALM_ROLE_4"
|
||||
},
|
||||
{
|
||||
"name": "REALM_COMPOSITE_1",
|
||||
"composites": {
|
||||
"realm": ["REALM_ROLE_1"]
|
||||
"realm": ["REALM_ROLE_1", "REALM_COMPOSITE_CHILD"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "REALM_COMPOSITE_CHILD",
|
||||
"composites": {
|
||||
"realm": ["REALM_ROLE_4"]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -142,6 +163,9 @@
|
|||
"application": {
|
||||
"APP_ROLE_APPLICATION" :[
|
||||
"APP_ROLE_1"
|
||||
],
|
||||
"APP_COMPOSITE_APPLICATION" :[
|
||||
"APP_COMPOSITE_ROLE"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +192,19 @@
|
|||
"application": {
|
||||
"APP_ROLE_APPLICATION" :[
|
||||
"APP_ROLE_1"
|
||||
],
|
||||
"APP_COMPOSITE_APPLICATION" :[
|
||||
"APP_COMPOSITE_CHILD"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "APP_COMPOSITE_CHILD",
|
||||
"composites": {
|
||||
"application": {
|
||||
"APP_COMPOSITE_APPLICATION" :[
|
||||
"APP_ROLE_2"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +221,7 @@
|
|||
"APP_ROLE_APPLICATION": [
|
||||
{
|
||||
"client": "APP_COMPOSITE_APPLICATION",
|
||||
"roles": ["APP_ROLE_2"]
|
||||
"roles": ["APP_ROLE_1"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue