keycloak-uncached

Merge pull request #1839 from patriot1burke/master default

11/18/2015 11:59:46 PM

Changes

Details

diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
index c53eb4b..db8b8d0 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
@@ -37,6 +37,14 @@
                 <constraints nullable="false"/>
             </column>
         </createTable>
+        <createTable tableName="REALM_DEFAULT_GROUPS">
+            <column name="REALM_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="GROUP_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
 
         <addColumn tableName="IDENTITY_PROVIDER">
             <column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
@@ -58,6 +66,9 @@
         <addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
         <addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
 
+        <addUniqueConstraint columnNames="GROUP_ID" constraintName="CON_GROUP_ID_DEF_GROUPS" tableName="REALM_DEFAULT_GROUPS"/>
+        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
+        <addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
         <addColumn tableName="CLIENT">
             <column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
         </addColumn>
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 e1333e9..00785cf 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -49,6 +49,7 @@ public class RealmRepresentation {
     protected RolesRepresentation roles;
     protected List<GroupRepresentation> groups;
     protected List<String> defaultRoles;
+    protected List<String> defaultGroups;
     @Deprecated
     protected Set<String> requiredCredentials;
     protected String passwordPolicy;
@@ -269,6 +270,14 @@ public class RealmRepresentation {
         this.defaultRoles = defaultRoles;
     }
 
+    public List<String> getDefaultGroups() {
+        return defaultGroups;
+    }
+
+    public void setDefaultGroups(List<String> defaultGroups) {
+        this.defaultGroups = defaultGroups;
+    }
+
     public String getPrivateKey() {
         return privateKey;
     }
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 8ac199c..6dee1cb 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -47,6 +47,19 @@ public interface RealmResource {
     @Produces(MediaType.APPLICATION_JSON)
     public GroupRepresentation getGroupByPath(@PathParam("path") String path);
 
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("default-groups")
+    public List<GroupRepresentation> getDefaultGroups();
+
+    @PUT
+    @Path("default-groups/{groupId}")
+    public void addDefaultGroup(@PathParam("groupId") String groupId);
+
+    @DELETE
+    @Path("default-groups/{groupId}")
+    public void removeDefaultGroup(@PathParam("groupId") String groupId);
+
     @Path("identity-provider")
     IdentityProvidersResource identityProviders();
 
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 389ec0a..8f5c844 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -61,6 +61,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
 
     // We are using names of defaultRoles (not ids)
     private List<String> defaultRoles = new ArrayList<String>();
+    private List<String> defaultGroups = new ArrayList<String>();
 
     private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
     private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
@@ -629,6 +630,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     public void setClientAuthenticationFlow(String clientAuthenticationFlow) {
         this.clientAuthenticationFlow = clientAuthenticationFlow;
     }
+
+    public List<String> getDefaultGroups() {
+        return defaultGroups;
+    }
+
+    public void setDefaultGroups(List<String> defaultGroups) {
+        this.defaultGroups = defaultGroups;
+    }
 }
 
 
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 641f37e..62f9162 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -165,6 +165,12 @@ public interface RealmModel extends RoleContainerModel {
 
     void updateDefaultRoles(String[] defaultRoles);
 
+    List<GroupModel> getDefaultGroups();
+
+    void addDefaultGroup(GroupModel group);
+
+    void removeDefaultGroup(GroupModel group);
+
     // Key is clientId
     Map<String, ClientModel> getClientNameMap();
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index b1d71ec..723617b 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -239,6 +239,14 @@ public class ModelToRepresentation {
             roleStrings.addAll(defaultRoles);
             rep.setDefaultRoles(roleStrings);
         }
+        List<GroupModel> defaultGroups = realm.getDefaultGroups();
+        if (!defaultGroups.isEmpty()) {
+            List<String> groupPaths = new LinkedList<>();
+            for (GroupModel group : defaultGroups) {
+                groupPaths.add(ModelToRepresentation.buildGroupPath(group));
+            }
+            rep.setDefaultGroups(groupPaths);
+        }
 
         List<RequiredCredentialModel> requiredCredentialModels = realm.getRequiredCredentials();
         if (requiredCredentialModels.size() > 0) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index bb9dbc8..dde4462 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -315,6 +315,13 @@ public class RepresentationToModel {
 
         if (rep.getGroups() != null) {
             importGroups(newRealm, rep);
+            if (rep.getDefaultGroups() != null) {
+                for (String path : rep.getDefaultGroups()) {
+                    GroupModel found = KeycloakModelUtils.findGroupByPath(newRealm, path);
+                    if (found == null) throw new RuntimeException("default group in realm rep doesn't exist: " + path);
+                    newRealm.addDefaultGroup(found);
+                }
+            }
         }
 
 
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 68e62bf..02abe5e 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -481,6 +481,30 @@ public class RealmAdapter implements RealmModel {
      }
 
     @Override
+    public List<GroupModel> getDefaultGroups() {
+        List<GroupModel> defaultGroups = new LinkedList<>();
+        for (String id : cached.getDefaultGroups()) {
+            defaultGroups.add(cacheSession.getGroupById(id, this));
+        }
+        return defaultGroups;
+
+    }
+
+    @Override
+    public void addDefaultGroup(GroupModel group) {
+        getDelegateForUpdate();
+        updated.addDefaultGroup(group);
+
+    }
+
+    @Override
+    public void removeDefaultGroup(GroupModel group) {
+        getDelegateForUpdate();
+        updated.removeDefaultGroup(group);
+
+    }
+
+    @Override
     public List<String> getDefaultRoles() {
         if (updated != null) return updated.getDefaultRoles();
         return cached.getDefaultRoles();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 604d7fa..4ae07fd 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -107,6 +107,7 @@ public class CachedRealm implements Serializable {
     protected Set<String> adminEnabledEventOperations = new HashSet<String>();
     protected boolean adminEventsDetailsEnabled;
     private List<String> defaultRoles = new LinkedList<String>();
+    private List<String> defaultGroups = new LinkedList<String>();
     private Set<String> groups = new HashSet<String>();
     private Map<String, String> realmRoles = new HashMap<String, String>();
     private Map<String, String> clients = new HashMap<String, String>();
@@ -229,6 +230,10 @@ public class CachedRealm implements Serializable {
             requiredActionProvidersByAlias.put(action.getAlias(), action);
         }
 
+        for (GroupModel group : model.getDefaultGroups()) {
+            defaultGroups.add(group.getId());
+        }
+
         browserFlow = model.getBrowserFlow();
         registrationFlow = model.getRegistrationFlow();
         directGrantFlow = model.getDirectGrantFlow();
@@ -516,4 +521,8 @@ public class CachedRealm implements Serializable {
     public Set<String> getGroups() {
         return groups;
     }
+
+    public List<String> getDefaultGroups() {
+        return defaultGroups;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
index d5851fd..2326cad 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
@@ -92,7 +92,7 @@ public class GroupEntity {
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
+        if (o == null) return false;
 
         GroupEntity that = (GroupEntity) o;
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 40c2568..6492b81 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -143,6 +143,10 @@ public class RealmEntity {
     @JoinTable(name="REALM_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
     protected Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
 
+    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+    @JoinTable(name="REALM_DEFAULT_GROUPS", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="GROUP_ID")})
+    protected Collection<GroupEntity> defaultGroups = new ArrayList<>();
+
     @Column(name="EVENTS_ENABLED")
     protected boolean eventsEnabled;
     @Column(name="EVENTS_EXPIRATION")
@@ -426,6 +430,14 @@ public class RealmEntity {
         this.defaultRoles = defaultRoles;
     }
 
+    public Collection<GroupEntity> getDefaultGroups() {
+        return defaultGroups;
+    }
+
+    public void setDefaultGroups(Collection<GroupEntity> defaultGroups) {
+        this.defaultGroups = defaultGroups;
+    }
+
     public String getPasswordPolicy() {
         return passwordPolicy;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 61e4982..64650c9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -71,6 +71,10 @@ public class JpaUserProvider implements UserProvider {
                     userModel.grantRole(application.getRole(r));
                 }
             }
+
+            for (GroupModel g : realm.getDefaultGroups()) {
+                userModel.joinGroup(g);
+            }
         }
         for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) {
             if (r.isEnabled() && r.isDefaultAction()) {
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 eae002b..1397cad 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
@@ -648,6 +648,44 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public List<GroupModel> getDefaultGroups() {
+        Collection<GroupEntity> entities = realm.getDefaultGroups();
+        List<GroupModel> defaultGroups = new LinkedList<>();
+        for (GroupEntity entity : entities) {
+            defaultGroups.add(session.realms().getGroupById(entity.getId(), this));
+        }
+        return defaultGroups;
+    }
+
+    @Override
+    public void addDefaultGroup(GroupModel group) {
+        Collection<GroupEntity> entities = realm.getDefaultGroups();
+        for (GroupEntity entity : entities) {
+            if (entity.getId().equals(group.getId())) return;
+        }
+        GroupEntity groupEntity = GroupAdapter.toEntity(group, em);
+        realm.getDefaultGroups().add(groupEntity);
+        em.flush();
+
+    }
+
+    @Override
+    public void removeDefaultGroup(GroupModel group) {
+        GroupEntity found = null;
+        for (GroupEntity defaultGroup : realm.getDefaultGroups()) {
+            if (defaultGroup.getId().equals(group.getId())) {
+                found = defaultGroup;
+                break;
+            }
+        }
+        if (found != null) {
+            realm.getDefaultGroups().remove(found);
+            em.flush();
+        }
+
+    }
+
+    @Override
     public Map<String, ClientModel> getClientNameMap() {
         Map<String, ClientModel> map = new HashMap<String, ClientModel>();
         for (ClientModel app : getClients()) {
@@ -2002,6 +2040,7 @@ public class RealmAdapter implements RealmModel {
         if (!groupEntity.getRealm().getId().equals(getId())) {
             return false;
         }
+        realm.getDefaultRoles().remove(groupEntity);
         for (GroupModel subGroup : group.getSubGroups()) {
             removeGroup(subGroup);
         }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 9b4f1f8..44758b7 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -300,6 +300,9 @@ public class MongoUserProvider implements UserProvider {
                     userModel.grantRole(application.getRole(r));
                 }
             }
+            for (GroupModel g : realm.getDefaultGroups()) {
+                userModel.joinGroup(g);
+            }
         }
 
         if (addDefaultRequiredActions) {
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 2fe85a1..8f8d43d 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
@@ -681,6 +681,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
 
     @Override
     public boolean removeGroup(GroupModel group) {
+        if (realm.getDefaultGroups() != null) {
+            getMongoStore().pullItemFromList(realm, "defaultGroups", group.getId(), invocationContext);
+        }
         for (GroupModel subGroup : group.getSubGroups()) {
             removeGroup(subGroup);
         }
@@ -689,6 +692,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         return getMongoStore().removeEntity(MongoGroupEntity.class, group.getId(), invocationContext);
     }
 
+
+
     @Override
     public List<String> getDefaultRoles() {
         return realm.getDefaultRoles();
@@ -721,6 +726,27 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public List<GroupModel> getDefaultGroups() {
+        List<GroupModel> defaultGroups = new LinkedList<>();
+        for (String id : realm.getDefaultGroups()) {
+            defaultGroups.add(session.realms().getGroupById(id, this));
+        }
+        return defaultGroups;
+    }
+
+    @Override
+    public void addDefaultGroup(GroupModel group) {
+        getMongoStore().pushItemToList(realm, "defaultGroups", group.getId(), true, invocationContext);
+
+    }
+
+    @Override
+    public void removeDefaultGroup(GroupModel group) {
+        getMongoStore().pullItemFromList(realm, "defaultGroups", group.getId(), invocationContext);
+
+    }
+
+    @Override
     public ClientModel getClientById(String id) {
         return model.getClientById(id, this);
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 9845031..d874d1e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -634,6 +634,48 @@ public class RealmAdminResource {
         return new IdentityProvidersResource(realm, session, this.auth, adminEvent);
     }
 
+    /**
+     * Get group hierarchy.  Only name and ids are returned.
+     *
+     * @return
+     */
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("default-groups")
+    public List<GroupRepresentation> getDefaultGroups() {
+        this.auth.requireView();
+        List<GroupRepresentation> defaults = new LinkedList<>();
+        for (GroupModel group : realm.getDefaultGroups()) {
+            defaults.add(ModelToRepresentation.toRepresentation(group, false));
+        }
+        return defaults;
+    }
+    @PUT
+    @NoCache
+    @Path("default-groups/{groupId}")
+    public void addDefaultGroup(@PathParam("groupId") String groupId) {
+        this.auth.requireManage();
+        GroupModel group = realm.getGroupById(groupId);
+        if (group == null) {
+            throw new NotFoundException("Group not found");
+        }
+        realm.addDefaultGroup(group);
+    }
+
+    @DELETE
+    @NoCache
+    @Path("default-groups/{groupId}")
+    public void removeDefaultGroup(@PathParam("groupId") String groupId) {
+        this.auth.requireManage();
+        GroupModel group = realm.getGroupById(groupId);
+        if (group == null) {
+            throw new NotFoundException("Group not found");
+        }
+        realm.removeDefaultGroup(group);
+    }
+
+
     @Path("groups")
     public GroupsResource getGroups() {
         GroupsResource resource =  new GroupsResource(realm, session, this.auth, adminEvent);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
index d2882dc..97250c4 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
@@ -180,6 +180,25 @@ public class GroupTest {
         Assert.assertTrue(token.getRealmAccess().getRoles().contains("level2Role"));
         Assert.assertTrue(token.getRealmAccess().getRoles().contains("level3Role"));
 
+        realm.addDefaultGroup(level3Group.getId());
+
+        List<GroupRepresentation> defaultGroups = realm.getDefaultGroups();
+        Assert.assertEquals(1, defaultGroups.size());
+        Assert.assertEquals(defaultGroups.get(0).getId(), level3Group.getId());
+
+        UserRepresentation newUser = new UserRepresentation();
+        newUser.setUsername("groupUser");
+        newUser.setEmail("group@group.com");
+        response = realm.users().create(newUser);
+        response.close();
+        newUser =  realm.users().search("groupUser", -1, -1).get(0);
+        membership = realm.users().get(newUser.getId()).groups();
+        Assert.assertEquals(1, membership.size());
+        Assert.assertEquals("level3", membership.get(0).getName());
+
+        realm.removeDefaultGroup(level3Group.getId());
+        defaultGroups = realm.getDefaultGroups();
+        Assert.assertEquals(0, defaultGroups.size());
 
     }