default groups

This commit is contained in:
Bill Burke 2015-11-18 19:40:30 -05:00
parent 6989589e72
commit 98958a2bc4
17 changed files with 242 additions and 1 deletions

View file

@ -37,6 +37,14 @@
<constraints nullable="false"/> <constraints nullable="false"/>
</column> </column>
</createTable> </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"> <addColumn tableName="IDENTITY_PROVIDER">
<column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)"> <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="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"/> <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"> <addColumn tableName="CLIENT">
<column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/> <column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
</addColumn> </addColumn>

View file

@ -49,6 +49,7 @@ public class RealmRepresentation {
protected RolesRepresentation roles; protected RolesRepresentation roles;
protected List<GroupRepresentation> groups; protected List<GroupRepresentation> groups;
protected List<String> defaultRoles; protected List<String> defaultRoles;
protected List<String> defaultGroups;
@Deprecated @Deprecated
protected Set<String> requiredCredentials; protected Set<String> requiredCredentials;
protected String passwordPolicy; protected String passwordPolicy;
@ -269,6 +270,14 @@ public class RealmRepresentation {
this.defaultRoles = defaultRoles; this.defaultRoles = defaultRoles;
} }
public List<String> getDefaultGroups() {
return defaultGroups;
}
public void setDefaultGroups(List<String> defaultGroups) {
this.defaultGroups = defaultGroups;
}
public String getPrivateKey() { public String getPrivateKey() {
return privateKey; return privateKey;
} }

View file

@ -47,6 +47,19 @@ public interface RealmResource {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public GroupRepresentation getGroupByPath(@PathParam("path") String path); 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") @Path("identity-provider")
IdentityProvidersResource identityProviders(); IdentityProvidersResource identityProviders();

View file

@ -165,6 +165,12 @@ public interface RealmModel extends RoleContainerModel {
void updateDefaultRoles(String[] defaultRoles); void updateDefaultRoles(String[] defaultRoles);
List<GroupModel> getDefaultGroups();
void addDefaultGroup(GroupModel group);
void removeDefaultGroup(GroupModel group);
// Key is clientId // Key is clientId
Map<String, ClientModel> getClientNameMap(); Map<String, ClientModel> getClientNameMap();

View file

@ -61,6 +61,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
// We are using names of defaultRoles (not ids) // We are using names of defaultRoles (not ids)
private List<String> defaultRoles = new ArrayList<String>(); private List<String> defaultRoles = new ArrayList<String>();
private List<String> defaultGroups = new ArrayList<String>();
private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>(); private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>(); private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
@ -629,6 +630,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
public void setClientAuthenticationFlow(String clientAuthenticationFlow) { public void setClientAuthenticationFlow(String clientAuthenticationFlow) {
this.clientAuthenticationFlow = clientAuthenticationFlow; this.clientAuthenticationFlow = clientAuthenticationFlow;
} }
public List<String> getDefaultGroups() {
return defaultGroups;
}
public void setDefaultGroups(List<String> defaultGroups) {
this.defaultGroups = defaultGroups;
}
} }

View file

@ -239,6 +239,14 @@ public class ModelToRepresentation {
roleStrings.addAll(defaultRoles); roleStrings.addAll(defaultRoles);
rep.setDefaultRoles(roleStrings); 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(); List<RequiredCredentialModel> requiredCredentialModels = realm.getRequiredCredentials();
if (requiredCredentialModels.size() > 0) { if (requiredCredentialModels.size() > 0) {

View file

@ -315,6 +315,13 @@ public class RepresentationToModel {
if (rep.getGroups() != null) { if (rep.getGroups() != null) {
importGroups(newRealm, rep); 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);
}
}
} }

View file

@ -480,6 +480,30 @@ public class RealmAdapter implements RealmModel {
return cacheSession.getRoleById(id, this); return cacheSession.getRoleById(id, this);
} }
@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 @Override
public List<String> getDefaultRoles() { public List<String> getDefaultRoles() {
if (updated != null) return updated.getDefaultRoles(); if (updated != null) return updated.getDefaultRoles();

View file

@ -107,6 +107,7 @@ public class CachedRealm implements Serializable {
protected Set<String> adminEnabledEventOperations = new HashSet<String>(); protected Set<String> adminEnabledEventOperations = new HashSet<String>();
protected boolean adminEventsDetailsEnabled; protected boolean adminEventsDetailsEnabled;
private List<String> defaultRoles = new LinkedList<String>(); private List<String> defaultRoles = new LinkedList<String>();
private List<String> defaultGroups = new LinkedList<String>();
private Set<String> groups = new HashSet<String>(); private Set<String> groups = new HashSet<String>();
private Map<String, String> realmRoles = new HashMap<String, String>(); private Map<String, String> realmRoles = new HashMap<String, String>();
private Map<String, String> clients = 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); requiredActionProvidersByAlias.put(action.getAlias(), action);
} }
for (GroupModel group : model.getDefaultGroups()) {
defaultGroups.add(group.getId());
}
browserFlow = model.getBrowserFlow(); browserFlow = model.getBrowserFlow();
registrationFlow = model.getRegistrationFlow(); registrationFlow = model.getRegistrationFlow();
directGrantFlow = model.getDirectGrantFlow(); directGrantFlow = model.getDirectGrantFlow();
@ -516,4 +521,8 @@ public class CachedRealm implements Serializable {
public Set<String> getGroups() { public Set<String> getGroups() {
return groups; return groups;
} }
public List<String> getDefaultGroups() {
return defaultGroups;
}
} }

View file

@ -71,6 +71,10 @@ public class JpaUserProvider implements UserProvider {
userModel.grantRole(application.getRole(r)); userModel.grantRole(application.getRole(r));
} }
} }
for (GroupModel g : realm.getDefaultGroups()) {
userModel.joinGroup(g);
}
} }
for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) { for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) {
if (r.isEnabled() && r.isDefaultAction()) { if (r.isEnabled() && r.isDefaultAction()) {

View file

@ -647,6 +647,44 @@ public class RealmAdapter implements RealmModel {
em.flush(); em.flush();
} }
@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 @Override
public Map<String, ClientModel> getClientNameMap() { public Map<String, ClientModel> getClientNameMap() {
Map<String, ClientModel> map = new HashMap<String, ClientModel>(); Map<String, ClientModel> map = new HashMap<String, ClientModel>();
@ -2002,6 +2040,7 @@ public class RealmAdapter implements RealmModel {
if (!groupEntity.getRealm().getId().equals(getId())) { if (!groupEntity.getRealm().getId().equals(getId())) {
return false; return false;
} }
realm.getDefaultRoles().remove(groupEntity);
for (GroupModel subGroup : group.getSubGroups()) { for (GroupModel subGroup : group.getSubGroups()) {
removeGroup(subGroup); removeGroup(subGroup);
} }

View file

@ -92,7 +92,7 @@ public class GroupEntity {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null) return false;
GroupEntity that = (GroupEntity) o; GroupEntity that = (GroupEntity) o;

View file

@ -143,6 +143,10 @@ public class RealmEntity {
@JoinTable(name="REALM_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")}) @JoinTable(name="REALM_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
protected Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>(); 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") @Column(name="EVENTS_ENABLED")
protected boolean eventsEnabled; protected boolean eventsEnabled;
@Column(name="EVENTS_EXPIRATION") @Column(name="EVENTS_EXPIRATION")
@ -426,6 +430,14 @@ public class RealmEntity {
this.defaultRoles = defaultRoles; this.defaultRoles = defaultRoles;
} }
public Collection<GroupEntity> getDefaultGroups() {
return defaultGroups;
}
public void setDefaultGroups(Collection<GroupEntity> defaultGroups) {
this.defaultGroups = defaultGroups;
}
public String getPasswordPolicy() { public String getPasswordPolicy() {
return passwordPolicy; return passwordPolicy;
} }

View file

@ -300,6 +300,9 @@ public class MongoUserProvider implements UserProvider {
userModel.grantRole(application.getRole(r)); userModel.grantRole(application.getRole(r));
} }
} }
for (GroupModel g : realm.getDefaultGroups()) {
userModel.joinGroup(g);
}
} }
if (addDefaultRequiredActions) { if (addDefaultRequiredActions) {

View file

@ -681,6 +681,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override @Override
public boolean removeGroup(GroupModel group) { public boolean removeGroup(GroupModel group) {
if (realm.getDefaultGroups() != null) {
getMongoStore().pullItemFromList(realm, "defaultGroups", group.getId(), invocationContext);
}
for (GroupModel subGroup : group.getSubGroups()) { for (GroupModel subGroup : group.getSubGroups()) {
removeGroup(subGroup); removeGroup(subGroup);
} }
@ -689,6 +692,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
return getMongoStore().removeEntity(MongoGroupEntity.class, group.getId(), invocationContext); return getMongoStore().removeEntity(MongoGroupEntity.class, group.getId(), invocationContext);
} }
@Override @Override
public List<String> getDefaultRoles() { public List<String> getDefaultRoles() {
return realm.getDefaultRoles(); return realm.getDefaultRoles();
@ -720,6 +725,27 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
updateRealm(); updateRealm();
} }
@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 @Override
public ClientModel getClientById(String id) { public ClientModel getClientById(String id) {
return model.getClientById(id, this); return model.getClientById(id, this);

View file

@ -634,6 +634,48 @@ public class RealmAdminResource {
return new IdentityProvidersResource(realm, session, this.auth, adminEvent); 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") @Path("groups")
public GroupsResource getGroups() { public GroupsResource getGroups() {
GroupsResource resource = new GroupsResource(realm, session, this.auth, adminEvent); GroupsResource resource = new GroupsResource(realm, session, this.auth, adminEvent);

View file

@ -180,6 +180,25 @@ public class GroupTest {
Assert.assertTrue(token.getRealmAccess().getRoles().contains("level2Role")); Assert.assertTrue(token.getRealmAccess().getRoles().contains("level2Role"));
Assert.assertTrue(token.getRealmAccess().getRoles().contains("level3Role")); 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());
} }