Make sure organization groups can not be managed but when managing an organization
Closes #29431 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
401d58a905
commit
b50d481b10
18 changed files with 580 additions and 339 deletions
|
@ -77,6 +77,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||||
@Override
|
@Override
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
group.setName(name);
|
group.setName(name);
|
||||||
|
fireGroupUpdatedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -105,6 +106,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||||
GroupEntity parentEntity = toEntity(parent, em);
|
GroupEntity parentEntity = toEntity(parent, em);
|
||||||
group.setParentId(parentEntity.getId());
|
group.setParentId(parentEntity.getId());
|
||||||
}
|
}
|
||||||
|
fireGroupUpdatedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -113,6 +115,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
subGroup.setParent(this);
|
subGroup.setParent(this);
|
||||||
|
fireGroupUpdatedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,6 +124,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
subGroup.setParent(null);
|
subGroup.setParent(null);
|
||||||
|
fireGroupUpdatedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -176,10 +180,12 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
|
fireGroupUpdatedEvent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
persistAttributeValue(name, value);
|
persistAttributeValue(name, value);
|
||||||
|
fireGroupUpdatedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -213,6 +219,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||||
em.remove(attr);
|
em.remove(attr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fireGroupUpdatedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -264,6 +271,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
em.flush();
|
em.flush();
|
||||||
em.detach(entity);
|
em.detach(entity);
|
||||||
|
fireGroupUpdatedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -293,6 +301,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||||
em.remove(entity);
|
em.remove(entity);
|
||||||
}
|
}
|
||||||
em.flush();
|
em.flush();
|
||||||
|
fireGroupUpdatedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -318,4 +327,8 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||||
public boolean escapeSlashesInGroupPath() {
|
public boolean escapeSlashesInGroupPath() {
|
||||||
return KeycloakModelUtils.escapeSlashesInGroupPath(session);
|
return KeycloakModelUtils.escapeSlashesInGroupPath(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fireGroupUpdatedEvent() {
|
||||||
|
GroupUpdatedEvent.fire(this, session);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,9 @@ import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.ClientScopeProvider;
|
import org.keycloak.models.ClientScopeProvider;
|
||||||
import org.keycloak.models.DeploymentStateProvider;
|
import org.keycloak.models.DeploymentStateProvider;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.GroupModel.GroupCreatedEvent;
|
||||||
|
import org.keycloak.models.GroupModel.GroupPathChangeEvent;
|
||||||
|
import org.keycloak.models.GroupModel.GroupUpdatedEvent;
|
||||||
import org.keycloak.models.GroupProvider;
|
import org.keycloak.models.GroupProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
@ -524,29 +527,8 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||||
String newPath = KeycloakModelUtils.buildGroupPath(group);
|
String newPath = KeycloakModelUtils.buildGroupPath(group);
|
||||||
String previousPath = KeycloakModelUtils.buildGroupPath(group, previousParent);
|
String previousPath = KeycloakModelUtils.buildGroupPath(group, previousParent);
|
||||||
|
|
||||||
GroupModel.GroupPathChangeEvent event =
|
GroupPathChangeEvent.fire(group, newPath, previousPath, session);
|
||||||
new GroupModel.GroupPathChangeEvent() {
|
fireGroupUpdatedEvent(group);
|
||||||
@Override
|
|
||||||
public RealmModel getRealm() {
|
|
||||||
return realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getNewPath() {
|
|
||||||
return newPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPreviousPath() {
|
|
||||||
return previousPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakSession getKeycloakSession() {
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
session.getKeycloakSessionFactory().publish(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -733,12 +715,17 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||||
em.persist(groupEntity);
|
em.persist(groupEntity);
|
||||||
em.flush();
|
em.flush();
|
||||||
|
|
||||||
return new GroupAdapter(session, realm, em, groupEntity);
|
GroupAdapter group = new GroupAdapter(session, realm, em, groupEntity);
|
||||||
|
|
||||||
|
fireGroupCreatedEvent(group);
|
||||||
|
|
||||||
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTopLevelGroup(RealmModel realm, GroupModel subGroup) {
|
public void addTopLevelGroup(RealmModel realm, GroupModel subGroup) {
|
||||||
subGroup.setParent(null);
|
subGroup.setParent(null);
|
||||||
|
fireGroupUpdatedEvent(subGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void preRemove(RealmModel realm, RoleModel role) {
|
public void preRemove(RealmModel realm, RoleModel role) {
|
||||||
|
@ -1262,4 +1249,12 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||||
public Set<String> getClientSearchableAttributes() {
|
public Set<String> getClientSearchableAttributes() {
|
||||||
return clientSearchableAttributes;
|
return clientSearchableAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fireGroupUpdatedEvent(GroupModel group) {
|
||||||
|
GroupUpdatedEvent.fire(group, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireGroupCreatedEvent(GroupAdapter group) {
|
||||||
|
GroupCreatedEvent.fire(group, session);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.component.ComponentFactory;
|
import org.keycloak.component.ComponentFactory;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.*;
|
||||||
|
import org.keycloak.models.GroupModel.GroupUpdatedEvent;
|
||||||
import org.keycloak.models.jpa.entities.*;
|
import org.keycloak.models.jpa.entities.*;
|
||||||
import org.keycloak.models.utils.ComponentUtil;
|
import org.keycloak.models.utils.ComponentUtil;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
@ -722,6 +723,7 @@ public class RealmAdapter implements StorageProviderRealmModel, JpaModel<RealmEn
|
||||||
|
|
||||||
groupsIds.add(group.getId());
|
groupsIds.add(group.getId());
|
||||||
em.flush();
|
em.flush();
|
||||||
|
GroupUpdatedEvent.fire(group, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -851,12 +853,12 @@ public class RealmAdapter implements StorageProviderRealmModel, JpaModel<RealmEn
|
||||||
public Stream<RoleModel> getRolesStream() {
|
public Stream<RoleModel> getRolesStream() {
|
||||||
return session.roles().getRealmRolesStream(this);
|
return session.roles().getRealmRolesStream(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RoleModel> getRolesStream(Integer first, Integer max) {
|
public Stream<RoleModel> getRolesStream(Integer first, Integer max) {
|
||||||
return session.roles().getRealmRolesStream(this, first, max);
|
return session.roles().getRealmRolesStream(this, first, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
|
public Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
|
||||||
return session.roles().searchForRolesStream(this, search, first, max);
|
return session.roles().searchForRolesStream(this, search, first, max);
|
||||||
|
|
|
@ -22,6 +22,8 @@ import org.keycloak.common.util.ObjectUtil;
|
||||||
import org.keycloak.credential.UserCredentialManager;
|
import org.keycloak.credential.UserCredentialManager;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.GroupModel.GroupMemberJoinEvent;
|
||||||
|
import org.keycloak.models.GroupModel.GroupMemberLeaveEvent;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -412,6 +414,7 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
em.flush();
|
em.flush();
|
||||||
em.detach(entity);
|
em.detach(entity);
|
||||||
|
GroupMemberJoinEvent.fire(group, session);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +430,7 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
|
||||||
em.remove(entity);
|
em.remove(entity);
|
||||||
}
|
}
|
||||||
em.flush();
|
em.flush();
|
||||||
|
GroupMemberLeaveEvent.fire(group, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -544,6 +547,4 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getId().hashCode();
|
return getId().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,10 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
private final GroupProvider groupProvider;
|
private final GroupProvider groupProvider;
|
||||||
private final UserProvider userProvider;
|
private final UserProvider userProvider;
|
||||||
private final RealmModel realm;
|
private final RealmModel realm;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
|
||||||
public JpaOrganizationProvider(KeycloakSession session) {
|
public JpaOrganizationProvider(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
||||||
groupProvider = session.groups();
|
groupProvider = session.groups();
|
||||||
userProvider = session.users();
|
userProvider = session.users();
|
||||||
|
@ -85,21 +87,22 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
throw new ModelDuplicateException("A organization with the same name already exists.");
|
throw new ModelDuplicateException("A organization with the same name already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
OrganizationEntity entity = new OrganizationEntity();
|
OrganizationAdapter adapter = new OrganizationAdapter(realm, this);
|
||||||
entity.setId(KeycloakModelUtils.generateId());
|
|
||||||
|
|
||||||
GroupModel group = createOrganizationGroup(entity.getId());
|
try {
|
||||||
|
session.setAttribute(OrganizationModel.class.getName(), adapter);
|
||||||
|
GroupModel group = createOrganizationGroup(adapter.getId());
|
||||||
|
|
||||||
entity.setGroupId(group.getId());
|
adapter.setGroupId(group.getId());
|
||||||
entity.setRealmId(realm.getId());
|
adapter.setName(name);
|
||||||
entity.setName(name);
|
adapter.setEnabled(true);
|
||||||
entity.setEnabled(true);
|
|
||||||
|
|
||||||
em.persist(entity);
|
em.persist(adapter.getEntity());
|
||||||
|
|
||||||
OrganizationAdapter adapter = new OrganizationAdapter(realm, entity, this);
|
adapter.setDomains(domains.stream().map(OrganizationDomainModel::new).collect(Collectors.toSet()));
|
||||||
|
} finally {
|
||||||
adapter.setDomains(domains.stream().map(OrganizationDomainModel::new).collect(Collectors.toSet()));
|
session.removeAttribute(OrganizationModel.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
@ -107,17 +110,22 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
@Override
|
@Override
|
||||||
public boolean remove(OrganizationModel organization) {
|
public boolean remove(OrganizationModel organization) {
|
||||||
OrganizationEntity entity = getEntity(organization.getId());
|
OrganizationEntity entity = getEntity(organization.getId());
|
||||||
GroupModel group = getOrganizationGroup(entity);
|
|
||||||
|
|
||||||
if (group != null) {
|
try {
|
||||||
//TODO: won't scale, requires a better mechanism for bulk deleting users
|
session.setAttribute(OrganizationModel.class.getName(), organization);
|
||||||
userProvider.getGroupMembersStream(realm, group).forEach(userModel -> removeMember(organization, userModel));
|
GroupModel group = getOrganizationGroup(entity);
|
||||||
groupProvider.removeGroup(realm, group);
|
|
||||||
organization.getIdentityProviders().forEach((model) -> removeIdentityProvider(organization, model));
|
if (group != null) {
|
||||||
|
//TODO: won't scale, requires a better mechanism for bulk deleting users
|
||||||
|
userProvider.getGroupMembersStream(realm, group).forEach(userModel -> removeMember(organization, userModel));
|
||||||
|
groupProvider.removeGroup(realm, group);
|
||||||
|
organization.getIdentityProviders().forEach((model) -> removeIdentityProvider(organization, model));
|
||||||
|
}
|
||||||
|
em.remove(entity);
|
||||||
|
} finally {
|
||||||
|
session.removeAttribute(OrganizationModel.class.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
em.remove(entity);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,18 +141,30 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
throwExceptionIfObjectIsNull(user, "User");
|
throwExceptionIfObjectIsNull(user, "User");
|
||||||
|
|
||||||
OrganizationEntity entity = getEntity(organization.getId());
|
OrganizationEntity entity = getEntity(organization.getId());
|
||||||
GroupModel group = getOrganizationGroup(entity);
|
OrganizationModel current = (OrganizationModel) session.getAttribute(OrganizationModel.class.getName());
|
||||||
|
|
||||||
if (user.isMemberOf(group)) {
|
if (current == null) {
|
||||||
return false;
|
session.setAttribute(OrganizationModel.class.getName(), organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.getFirstAttribute(ORGANIZATION_ATTRIBUTE) != null) {
|
try {
|
||||||
throw new ModelException("User [" + user.getId() + "] is a member of a different organization");
|
GroupModel group = getOrganizationGroup(entity);
|
||||||
}
|
|
||||||
|
|
||||||
user.joinGroup(group);
|
if (user.isMemberOf(group)) {
|
||||||
user.setSingleAttribute(ORGANIZATION_ATTRIBUTE, entity.getId());
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.getFirstAttribute(ORGANIZATION_ATTRIBUTE) != null) {
|
||||||
|
throw new ModelException("User [" + user.getId() + "] is a member of a different organization");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.joinGroup(group);
|
||||||
|
user.setSingleAttribute(ORGANIZATION_ATTRIBUTE, entity.getId());
|
||||||
|
} finally {
|
||||||
|
if (current == null) {
|
||||||
|
session.removeAttribute(OrganizationModel.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -333,10 +353,22 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
if (isManagedMember(organization, member)) {
|
if (isManagedMember(organization, member)) {
|
||||||
userProvider.removeUser(realm, member);
|
userProvider.removeUser(realm, member);
|
||||||
} else {
|
} else {
|
||||||
List<String> organizations = member.getAttributes().get(ORGANIZATION_ATTRIBUTE);
|
OrganizationModel current = (OrganizationModel) session.getAttribute(OrganizationModel.class.getName());
|
||||||
organizations.remove(organization.getId());
|
|
||||||
member.setAttribute(ORGANIZATION_ATTRIBUTE, organizations);
|
if (current == null) {
|
||||||
member.leaveGroup(getOrganizationGroup(organization));
|
session.setAttribute(OrganizationModel.class.getName(), organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<String> organizations = member.getAttributes().get(ORGANIZATION_ATTRIBUTE);
|
||||||
|
organizations.remove(organization.getId());
|
||||||
|
member.setAttribute(ORGANIZATION_ATTRIBUTE, organizations);
|
||||||
|
member.leaveGroup(getOrganizationGroup(organization));
|
||||||
|
} finally {
|
||||||
|
if (current == null) {
|
||||||
|
session.removeAttribute(OrganizationModel.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
package org.keycloak.organization.jpa;
|
package org.keycloak.organization.jpa;
|
||||||
|
|
||||||
import org.keycloak.Config.Scope;
|
import org.keycloak.Config.Scope;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.GroupModel.GroupEvent;
|
||||||
|
import org.keycloak.models.ModelValidationException;
|
||||||
import org.keycloak.organization.authentication.authenticators.broker.IdpOrganizationAuthenticatorFactory;
|
import org.keycloak.organization.authentication.authenticators.broker.IdpOrganizationAuthenticatorFactory;
|
||||||
import org.keycloak.organization.authentication.authenticators.browser.OrganizationAuthenticatorFactory;
|
import org.keycloak.organization.authentication.authenticators.browser.OrganizationAuthenticatorFactory;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
@ -29,6 +32,7 @@ import org.keycloak.models.RealmModel.RealmPostCreateEvent;
|
||||||
import org.keycloak.models.RealmModel.RealmRemovedEvent;
|
import org.keycloak.models.RealmModel.RealmRemovedEvent;
|
||||||
import org.keycloak.organization.OrganizationProvider;
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
import org.keycloak.organization.OrganizationProviderFactory;
|
import org.keycloak.organization.OrganizationProviderFactory;
|
||||||
|
import org.keycloak.organization.utils.Organizations;
|
||||||
import org.keycloak.provider.ProviderEvent;
|
import org.keycloak.provider.ProviderEvent;
|
||||||
|
|
||||||
public class JpaOrganizationProviderFactory implements OrganizationProviderFactory {
|
public class JpaOrganizationProviderFactory implements OrganizationProviderFactory {
|
||||||
|
@ -68,6 +72,14 @@ public class JpaOrganizationProviderFactory implements OrganizationProviderFacto
|
||||||
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
||||||
provider.removeAll();
|
provider.removeAll();
|
||||||
}
|
}
|
||||||
|
if (event instanceof GroupEvent) {
|
||||||
|
GroupEvent groupEvent = (GroupEvent) event;
|
||||||
|
KeycloakSession session = groupEvent.getKeycloakSession();
|
||||||
|
GroupModel group = groupEvent.getGroup();
|
||||||
|
if (!Organizations.canManageOrganizationGroup(session, group)) {
|
||||||
|
throw new ModelValidationException("Can not update organization group");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureAuthenticationFlows(RealmModel realm) {
|
private void configureAuthenticationFlows(RealmModel realm) {
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.jpa.JpaModel;
|
import org.keycloak.models.jpa.JpaModel;
|
||||||
import org.keycloak.models.jpa.entities.OrganizationDomainEntity;
|
import org.keycloak.models.jpa.entities.OrganizationDomainEntity;
|
||||||
import org.keycloak.models.jpa.entities.OrganizationEntity;
|
import org.keycloak.models.jpa.entities.OrganizationEntity;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.organization.OrganizationProvider;
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
import org.keycloak.utils.EmailValidationUtil;
|
import org.keycloak.utils.EmailValidationUtil;
|
||||||
import org.keycloak.utils.StringUtil;
|
import org.keycloak.utils.StringUtil;
|
||||||
|
@ -47,6 +48,14 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
|
||||||
private final OrganizationProvider provider;
|
private final OrganizationProvider provider;
|
||||||
private GroupModel group;
|
private GroupModel group;
|
||||||
|
|
||||||
|
public OrganizationAdapter(RealmModel realm, OrganizationProvider provider) {
|
||||||
|
entity = new OrganizationEntity();
|
||||||
|
entity.setId(KeycloakModelUtils.generateId());
|
||||||
|
entity.setRealmId(realm.getId());
|
||||||
|
this.realm = realm;
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
public OrganizationAdapter(RealmModel realm, OrganizationEntity entity, OrganizationProvider provider) {
|
public OrganizationAdapter(RealmModel realm, OrganizationEntity entity, OrganizationProvider provider) {
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
|
@ -66,6 +75,10 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
|
||||||
return entity.getGroupId();
|
return entity.getGroupId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setGroupId(String id) {
|
||||||
|
entity.setGroupId(id);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
entity.setName(name);
|
entity.setName(name);
|
||||||
|
|
|
@ -30,17 +30,132 @@ import java.util.stream.Stream;
|
||||||
*/
|
*/
|
||||||
public interface GroupModel extends RoleMapperModel {
|
public interface GroupModel extends RoleMapperModel {
|
||||||
|
|
||||||
interface GroupRemovedEvent extends ProviderEvent {
|
interface GroupEvent extends ProviderEvent {
|
||||||
RealmModel getRealm();
|
RealmModel getRealm();
|
||||||
GroupModel getGroup();
|
GroupModel getGroup();
|
||||||
KeycloakSession getKeycloakSession();
|
KeycloakSession getKeycloakSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GroupPathChangeEvent extends ProviderEvent {
|
interface GroupCreatedEvent extends GroupEvent {
|
||||||
RealmModel getRealm();
|
static void fire(GroupModel group, KeycloakSession session) {
|
||||||
|
session.getKeycloakSessionFactory().publish(new GroupCreatedEvent() {
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return session.getContext().getRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getKeycloakSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupRemovedEvent extends GroupEvent {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupUpdatedEvent extends GroupEvent {
|
||||||
|
static void fire(GroupModel group, KeycloakSession session) {
|
||||||
|
session.getKeycloakSessionFactory().publish(new GroupUpdatedEvent() {
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return session.getContext().getRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getKeycloakSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupMemberJoinEvent extends GroupEvent {
|
||||||
|
static void fire(GroupModel group, KeycloakSession session) {
|
||||||
|
session.getKeycloakSessionFactory().publish(new GroupMemberJoinEvent() {
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return session.getContext().getRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getKeycloakSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupMemberLeaveEvent extends GroupEvent {
|
||||||
|
static void fire(GroupModel group, KeycloakSession session) {
|
||||||
|
session.getKeycloakSessionFactory().publish(new GroupMemberLeaveEvent() {
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return session.getContext().getRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getKeycloakSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupPathChangeEvent extends GroupEvent {
|
||||||
String getNewPath();
|
String getNewPath();
|
||||||
String getPreviousPath();
|
String getPreviousPath();
|
||||||
KeycloakSession getKeycloakSession();
|
|
||||||
|
static void fire(GroupModel group, String newPath, String previousPath, KeycloakSession session) {
|
||||||
|
session.getKeycloakSessionFactory().publish(new GroupPathChangeEvent() {
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return session.getContext().getRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getKeycloakSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNewPath() {
|
||||||
|
return newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPreviousPath() {
|
||||||
|
return previousPath;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Comparator<GroupModel> COMPARE_BY_NAME = Comparator.comparing(GroupModel::getName);
|
Comparator<GroupModel> COMPARE_BY_NAME = Comparator.comparing(GroupModel::getName);
|
||||||
|
|
|
@ -164,9 +164,7 @@ public class OrganizationResource {
|
||||||
|
|
||||||
@Path("{id}/members")
|
@Path("{id}/members")
|
||||||
public OrganizationMemberResource members(@PathParam("id") String id) {
|
public OrganizationMemberResource members(@PathParam("id") String id) {
|
||||||
OrganizationModel organization = getOrganization(id);
|
return new OrganizationMemberResource(session, getOrganization(id), auth, adminEvent);
|
||||||
session.setAttribute(OrganizationModel.class.getName(), organization);
|
|
||||||
return new OrganizationMemberResource(session, organization, auth, adminEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{id}/identity-providers")
|
@Path("{id}/identity-providers")
|
||||||
|
@ -188,6 +186,8 @@ public class OrganizationResource {
|
||||||
throw ErrorResponse.error("Organization not found.", Response.Status.NOT_FOUND);
|
throw ErrorResponse.error("Organization not found.", Response.Status.NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.setAttribute(OrganizationModel.class.getName(), model);
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.keycloak.organization.forms.login.freemarker.model;
|
package org.keycloak.organization.forms.login.freemarker.model;
|
||||||
|
|
||||||
import org.keycloak.forms.login.freemarker.model.RealmBean;
|
import org.keycloak.forms.login.freemarker.model.RealmBean;
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.organization.utils;
|
||||||
|
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.common.Profile.Feature;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
|
import org.keycloak.utils.StringUtil;
|
||||||
|
|
||||||
|
public class Organizations {
|
||||||
|
|
||||||
|
public static boolean canManageOrganizationGroup(KeycloakSession session, GroupModel group) {
|
||||||
|
if (Profile.isFeatureEnabled(Feature.ORGANIZATION)) {
|
||||||
|
Object organization = session.getAttribute(OrganizationModel.class.getName());
|
||||||
|
|
||||||
|
if (organization != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String orgId = group.getFirstAttribute(OrganizationModel.ORGANIZATION_ATTRIBUTE);
|
||||||
|
|
||||||
|
return StringUtil.isBlank(orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.GroupModel.GroupPathChangeEvent;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -61,7 +62,6 @@ import java.util.Set;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.keycloak.utils.GroupUtils;
|
import org.keycloak.utils.GroupUtils;
|
||||||
|
|
||||||
import static org.keycloak.utils.OrganizationUtils.checkForOrgRelatedGroupRep;
|
|
||||||
import static org.keycloak.utils.StreamsUtil.paginatedStream;
|
import static org.keycloak.utils.StreamsUtil.paginatedStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,11 +118,14 @@ public class GroupResource {
|
||||||
this.auth.groups().requireManage(group);
|
this.auth.groups().requireManage(group);
|
||||||
|
|
||||||
String groupName = rep.getName();
|
String groupName = rep.getName();
|
||||||
|
|
||||||
if (ObjectUtil.isBlank(groupName)) {
|
if (ObjectUtil.isBlank(groupName)) {
|
||||||
throw ErrorResponse.error("Group name is missing", Response.Status.BAD_REQUEST);
|
throw ErrorResponse.error("Group name is missing", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForOrgRelatedGroupRep(session, rep);
|
if (rep.getId() != null && !group.getId().equals(rep.getId())) {
|
||||||
|
throw ErrorResponse.error("Invalid group id", Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
if (!Objects.equals(groupName, group.getName())) {
|
if (!Objects.equals(groupName, group.getName())) {
|
||||||
boolean exists = siblings().filter(s -> !Objects.equals(s.getId(), group.getId()))
|
boolean exists = siblings().filter(s -> !Objects.equals(s.getId(), group.getId()))
|
||||||
|
@ -197,8 +200,6 @@ public class GroupResource {
|
||||||
throw ErrorResponse.error("Group name is missing", Response.Status.BAD_REQUEST);
|
throw ErrorResponse.error("Group name is missing", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForOrgRelatedGroupRep(session, rep);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Response.ResponseBuilder builder = Response.status(204);
|
Response.ResponseBuilder builder = Response.status(204);
|
||||||
GroupModel child = null;
|
GroupModel child = null;
|
||||||
|
@ -246,29 +247,7 @@ public class GroupResource {
|
||||||
|
|
||||||
String newPath = KeycloakModelUtils.buildGroupPath(model);
|
String newPath = KeycloakModelUtils.buildGroupPath(model);
|
||||||
|
|
||||||
GroupModel.GroupPathChangeEvent event =
|
GroupPathChangeEvent.fire(model, newPath, previousPath, session);
|
||||||
new GroupModel.GroupPathChangeEvent() {
|
|
||||||
@Override
|
|
||||||
public RealmModel getRealm() {
|
|
||||||
return realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getNewPath() {
|
|
||||||
return newPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPreviousPath() {
|
|
||||||
return previousPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakSession getKeycloakSession() {
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
session.getKeycloakSessionFactory().publish(event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,8 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.core.Response.Status;
|
||||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||||
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
||||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||||
|
@ -44,6 +46,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
import org.keycloak.organization.utils.Organizations;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.resources.KeycloakOpenAPI;
|
import org.keycloak.services.resources.KeycloakOpenAPI;
|
||||||
|
@ -52,10 +55,6 @@ import org.keycloak.services.resources.admin.permissions.GroupPermissionEvaluato
|
||||||
import org.keycloak.utils.GroupUtils;
|
import org.keycloak.utils.GroupUtils;
|
||||||
import org.keycloak.utils.SearchQueryUtils;
|
import org.keycloak.utils.SearchQueryUtils;
|
||||||
|
|
||||||
import static org.keycloak.utils.OrganizationUtils.checkForOrgRelatedGroupModel;
|
|
||||||
import static org.keycloak.utils.OrganizationUtils.checkForOrgRelatedGroupRep;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @resource Groups
|
* @resource Groups
|
||||||
* @author Bill Burke
|
* @author Bill Burke
|
||||||
|
@ -124,11 +123,14 @@ public class GroupsResource {
|
||||||
@Path("{group-id}")
|
@Path("{group-id}")
|
||||||
public GroupResource getGroupById(@PathParam("group-id") String id) {
|
public GroupResource getGroupById(@PathParam("group-id") String id) {
|
||||||
GroupModel group = realm.getGroupById(id);
|
GroupModel group = realm.getGroupById(id);
|
||||||
|
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
throw new NotFoundException("Could not find group by id");
|
throw new NotFoundException("Could not find group by id");
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForOrgRelatedGroupModel(session, group);
|
if (!Organizations.canManageOrganizationGroup(session, group)) {
|
||||||
|
throw ErrorResponse.error("Cannot manage organization related group via non Organization API.", Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
return new GroupResource(realm, group, session, this.auth, adminEvent);
|
return new GroupResource(realm, group, session, this.auth, adminEvent);
|
||||||
}
|
}
|
||||||
|
@ -181,8 +183,6 @@ public class GroupsResource {
|
||||||
throw ErrorResponse.error("Group name is missing", Response.Status.BAD_REQUEST);
|
throw ErrorResponse.error("Group name is missing", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForOrgRelatedGroupRep(session, rep);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (rep.getId() != null) {
|
if (rep.getId() != null) {
|
||||||
child = realm.getGroupById(rep.getId());
|
child = realm.getGroupById(rep.getId());
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.keycloak.services.resources.admin;
|
package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
import static org.keycloak.util.JsonSerialization.readValue;
|
import static org.keycloak.util.JsonSerialization.readValue;
|
||||||
import static org.keycloak.utils.OrganizationUtils.checkForOrgRelatedGroupModel;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
@ -1056,8 +1055,6 @@ public class RealmAdminResource {
|
||||||
throw new NotFoundException("Group not found");
|
throw new NotFoundException("Group not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForOrgRelatedGroupModel(session, group);
|
|
||||||
|
|
||||||
realm.addDefaultGroup(group);
|
realm.addDefaultGroup(group);
|
||||||
|
|
||||||
adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP).resourcePath(session.getContext().getUri()).success();
|
adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP).resourcePath(session.getContext().getUri()).success();
|
||||||
|
@ -1076,8 +1073,6 @@ public class RealmAdminResource {
|
||||||
throw new NotFoundException("Group not found");
|
throw new NotFoundException("Group not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForOrgRelatedGroupModel(session, group);
|
|
||||||
|
|
||||||
realm.removeDefaultGroup(group);
|
realm.removeDefaultGroup(group);
|
||||||
|
|
||||||
adminEvent.operation(OperationType.DELETE).resource(ResourceType.GROUP).resourcePath(session.getContext().getUri()).success();
|
adminEvent.operation(OperationType.DELETE).resource(ResourceType.GROUP).resourcePath(session.getContext().getUri()).success();
|
||||||
|
|
|
@ -128,7 +128,6 @@ import java.util.stream.Stream;
|
||||||
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID;
|
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID;
|
||||||
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME;
|
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME;
|
||||||
import static org.keycloak.userprofile.UserProfileContext.USER_API;
|
import static org.keycloak.userprofile.UserProfileContext.USER_API;
|
||||||
import static org.keycloak.utils.OrganizationUtils.checkForOrgRelatedGroupModel;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base resource for managing users
|
* Base resource for managing users
|
||||||
|
@ -1018,8 +1017,6 @@ public class UserResource {
|
||||||
}
|
}
|
||||||
auth.groups().requireManageMembership(group);
|
auth.groups().requireManageMembership(group);
|
||||||
|
|
||||||
checkForOrgRelatedGroupModel(session, group);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (user.isMemberOf(group)){
|
if (user.isMemberOf(group)){
|
||||||
user.leaveGroup(group);
|
user.leaveGroup(group);
|
||||||
|
@ -1048,8 +1045,6 @@ public class UserResource {
|
||||||
}
|
}
|
||||||
auth.groups().requireManageMembership(group);
|
auth.groups().requireManageMembership(group);
|
||||||
|
|
||||||
checkForOrgRelatedGroupModel(session, group);
|
|
||||||
|
|
||||||
if (!RoleUtils.isDirectMember(user.getGroupsStream(),group)){
|
if (!RoleUtils.isDirectMember(user.getGroupsStream(),group)){
|
||||||
user.joinGroup(group);
|
user.joinGroup(group);
|
||||||
adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(session.getContext().getUri()).success();
|
adminEvent.operation(OperationType.CREATE).resource(ResourceType.GROUP_MEMBERSHIP).representation(ModelToRepresentation.toRepresentation(group, true)).resourcePath(session.getContext().getUri()).success();
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.utils;
|
|
||||||
|
|
||||||
import jakarta.ws.rs.core.Response;
|
|
||||||
import org.keycloak.models.GroupModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.OrganizationModel;
|
|
||||||
import org.keycloak.organization.OrganizationProvider;
|
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
|
||||||
import org.keycloak.services.ErrorResponse;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class OrganizationUtils {
|
|
||||||
|
|
||||||
public static void checkForOrgRelatedGroupRep(KeycloakSession session, GroupRepresentation rep) {
|
|
||||||
if (isOrgsEnabled(session)) {
|
|
||||||
checkRep(rep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void checkForOrgRelatedGroupModel(KeycloakSession session, GroupModel model) {
|
|
||||||
if (isOrgsEnabled(session)) {
|
|
||||||
checkModel(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isOrgsEnabled(KeycloakSession session) {
|
|
||||||
OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class);
|
|
||||||
return orgProvider != null && orgProvider.isEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isOrganizationRelatedGroup(Object o) {
|
|
||||||
if (o instanceof GroupRepresentation rep) {
|
|
||||||
return attributeContains(rep.getAttributes());
|
|
||||||
} else if (o instanceof GroupModel model) {
|
|
||||||
return attributeContains(model.getAttributes());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean attributeContains(Map<String, List<String>> attributes) {
|
|
||||||
return attributes != null && attributes.containsKey(OrganizationModel.ORGANIZATION_ATTRIBUTE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkModel(GroupModel model) {
|
|
||||||
if (isOrganizationRelatedGroup(model)) {
|
|
||||||
throw ErrorResponse.error("Cannot manage organization related group via non Organization API.", Response.Status.FORBIDDEN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkRep(GroupRepresentation rep) {
|
|
||||||
if (isOrganizationRelatedGroup(rep)) {
|
|
||||||
throw ErrorResponse.error("Cannot use group attribute reserved for organizations.", Response.Status.FORBIDDEN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.organization.admin;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.keycloak.testsuite.admin.group.GroupSearchTest.buildSearchQuery;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.BadRequestException;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import jakarta.ws.rs.core.Response.Status;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.resource.GroupResource;
|
||||||
|
import org.keycloak.common.Profile.Feature;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.ModelValidationException;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ManagementPermissionRepresentation;
|
||||||
|
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
|
import org.keycloak.testsuite.runonserver.RunOnServer;
|
||||||
|
|
||||||
|
@EnableFeature(Feature.ORGANIZATION)
|
||||||
|
public class OrganizationGroupTest extends AbstractOrganizationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testManageOrgGroupsViaDifferentAPIs() {
|
||||||
|
// test realm contains some groups initially
|
||||||
|
List<GroupRepresentation> getAllBefore = testRealm().groups().groups();
|
||||||
|
long countBefore = testRealm().groups().count().get("count");
|
||||||
|
|
||||||
|
List<String> orgIds = new ArrayList<>();
|
||||||
|
// create 5 organizations
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
OrganizationRepresentation expected = createOrganization("myorg" + i);
|
||||||
|
OrganizationRepresentation existing = testRealm().organizations().get(expected.getId()).toRepresentation();
|
||||||
|
orgIds.add(expected.getId());
|
||||||
|
assertNotNull(existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create one top-level group and one subgroup
|
||||||
|
GroupRepresentation topGroup = createGroup(testRealm(), "top");
|
||||||
|
GroupRepresentation level2Group = new GroupRepresentation();
|
||||||
|
level2Group.setName("level2");
|
||||||
|
testRealm().groups().group(topGroup.getId()).subGroup(level2Group);
|
||||||
|
|
||||||
|
// check that count queries include org related groups
|
||||||
|
assertEquals(countBefore + 7, (long) testRealm().groups().count().get("count"));
|
||||||
|
|
||||||
|
// check that search queries include org related groups but those can't be updated
|
||||||
|
assertEquals(getAllBefore.size() + 6, testRealm().groups().groups().size());
|
||||||
|
// we need to pull full representation of the group, otherwise org related attributes are lost in the representation
|
||||||
|
List<GroupRepresentation> groups = testRealm().groups().query(buildSearchQuery(OrganizationModel.ORGANIZATION_ATTRIBUTE, orgIds.get(0)), false, 0, 10, false);
|
||||||
|
assertEquals(1, groups.size());
|
||||||
|
GroupRepresentation orgGroupRep = groups.get(0);
|
||||||
|
GroupResource group = testRealm().groups().group(orgGroupRep.getId());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// group to be updated is organization related group
|
||||||
|
group.update(topGroup);
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success, the group could not be updated
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// cannot update a group with the attribute reserved for organization related groups
|
||||||
|
testRealm().groups().group(topGroup.getId()).update(orgGroupRep);
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success, the group could not be updated
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// cannot remove organization related group
|
||||||
|
group.remove();
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success, the group could not be removed
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// cannot manage organization related group permissions
|
||||||
|
group.setPermissions(new ManagementPermissionRepresentation(true));
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success, the group's permissions cannot be managed
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to add subgroup to an org related group
|
||||||
|
try (Response response = group.subGroup(topGroup)) {
|
||||||
|
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to add org related group as a subgroup to a group
|
||||||
|
try (Response response = testRealm().groups().group(topGroup.getId()).subGroup(orgGroupRep)) {
|
||||||
|
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// cannot manage organization related group role mappers
|
||||||
|
group.roles().realmLevel().add(null);
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// cannot manage organization related group role mappers
|
||||||
|
group.roles().realmLevel().remove(null);
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// cannot manage organization related group role mappers
|
||||||
|
group.roles().clientLevel(testRealm().clients().findByClientId("test-app").get(0).getId()).add(null);
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// cannot manage organization related group role mappers
|
||||||
|
group.roles().clientLevel(testRealm().clients().findByClientId("test-app").get(0).getId()).remove(null);
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
// cannot add top level group with reserved attribute for organizations
|
||||||
|
String id = orgGroupRep.getId();
|
||||||
|
String name = orgGroupRep.getName();
|
||||||
|
orgGroupRep.setId(null);
|
||||||
|
orgGroupRep.setName(null);
|
||||||
|
try (Response response = testRealm().groups().add(orgGroupRep)) {
|
||||||
|
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||||
|
} finally {
|
||||||
|
orgGroupRep.setId(id);
|
||||||
|
orgGroupRep.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// cannot add organization related group as a default group
|
||||||
|
testRealm().addDefaultGroup(orgGroupRep.getId());
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
OrganizationRepresentation org = createOrganization();
|
||||||
|
UserRepresentation userRep = addMember(testRealm().organizations().get(org.getId()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// cannot join organization related group
|
||||||
|
testRealm().users().get(userRep.getId()).joinGroup(orgGroupRep.getId());
|
||||||
|
fail("Expected BadRequestException");
|
||||||
|
} catch (BadRequestException ex) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testManagingOrganizationGroupNotInOrganizationScope() {
|
||||||
|
String id = createOrganization().getId();
|
||||||
|
String memberId = addMember(testRealm().organizations().get(id)).getId();
|
||||||
|
|
||||||
|
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) session -> {
|
||||||
|
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
||||||
|
OrganizationModel organization = provider.getById(id);
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
GroupModel orgGroup = session.groups().getGroupByName(realm, null, organization.getId());
|
||||||
|
|
||||||
|
try {
|
||||||
|
orgGroup.setName("fail");
|
||||||
|
fail("can not manage");
|
||||||
|
} catch (ModelValidationException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
orgGroup.setSingleAttribute(OrganizationModel.ORGANIZATION_ATTRIBUTE, "fail");
|
||||||
|
fail("can not manage");
|
||||||
|
} catch (ModelValidationException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupModel child = realm.createGroup("child");
|
||||||
|
|
||||||
|
try {
|
||||||
|
orgGroup.addChild(child);
|
||||||
|
fail("can not manage");
|
||||||
|
} catch (ModelValidationException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupModel parent = realm.createGroup("parent");
|
||||||
|
|
||||||
|
try {
|
||||||
|
realm.moveGroup(orgGroup, parent);
|
||||||
|
fail("can not manage");
|
||||||
|
} catch (ModelValidationException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
realm.removeGroup(orgGroup);
|
||||||
|
fail("can not manage");
|
||||||
|
} catch (ModelValidationException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
realm.addDefaultGroup(orgGroup);
|
||||||
|
fail("can not manage");
|
||||||
|
} catch (ModelValidationException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
UserModel user = session.users().getUserByUsername(realm, "john-doh@localhost");
|
||||||
|
assertNotNull(user);
|
||||||
|
try {
|
||||||
|
user.joinGroup(orgGroup);
|
||||||
|
fail("can not manage");
|
||||||
|
} catch (ModelValidationException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
UserModel member = session.users().getUserById(realm, memberId);
|
||||||
|
assertNotNull(user);
|
||||||
|
try {
|
||||||
|
member.leaveGroup(orgGroup);
|
||||||
|
fail("can not manage");
|
||||||
|
} catch (ModelValidationException ignore) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,6 @@ import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.keycloak.testsuite.admin.group.GroupSearchTest.buildSearchQuery;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
@ -39,20 +38,14 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.ws.rs.ForbiddenException;
|
|
||||||
import jakarta.ws.rs.NotFoundException;
|
import jakarta.ws.rs.NotFoundException;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.Response.Status;
|
import jakarta.ws.rs.core.Response.Status;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.GroupResource;
|
|
||||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||||
import org.keycloak.common.Profile.Feature;
|
import org.keycloak.common.Profile.Feature;
|
||||||
import org.keycloak.models.OrganizationModel;
|
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
|
||||||
import org.keycloak.representations.idm.ManagementPermissionRepresentation;
|
|
||||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
||||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
|
|
||||||
@EnableFeature(Feature.ORGANIZATION)
|
@EnableFeature(Feature.ORGANIZATION)
|
||||||
|
@ -374,151 +367,4 @@ public class OrganizationTest extends AbstractOrganizationTest {
|
||||||
assertEquals(1, existing.getDomains().size());
|
assertEquals(1, existing.getDomains().size());
|
||||||
assertNotNull(existing.getDomain("acme.com"));
|
assertNotNull(existing.getDomain("acme.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testManageOrgGroupsViaDifferentAPIs() {
|
|
||||||
// test realm contains some groups initially
|
|
||||||
List<GroupRepresentation> getAllBefore = testRealm().groups().groups();
|
|
||||||
long countBefore = testRealm().groups().count().get("count");
|
|
||||||
|
|
||||||
List<String> orgIds = new ArrayList<>();
|
|
||||||
// create 5 organizations
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
OrganizationRepresentation expected = createOrganization("myorg" + i);
|
|
||||||
OrganizationRepresentation existing = testRealm().organizations().get(expected.getId()).toRepresentation();
|
|
||||||
orgIds.add(expected.getId());
|
|
||||||
assertNotNull(existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
// create one top-level group and one subgroup
|
|
||||||
GroupRepresentation topGroup = createGroup(testRealm(), "top");
|
|
||||||
GroupRepresentation level2Group = new GroupRepresentation();
|
|
||||||
level2Group.setName("level2");
|
|
||||||
testRealm().groups().group(topGroup.getId()).subGroup(level2Group);
|
|
||||||
|
|
||||||
// check that count queries include org related groups
|
|
||||||
assertEquals(countBefore + 7, (long) testRealm().groups().count().get("count"));
|
|
||||||
|
|
||||||
// check that search queries include org related groups but those can't be updated
|
|
||||||
assertEquals(getAllBefore.size() + 6, testRealm().groups().groups().size());
|
|
||||||
// we need to pull full representation of the group, otherwise org related attributes are lost in the representation
|
|
||||||
List<GroupRepresentation> groups = testRealm().groups().query(buildSearchQuery(OrganizationModel.ORGANIZATION_ATTRIBUTE, orgIds.get(0)), false, 0, 10, false);
|
|
||||||
assertEquals(1, groups.size());
|
|
||||||
GroupRepresentation orgGroupRep = groups.get(0);
|
|
||||||
GroupResource group = testRealm().groups().group(orgGroupRep.getId());
|
|
||||||
|
|
||||||
try {
|
|
||||||
// group to be updated is organization related group
|
|
||||||
group.update(topGroup);
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success, the group could not be updated
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot update a group with the attribute reserved for organization related groups
|
|
||||||
testRealm().groups().group(topGroup.getId()).update(orgGroupRep);
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success, the group could not be updated
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot remove organization related group
|
|
||||||
group.remove();
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success, the group could not be removed
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot manage organization related group permissions
|
|
||||||
group.setPermissions(new ManagementPermissionRepresentation(true));
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success, the group's permissions cannot be managed
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to add subgroup to an org related group
|
|
||||||
try (Response response = group.subGroup(topGroup)) {
|
|
||||||
assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to add org related group as a subgroup to a group
|
|
||||||
try (Response response = testRealm().groups().group(topGroup.getId()).subGroup(orgGroupRep)) {
|
|
||||||
assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot manage organization related group role mappers
|
|
||||||
group.roles().realmLevel().add(null);
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot manage organization related group role mappers
|
|
||||||
group.roles().realmLevel().remove(null);
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot manage organization related group role mappers
|
|
||||||
group.roles().clientLevel(testRealm().clients().findByClientId("test-app").get(0).getId()).add(null);
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot manage organization related group role mappers
|
|
||||||
group.roles().clientLevel(testRealm().clients().findByClientId("test-app").get(0).getId()).remove(null);
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success
|
|
||||||
}
|
|
||||||
|
|
||||||
// cannot add top level group with reserved attribute for organizations
|
|
||||||
try (Response response = testRealm().groups().add(orgGroupRep)) {
|
|
||||||
assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot add organization related group as a default group
|
|
||||||
testRealm().addDefaultGroup(orgGroupRep.getId());
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot remove organization related group as a default group
|
|
||||||
testRealm().removeDefaultGroup(orgGroupRep.getId());
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success
|
|
||||||
}
|
|
||||||
|
|
||||||
OrganizationRepresentation org = createOrganization();
|
|
||||||
UserRepresentation userRep = addMember(testRealm().organizations().get(org.getId()));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot join organization related group
|
|
||||||
testRealm().users().get(userRep.getId()).joinGroup(orgGroupRep.getId());
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// cannot leave organization related group
|
|
||||||
testRealm().users().get(userRep.getId()).leaveGroup(orgGroupRep.getId());
|
|
||||||
fail("Expected ForbiddenException");
|
|
||||||
} catch (ForbiddenException ex) {
|
|
||||||
// success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue