From b109ce14b0857043f8a6a4956897b1afdb0b4ae6 Mon Sep 17 00:00:00 2001 From: Dimitri Teleguin Date: Mon, 1 Aug 2016 20:04:31 +0300 Subject: [PATCH] KEYCLOAK-3327 Make realm attributes accessible via the RealmModel --- .../idm/RealmRepresentation.java | 10 +++ .../models/cache/infinispan/RealmAdapter.java | 60 +++++++++++++++++ .../infinispan/entities/CachedRealm.java | 37 ++++++++++- .../org/keycloak/models/jpa/RealmAdapter.java | 11 ++++ .../mongo/keycloak/adapters/RealmAdapter.java | 64 ++++++++++++++++++- .../java/org/keycloak/models/RealmModel.java | 11 ++++ .../keycloak/models/entities/RealmEntity.java | 13 +++- .../models/utils/ModelToRepresentation.java | 4 ++ .../models/utils/RepresentationToModel.java | 16 +++++ .../testsuite/admin/realm/RealmTest.java | 18 ++++++ .../test/resources/admin-test/testrealm.json | 6 ++ 11 files changed, 242 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 67c1678ad1..89e0c0181f 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -123,6 +123,8 @@ public class RealmRepresentation { protected String resetCredentialsFlow; protected String clientAuthenticationFlow; + protected Map attributes; + protected String keycloakVersion; @Deprecated @@ -864,4 +866,12 @@ public class RealmRepresentation { return identityProviders != null && !identityProviders.isEmpty(); } + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public Map getAttributes() { + return attributes; + } + } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 8256aa221d..6950ed8496 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -1465,4 +1465,64 @@ public class RealmAdapter implements RealmModel { if (isUpdated()) return updated.getComponent(id); return cached.getComponents().get(id); } + + public void setAttribute(String name, String value) { + getDelegateForUpdate(); + updated.setAttribute(name, value); + } + + @Override + public void setAttribute(String name, Boolean value) { + getDelegateForUpdate(); + updated.setAttribute(name, value); + } + + @Override + public void setAttribute(String name, Integer value) { + getDelegateForUpdate(); + updated.setAttribute(name, value); + } + + @Override + public void setAttribute(String name, Long value) { + getDelegateForUpdate(); + updated.setAttribute(name, value); + } + + @Override + public void removeAttribute(String name) { + getDelegateForUpdate(); + updated.removeAttribute(name); + } + + @Override + public String getAttribute(String name) { + if (isUpdated()) return updated.getAttribute(name); + return cached.getAttribute(name); + } + + @Override + public Integer getAttribute(String name, Integer defaultValue) { + if (isUpdated()) return updated.getAttribute(name, defaultValue); + return cached.getAttribute(name, defaultValue); + } + + @Override + public Long getAttribute(String name, Long defaultValue) { + if (isUpdated()) return updated.getAttribute(name, defaultValue); + return cached.getAttribute(name, defaultValue); + } + + @Override + public Boolean getAttribute(String name, Boolean defaultValue) { + if (isUpdated()) return updated.getAttribute(name, defaultValue); + return cached.getAttribute(name, defaultValue); + } + + @Override + public Map getAttributes() { + if (isUpdated()) return updated.getAttributes(); + return cached.getAttributes(); + } + } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java index 7eab0ebac2..e85078e4c4 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java @@ -152,6 +152,8 @@ public class CachedRealm extends AbstractRevisioned { protected MultivaluedHashMap identityProviderMappers = new MultivaluedHashMap<>(); protected Set identityProviderMapperSet; + protected Map attributes; + public CachedRealm(Long revision, RealmModel model) { super(revision, model.getId()); name = model.getName(); @@ -231,10 +233,10 @@ public class CachedRealm extends AbstractRevisioned { eventsExpiration = model.getEventsExpiration(); eventsListeners = model.getEventsListeners(); enabledEventTypes = model.getEnabledEventTypes(); - + adminEventsEnabled = model.isAdminEventsEnabled(); adminEventsDetailsEnabled = model.isAdminEventsDetailsEnabled(); - + defaultRoles = model.getDefaultRoles(); ClientModel masterAdminClient = model.getMasterAdminClient(); this.masterAdminClient = (masterAdminClient != null) ? masterAdminClient.getId() : null; @@ -285,6 +287,11 @@ public class CachedRealm extends AbstractRevisioned { components.put(component.getId(), component); } + try { + attributes = model.getAttributes(); + } catch (UnsupportedOperationException ex) { + } + } protected void cacheClientTemplates(RealmModel model) { @@ -475,7 +482,7 @@ public class CachedRealm extends AbstractRevisioned { public Set getEventsListeners() { return eventsListeners; } - + public Set getEnabledEventTypes() { return enabledEventTypes; } @@ -619,4 +626,28 @@ public class CachedRealm extends AbstractRevisioned { public Map getComponents() { return components; } + + public String getAttribute(String name) { + return attributes != null ? attributes.get(name) : null; + } + + public Integer getAttribute(String name, Integer defaultValue) { + String v = getAttribute(name); + return v != null ? Integer.parseInt(v) : defaultValue; + } + + public Long getAttribute(String name, Long defaultValue) { + String v = getAttribute(name); + return v != null ? Long.parseLong(v) : defaultValue; + } + + public Boolean getAttribute(String name, Boolean defaultValue) { + String v = getAttribute(name); + return v != null ? Boolean.parseBoolean(v) : defaultValue; + } + + public Map getAttributes() { + return attributes; + } + } 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 46df41a455..a5747c6285 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 @@ -182,6 +182,7 @@ public class RealmAdapter implements RealmModel, JpaModel { em.flush(); } + @Override public void setAttribute(String name, String value) { for (RealmAttributeEntity attr : realm.getAttributes()) { if (attr.getName().equals(name)) { @@ -197,18 +198,22 @@ public class RealmAdapter implements RealmModel, JpaModel { realm.getAttributes().add(attr); } + @Override public void setAttribute(String name, Boolean value) { setAttribute(name, value.toString()); } + @Override public void setAttribute(String name, Integer value) { setAttribute(name, value.toString()); } + @Override public void setAttribute(String name, Long value) { setAttribute(name, value.toString()); } + @Override public void removeAttribute(String name) { Iterator it = realm.getAttributes().iterator(); while (it.hasNext()) { @@ -220,6 +225,7 @@ public class RealmAdapter implements RealmModel, JpaModel { } } + @Override public String getAttribute(String name) { for (RealmAttributeEntity attr : realm.getAttributes()) { if (attr.getName().equals(name)) { @@ -229,24 +235,28 @@ public class RealmAdapter implements RealmModel, JpaModel { return null; } + @Override public Integer getAttribute(String name, Integer defaultValue) { String v = getAttribute(name); return v != null ? Integer.parseInt(v) : defaultValue; } + @Override public Long getAttribute(String name, Long defaultValue) { String v = getAttribute(name); return v != null ? Long.parseLong(v) : defaultValue; } + @Override public Boolean getAttribute(String name, Boolean defaultValue) { String v = getAttribute(name); return v != null ? Boolean.parseBoolean(v) : defaultValue; } + @Override public Map getAttributes() { // should always return a copy Map result = new HashMap(); @@ -255,6 +265,7 @@ public class RealmAdapter implements RealmModel, JpaModel { } return result; } + @Override public boolean isBruteForceProtected() { return getAttribute("bruteForceProtected", false); diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index e7dc5f60ce..86a90013cc 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1230,7 +1230,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } updateRealm(); } - + @Override public boolean isAdminEventsEnabled() { return realm.isAdminEventsEnabled(); @@ -1240,7 +1240,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme public void setAdminEventsEnabled(boolean enabled) { realm.setAdminEventsEnabled(enabled); updateRealm(); - + } @Override @@ -1253,7 +1253,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme realm.setAdminEventsDetailsEnabled(enabled); updateRealm(); } - + @Override public ClientModel getMasterAdminClient() { MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext); @@ -2181,4 +2181,62 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } return null; } + + @Override + public void setAttribute(String name, String value) { + realm.getAttributes().put(name, value); + updateRealm(); + } + + @Override + public void setAttribute(String name, Boolean value) { + setAttribute(name, value.toString()); + } + + @Override + public void setAttribute(String name, Integer value) { + setAttribute(name, value.toString()); + } + + @Override + public void setAttribute(String name, Long value) { + setAttribute(name, value.toString()); + } + + @Override + public void removeAttribute(String name) { + realm.getAttributes().remove(name); + updateRealm(); + } + + @Override + public String getAttribute(String name) { + return realm.getAttributes().get(name); + } + + @Override + public Integer getAttribute(String name, Integer defaultValue) { + String v = getAttribute(name); + return v != null ? Integer.parseInt(v) : defaultValue; + } + + @Override + public Long getAttribute(String name, Long defaultValue) { + String v = getAttribute(name); + return v != null ? Long.parseLong(v) : defaultValue; + } + + @Override + public Boolean getAttribute(String name, Boolean defaultValue) { + String v = getAttribute(name); + return v != null ? Boolean.parseBoolean(v) : defaultValue; + } + + @Override + public Map getAttributes() { + Map attributes = new HashMap<>(); + attributes.putAll(realm.getAttributes()); + return attributes; + } + } diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index 04f1476716..3223fbd8d2 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -104,6 +104,17 @@ public interface RealmModel extends RoleContainerModel { void setEditUsernameAllowed(boolean editUsernameAllowed); + void setAttribute(String name, String value); + void setAttribute(String name, Boolean value); + void setAttribute(String name, Integer value); + void setAttribute(String name, Long value); + void removeAttribute(String name); + String getAttribute(String name); + Integer getAttribute(String name, Integer defaultValue); + Long getAttribute(String name, Long defaultValue); + Boolean getAttribute(String name, Boolean defaultValue); + Map getAttributes(); + //--- brute force settings boolean isBruteForceProtected(); void setBruteForceProtected(boolean value); diff --git a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java index c5a6ecf8fe..cccdacdaf3 100755 --- a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -94,6 +94,8 @@ public class RealmEntity extends AbstractIdentifiableEntity { private Map smtpConfig = new HashMap(); private Map socialConfig = new HashMap(); + private Map attributes = new HashMap<>(); + private boolean eventsEnabled; private long eventsExpiration; private List eventsListeners = new ArrayList(); @@ -692,6 +694,13 @@ public class RealmEntity extends AbstractIdentifiableEntity { public void setComponentEntities(List componentEntities) { this.componentEntities = componentEntities; } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + } - - diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index e2d4799206..2d75c547e6 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -390,6 +390,10 @@ public class ModelToRepresentation { exportRequiredActions(realm, rep); exportGroups(realm, rep); } + + Map attributes = realm.getAttributes(); + rep.setAttributes(attributes); + return rep; } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index f36f010410..2610d5370c 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -373,6 +373,15 @@ public class RepresentationToModel { if(rep.getDefaultLocale() != null){ newRealm.setDefaultLocale(rep.getDefaultLocale()); } + + // import attributes + + if (rep.getAttributes() != null) { + for (Map.Entry attr : rep.getAttributes().entrySet()) { + newRealm.setAttribute(attr.getKey(), attr.getValue()); + } + } + } protected static void importComponents(RealmModel newRealm, MultivaluedHashMap components, String parentId) { @@ -821,6 +830,13 @@ public class RepresentationToModel { if (rep.getClientAuthenticationFlow() != null) { realm.setClientAuthenticationFlow(realm.getFlowByAlias(rep.getClientAuthenticationFlow())); } + + if (rep.getAttributes() != null) { + for (Map.Entry entry : rep.getAttributes().entrySet()) { + realm.setAttribute(entry.getKey(), entry.getValue()); + } + } + } // Basic realm stuff diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java index f08f79059e..5bf2cc1651 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java @@ -57,6 +57,7 @@ import java.util.LinkedList; import java.util.List; import java.util.HashSet; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; @@ -237,6 +238,16 @@ public class RealmTest extends AbstractAdminTest { assertEquals(Boolean.FALSE, rep.isRegistrationAllowed()); assertEquals(Boolean.FALSE, rep.isRegistrationEmailAsUsername()); assertEquals(Boolean.FALSE, rep.isEditUsernameAllowed()); + + // attributes + rep.getAttributes().put("foo", "bar"); + + realm.update(rep); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM); + + rep = realm.toRepresentation(); + assertEquals("bar", rep.getAttributes().get("foo")); + } @Test @@ -393,6 +404,13 @@ public class RealmTest extends AbstractAdminTest { assertEquals(realm.getBrowserSecurityHeaders(), storedRealm.getBrowserSecurityHeaders()); } + if (realm.getAttributes() != null) { + HashMap attributes = new HashMap<>(); + attributes.putAll(storedRealm.getAttributes()); + attributes.entrySet().retainAll(realm.getAttributes().entrySet()); + assertEquals(realm.getAttributes(), attributes); + } + } @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json index bc5b2a72f7..b20eb5da1f 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json @@ -95,5 +95,11 @@ "roles": ["customer-user"] } ] + }, + "attributes": { + "string-attr": "foo", + "int-attr": "123", + "long-attr": "1234567890123456", + "bool-attr": "true" } }