feat: Introducing Organization Administrator Role (#39549)
## Description > [!TIP] > _Add a TL;DR when the description is longer than 500 words or extremely technical (helps the content, marketing, and DevRel team)._ > > _Please also include relevant motivation and context. List any dependencies that are required for this change. Add links to Notion, Figma or any other documents that might be relevant to the PR._ Fixes #`Issue Number` _or_ Fixes `Issue URL` > [!WARNING] > _If no issue exists, please create an issue first, and check with the maintainers if the issue is valid._ ## Automation /test sanity ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/13653636383> > Commit: 60c77b1803fb9bf870a62e4475bb59ad2a471fb6 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13653636383&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Sanity` > Spec: > <hr>Tue, 04 Mar 2025 12:58:58 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced an organization-based admin role and enhanced permission management for organization administrators. - Added functionality to assign and remove instance administrator privileges, streamlining role transitions. - **Refactor** - Updated user role terminology and logic across services to replace legacy tenant and super user assignments with the new organization-focused model. - Revised migration and repository processes to support organization-specific permission groups. - **Tests** - Improved test coverage to verify accurate assignment and removal of the updated administrative roles. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
7a59d3d43a
commit
605ad47c51
|
|
@ -25,6 +25,7 @@ import static com.appsmith.server.constants.FieldName.VIEWER;
|
|||
import static com.appsmith.server.constants.FieldName.WORKSPACE_ADMINISTRATOR_DESCRIPTION;
|
||||
import static com.appsmith.server.constants.FieldName.WORKSPACE_DEVELOPER_DESCRIPTION;
|
||||
import static com.appsmith.server.constants.FieldName.WORKSPACE_VIEWER_DESCRIPTION;
|
||||
import static com.appsmith.server.constants.ce.FieldNameCE.ORGANIZATION_ADMINISTRATOR_ROLE;
|
||||
|
||||
@Getter
|
||||
public enum AppsmithRole {
|
||||
|
|
@ -62,7 +63,7 @@ public enum AppsmithRole {
|
|||
WORKSPACE_READ_APPLICATIONS,
|
||||
WORKSPACE_INVITE_USERS,
|
||||
WORKSPACE_EXECUTE_DATASOURCES)),
|
||||
TENANT_ADMIN("", "", Set.of(MANAGE_ORGANIZATION)),
|
||||
ORGANIZATION_ADMIN(ORGANIZATION_ADMINISTRATOR_ROLE, "", Set.of(MANAGE_ORGANIZATION)),
|
||||
;
|
||||
|
||||
private Set<AclPermission> permissions;
|
||||
|
|
|
|||
|
|
@ -207,4 +207,5 @@ public class FieldNameCE {
|
|||
public static final String BODY = "body";
|
||||
public static final String ORGANIZATION_ID = "organizationId";
|
||||
public static final String NONE = "none";
|
||||
public static final String ORGANIZATION_ADMINISTRATOR_ROLE = "Organization Administrator Role";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.appsmith.server.helpers;
|
|||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
|
|
@ -12,6 +14,8 @@ public class InMemoryCacheableRepositoryHelper {
|
|||
|
||||
private String instanceAdminPermissionGroupId = null;
|
||||
|
||||
private Map<String, String> inMemoryOrganizationIdOrganizationPermissionGroupIdMap = new HashMap<>();
|
||||
|
||||
public Set<String> getAnonymousUserPermissionGroupIds() {
|
||||
return anonymousUserPermissionGroupIds;
|
||||
}
|
||||
|
|
@ -35,4 +39,12 @@ public class InMemoryCacheableRepositoryHelper {
|
|||
public String getInstanceAdminPermissionGroupId() {
|
||||
return instanceAdminPermissionGroupId;
|
||||
}
|
||||
|
||||
public String getOrganizationAdminPermissionGroupId(String organizationId) {
|
||||
return this.inMemoryOrganizationIdOrganizationPermissionGroupIdMap.get(organizationId);
|
||||
}
|
||||
|
||||
public void setOrganizationAdminPermissionGroupId(String organizationId, String permissionGroupId) {
|
||||
this.inMemoryOrganizationIdOrganizationPermissionGroupIdMap.put(organizationId, permissionGroupId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,35 @@
|
|||
package com.appsmith.server.helpers;
|
||||
|
||||
import com.appsmith.server.configurations.CommonConfig;
|
||||
import com.appsmith.server.helpers.ce.UserUtilsCE;
|
||||
import com.appsmith.server.repositories.CacheableRepositoryHelper;
|
||||
import com.appsmith.server.repositories.ConfigRepository;
|
||||
import com.appsmith.server.repositories.OrganizationRepository;
|
||||
import com.appsmith.server.repositories.PermissionGroupRepository;
|
||||
import com.appsmith.server.solutions.PermissionGroupPermission;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserUtils extends UserUtilsCE {
|
||||
|
||||
public UserUtils(
|
||||
ConfigRepository configRepository,
|
||||
PermissionGroupRepository permissionGroupRepository,
|
||||
CacheableRepositoryHelper cacheableRepositoryHelper,
|
||||
PermissionGroupPermission permissionGroupPermission,
|
||||
ObservationRegistry observationRegistry) {
|
||||
|
||||
ObservationRegistry observationRegistry,
|
||||
CommonConfig commonConfig,
|
||||
InMemoryCacheableRepositoryHelper inMemoryCacheableRepositoryHelper,
|
||||
OrganizationRepository organizationRepository,
|
||||
SessionUserService sessionUserService) {
|
||||
super(
|
||||
configRepository,
|
||||
permissionGroupRepository,
|
||||
cacheableRepositoryHelper,
|
||||
permissionGroupPermission,
|
||||
observationRegistry);
|
||||
observationRegistry,
|
||||
commonConfig,
|
||||
inMemoryCacheableRepositoryHelper,
|
||||
organizationRepository,
|
||||
sessionUserService);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,33 @@
|
|||
package com.appsmith.server.helpers.ce;
|
||||
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Config;
|
||||
import com.appsmith.server.configurations.CommonConfig;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.PermissionGroup;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.Permission;
|
||||
import com.appsmith.server.helpers.InMemoryCacheableRepositoryHelper;
|
||||
import com.appsmith.server.helpers.ce.bridge.Bridge;
|
||||
import com.appsmith.server.helpers.ce.bridge.BridgeUpdate;
|
||||
import com.appsmith.server.repositories.CacheableRepositoryHelper;
|
||||
import com.appsmith.server.repositories.ConfigRepository;
|
||||
import com.appsmith.server.repositories.OrganizationRepository;
|
||||
import com.appsmith.server.repositories.PermissionGroupRepository;
|
||||
import com.appsmith.server.solutions.PermissionGroupPermission;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import net.minidev.json.JSONObject;
|
||||
import reactor.core.observability.micrometer.Micrometer;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.external.constants.spans.UserSpan.CHECK_SUPER_USER_SPAN;
|
||||
import static com.appsmith.server.acl.AclPermission.ASSIGN_PERMISSION_GROUPS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_INSTANCE_CONFIGURATION;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_INSTANCE_CONFIGURATION;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_PERMISSION_GROUP_MEMBERS;
|
||||
import static com.appsmith.server.acl.AclPermission.UNASSIGN_PERMISSION_GROUPS;
|
||||
import static com.appsmith.server.constants.FieldName.DEFAULT_PERMISSION_GROUP;
|
||||
import static com.appsmith.server.constants.FieldName.INSTANCE_CONFIG;
|
||||
import static org.springframework.util.StringUtils.hasLength;
|
||||
|
||||
public class UserUtilsCE {
|
||||
|
||||
|
|
@ -41,71 +35,91 @@ public class UserUtilsCE {
|
|||
|
||||
private final PermissionGroupRepository permissionGroupRepository;
|
||||
|
||||
private final PermissionGroupPermission permissionGroupPermission;
|
||||
private final ObservationRegistry observationRegistry;
|
||||
private final CacheableRepositoryHelper cacheableRepositoryHelper;
|
||||
private final CommonConfig commonConfig;
|
||||
private final InMemoryCacheableRepositoryHelper inMemoryCacheableRepositoryHelper;
|
||||
private final OrganizationRepository organizationRepository;
|
||||
private final SessionUserService sessionUserService;
|
||||
|
||||
public UserUtilsCE(
|
||||
ConfigRepository configRepository,
|
||||
PermissionGroupRepository permissionGroupRepository,
|
||||
CacheableRepositoryHelper cacheableRepositoryHelper,
|
||||
PermissionGroupPermission permissionGroupPermission,
|
||||
ObservationRegistry observationRegistry) {
|
||||
ObservationRegistry observationRegistry,
|
||||
CommonConfig commonConfig,
|
||||
InMemoryCacheableRepositoryHelper inMemoryCacheableRepositoryHelper,
|
||||
OrganizationRepository organizationRepository,
|
||||
SessionUserService sessionUserService) {
|
||||
this.configRepository = configRepository;
|
||||
this.permissionGroupRepository = permissionGroupRepository;
|
||||
this.permissionGroupPermission = permissionGroupPermission;
|
||||
this.observationRegistry = observationRegistry;
|
||||
this.cacheableRepositoryHelper = cacheableRepositoryHelper;
|
||||
this.commonConfig = commonConfig;
|
||||
this.inMemoryCacheableRepositoryHelper = inMemoryCacheableRepositoryHelper;
|
||||
this.organizationRepository = organizationRepository;
|
||||
this.sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public Mono<Boolean> isSuperUser(User user) {
|
||||
return configRepository
|
||||
.findByNameAsUser(INSTANCE_CONFIG, user, AclPermission.MANAGE_INSTANCE_CONFIGURATION)
|
||||
.map(config -> Boolean.TRUE)
|
||||
|
||||
return organizationRepository
|
||||
.findByIdAsUser(user, user.getOrganizationId(), AclPermission.MANAGE_ORGANIZATION)
|
||||
.map(organization -> Boolean.TRUE)
|
||||
.switchIfEmpty(Mono.just(Boolean.FALSE))
|
||||
.name(CHECK_SUPER_USER_SPAN)
|
||||
.tap(Micrometer.observation(observationRegistry));
|
||||
}
|
||||
|
||||
public Mono<Boolean> isCurrentUserSuperUser() {
|
||||
return configRepository
|
||||
.findByName(INSTANCE_CONFIG, AclPermission.MANAGE_INSTANCE_CONFIGURATION)
|
||||
.map(config -> Boolean.TRUE)
|
||||
.switchIfEmpty(Mono.just(Boolean.FALSE));
|
||||
return sessionUserService.getCurrentUser().flatMap(this::isSuperUser);
|
||||
}
|
||||
|
||||
public Mono<Boolean> makeSuperUser(List<User> users) {
|
||||
return getSuperAdminPermissionGroup()
|
||||
.flatMap(permissionGroup -> {
|
||||
public Mono<Boolean> makeInstanceAdministrator(List<User> users) {
|
||||
|
||||
// TODO : Replace cloud hosting check with a feature flag check for multi tenancy
|
||||
boolean cloudHosting = commonConfig.isCloudHosting();
|
||||
Mono<PermissionGroup> organizationAdminPgMono = Mono.just(new PermissionGroup());
|
||||
|
||||
if (!cloudHosting) {
|
||||
organizationAdminPgMono = getDefaultOrganizationAdminPermissionGroup();
|
||||
}
|
||||
|
||||
return Mono.zip(getInstanceAdminPermissionGroup(), organizationAdminPgMono)
|
||||
.flatMap(tuple -> {
|
||||
PermissionGroup instanceAdminPg = tuple.getT1();
|
||||
PermissionGroup organizationAdminPg = tuple.getT2();
|
||||
|
||||
Set<String> assignedToUserIds = new HashSet<>();
|
||||
|
||||
if (permissionGroup.getAssignedToUserIds() != null) {
|
||||
assignedToUserIds.addAll(permissionGroup.getAssignedToUserIds());
|
||||
if (instanceAdminPg.getAssignedToUserIds() != null) {
|
||||
assignedToUserIds.addAll(instanceAdminPg.getAssignedToUserIds());
|
||||
}
|
||||
assignedToUserIds.addAll(users.stream().map(User::getId).collect(Collectors.toList()));
|
||||
BridgeUpdate updateObj = Bridge.update();
|
||||
String path = PermissionGroup.Fields.assignedToUserIds;
|
||||
|
||||
updateObj.set(path, assignedToUserIds);
|
||||
// Make Super User is called before the first administrator is created.
|
||||
return permissionGroupRepository.updateById(permissionGroup.getId(), updateObj);
|
||||
})
|
||||
.then(Mono.just(users))
|
||||
.flatMapMany(Flux::fromIterable)
|
||||
.flatMap(user -> permissionGroupRepository.evictAllPermissionGroupCachesForUser(
|
||||
user.getEmail(), user.getOrganizationId()))
|
||||
.then(Mono.just(Boolean.TRUE));
|
||||
}
|
||||
// Make Instance Admin is called before the first administrator is created.
|
||||
Mono<Integer> updateInstanceAdminPgAssignmentMono =
|
||||
permissionGroupRepository.updateById(instanceAdminPg.getId(), updateObj);
|
||||
|
||||
public Mono<Boolean> removeSuperUser(List<User> users) {
|
||||
return getSuperAdminPermissionGroup()
|
||||
.flatMap(permissionGroup -> {
|
||||
if (permissionGroup.getAssignedToUserIds() == null) {
|
||||
permissionGroup.setAssignedToUserIds(new HashSet<>());
|
||||
if (!hasLength(organizationAdminPg.getId())) {
|
||||
return updateInstanceAdminPgAssignmentMono;
|
||||
}
|
||||
permissionGroup
|
||||
.getAssignedToUserIds()
|
||||
.removeAll(users.stream().map(User::getId).collect(Collectors.toList()));
|
||||
return permissionGroupRepository.updateById(
|
||||
permissionGroup.getId(), permissionGroup, permissionGroupPermission.getAssignPermission());
|
||||
|
||||
// Also assign the users to the organization admin permission group
|
||||
Set<String> organizationAdminAssignedToUserIds = new HashSet<>();
|
||||
if (organizationAdminPg.getAssignedToUserIds() != null) {
|
||||
organizationAdminAssignedToUserIds.addAll(organizationAdminPg.getAssignedToUserIds());
|
||||
}
|
||||
organizationAdminAssignedToUserIds.addAll(
|
||||
users.stream().map(User::getId).collect(Collectors.toList()));
|
||||
BridgeUpdate updateObj2 = Bridge.update();
|
||||
String path2 = PermissionGroup.Fields.assignedToUserIds;
|
||||
updateObj2.set(path2, organizationAdminAssignedToUserIds);
|
||||
return updateInstanceAdminPgAssignmentMono.then(
|
||||
permissionGroupRepository.updateById(organizationAdminPg.getId(), updateObj2));
|
||||
})
|
||||
.then(Mono.just(users))
|
||||
.flatMapMany(Flux::fromIterable)
|
||||
|
|
@ -114,102 +128,88 @@ public class UserUtilsCE {
|
|||
.then(Mono.just(Boolean.TRUE));
|
||||
}
|
||||
|
||||
protected Mono<Config> createInstanceConfigForSuperUser() {
|
||||
public Mono<Boolean> removeInstanceAdmin(List<User> users) {
|
||||
|
||||
Mono<Tuple2<PermissionGroup, Config>> savedConfigAndPermissionGroupMono =
|
||||
createConfigAndPermissionGroupForSuperAdmin();
|
||||
// TODO : Replace cloud hosting check with a feature flag check for multi tenancy
|
||||
boolean cloudHosting = commonConfig.isCloudHosting();
|
||||
Mono<PermissionGroup> organizationAdminPgMono = Mono.just(new PermissionGroup());
|
||||
|
||||
// return the saved instance config
|
||||
return savedConfigAndPermissionGroupMono.map(Tuple2::getT2);
|
||||
}
|
||||
if (!cloudHosting) {
|
||||
organizationAdminPgMono = getDefaultOrganizationAdminPermissionGroup();
|
||||
}
|
||||
|
||||
protected Mono<Tuple2<PermissionGroup, Config>> createConfigAndPermissionGroupForSuperAdmin() {
|
||||
return Mono.zip(createInstanceAdminConfigObject(), createInstanceAdminPermissionGroupWithoutPermissions())
|
||||
return Mono.zip(getInstanceAdminPermissionGroup(), organizationAdminPgMono)
|
||||
.flatMap(tuple -> {
|
||||
Config savedInstanceConfig = tuple.getT1();
|
||||
PermissionGroup savedPermissionGroup = tuple.getT2();
|
||||
PermissionGroup instanceAdminPg = tuple.getT1();
|
||||
PermissionGroup organizationAdminPg = tuple.getT2();
|
||||
|
||||
// Update the instance config with the permission group id
|
||||
savedInstanceConfig.setConfig(
|
||||
new JSONObject(Map.of(DEFAULT_PERMISSION_GROUP, savedPermissionGroup.getId())));
|
||||
if (instanceAdminPg.getAssignedToUserIds() == null) {
|
||||
instanceAdminPg.setAssignedToUserIds(new HashSet<>());
|
||||
}
|
||||
Set<String> assignedToUserIds = new HashSet<>(instanceAdminPg.getAssignedToUserIds());
|
||||
assignedToUserIds.removeAll(users.stream().map(User::getId).collect(Collectors.toList()));
|
||||
|
||||
Policy editConfigPolicy = Policy.builder()
|
||||
.permission(MANAGE_INSTANCE_CONFIGURATION.getValue())
|
||||
.permissionGroups(Set.of(savedPermissionGroup.getId()))
|
||||
.build();
|
||||
Policy readConfigPolicy = Policy.builder()
|
||||
.permission(READ_INSTANCE_CONFIGURATION.getValue())
|
||||
.permissionGroups(Set.of(savedPermissionGroup.getId()))
|
||||
.build();
|
||||
BridgeUpdate updateObj = Bridge.update();
|
||||
String path = PermissionGroup.Fields.assignedToUserIds;
|
||||
|
||||
savedInstanceConfig.setPolicies(Set.of(editConfigPolicy, readConfigPolicy));
|
||||
updateObj.set(path, assignedToUserIds);
|
||||
// Make Instance Admin is called before the first administrator is created.
|
||||
Mono<Integer> updateInstanceAdminPgAssignmentMono =
|
||||
permissionGroupRepository.updateById(instanceAdminPg.getId(), updateObj);
|
||||
|
||||
// Add config permissions to permission group
|
||||
Set<Permission> configPermissions =
|
||||
Set.of(new Permission(savedInstanceConfig.getId(), MANAGE_INSTANCE_CONFIGURATION));
|
||||
if (!hasLength(organizationAdminPg.getId())) {
|
||||
return updateInstanceAdminPgAssignmentMono;
|
||||
}
|
||||
|
||||
return Mono.zip(
|
||||
addPermissionsToPermissionGroup(savedPermissionGroup, configPermissions),
|
||||
configRepository.save(savedInstanceConfig));
|
||||
});
|
||||
// Also unassign the users from the organization admin permission group
|
||||
Set<String> organizationAdminAssignedToUserIds = new HashSet<>();
|
||||
if (organizationAdminPg.getAssignedToUserIds() != null) {
|
||||
organizationAdminAssignedToUserIds.addAll(organizationAdminPg.getAssignedToUserIds());
|
||||
}
|
||||
organizationAdminAssignedToUserIds.removeAll(
|
||||
users.stream().map(User::getId).collect(Collectors.toList()));
|
||||
BridgeUpdate updateObj2 = Bridge.update();
|
||||
String path2 = PermissionGroup.Fields.assignedToUserIds;
|
||||
updateObj2.set(path2, organizationAdminAssignedToUserIds);
|
||||
return updateInstanceAdminPgAssignmentMono.then(
|
||||
permissionGroupRepository.updateById(organizationAdminPg.getId(), updateObj2));
|
||||
})
|
||||
.then(Mono.just(users))
|
||||
.flatMapMany(Flux::fromIterable)
|
||||
.flatMap(user -> permissionGroupRepository.evictAllPermissionGroupCachesForUser(
|
||||
user.getEmail(), user.getOrganizationId()))
|
||||
.then(Mono.just(Boolean.TRUE));
|
||||
}
|
||||
|
||||
private Mono<Config> createInstanceAdminConfigObject() {
|
||||
Config instanceAdminConfiguration = new Config();
|
||||
instanceAdminConfiguration.setName(FieldName.INSTANCE_CONFIG);
|
||||
public Mono<PermissionGroup> getInstanceAdminPermissionGroup() {
|
||||
|
||||
return configRepository.save(instanceAdminConfiguration);
|
||||
}
|
||||
String instanceAdminPermissionGroupId = inMemoryCacheableRepositoryHelper.getInstanceAdminPermissionGroupId();
|
||||
if (hasLength(instanceAdminPermissionGroupId)) {
|
||||
return permissionGroupRepository.findById(instanceAdminPermissionGroupId);
|
||||
}
|
||||
|
||||
private Mono<PermissionGroup> createInstanceAdminPermissionGroupWithoutPermissions() {
|
||||
PermissionGroup instanceAdminPermissionGroup = new PermissionGroup();
|
||||
instanceAdminPermissionGroup.setName(FieldName.INSTANCE_ADMIN_ROLE);
|
||||
|
||||
return permissionGroupRepository.save(instanceAdminPermissionGroup).flatMap(savedPermissionGroup -> {
|
||||
Set<Permission> permissions = Set.of(
|
||||
new Permission(savedPermissionGroup.getId(), READ_PERMISSION_GROUP_MEMBERS),
|
||||
new Permission(savedPermissionGroup.getId(), ASSIGN_PERMISSION_GROUPS),
|
||||
new Permission(savedPermissionGroup.getId(), UNASSIGN_PERMISSION_GROUPS));
|
||||
savedPermissionGroup.setPermissions(permissions);
|
||||
|
||||
Policy readPermissionGroupPolicy = Policy.builder()
|
||||
.permission(READ_PERMISSION_GROUP_MEMBERS.getValue())
|
||||
.permissionGroups(Set.of(savedPermissionGroup.getId()))
|
||||
.build();
|
||||
|
||||
Policy assignPermissionGroupPolicy = Policy.builder()
|
||||
.permission(ASSIGN_PERMISSION_GROUPS.getValue())
|
||||
.permissionGroups(Set.of(savedPermissionGroup.getId()))
|
||||
.build();
|
||||
|
||||
Policy unassignPermissionGroupPolicy = Policy.builder()
|
||||
.permission(UNASSIGN_PERMISSION_GROUPS.getValue())
|
||||
.permissionGroups(Set.of(savedPermissionGroup.getId()))
|
||||
.build();
|
||||
|
||||
savedPermissionGroup.setPolicies(
|
||||
Set.of(readPermissionGroupPolicy, assignPermissionGroupPolicy, unassignPermissionGroupPolicy));
|
||||
|
||||
return permissionGroupRepository.save(savedPermissionGroup);
|
||||
return configRepository.findByName(INSTANCE_CONFIG).flatMap(instanceConfig -> {
|
||||
JSONObject config = instanceConfig.getConfig();
|
||||
String defaultPermissionGroup = (String) config.getOrDefault(DEFAULT_PERMISSION_GROUP, "");
|
||||
return permissionGroupRepository
|
||||
.findById(defaultPermissionGroup)
|
||||
.doOnSuccess(permissionGroup -> inMemoryCacheableRepositoryHelper.setInstanceAdminPermissionGroupId(
|
||||
permissionGroup.getId()));
|
||||
});
|
||||
}
|
||||
|
||||
protected Mono<PermissionGroup> addPermissionsToPermissionGroup(
|
||||
PermissionGroup permissionGroup, Set<Permission> permissions) {
|
||||
Set<Permission> existingPermissions = new HashSet<>(permissionGroup.getPermissions());
|
||||
existingPermissions.addAll(permissions);
|
||||
permissionGroup.setPermissions(existingPermissions);
|
||||
return permissionGroupRepository.save(permissionGroup);
|
||||
}
|
||||
|
||||
public Mono<PermissionGroup> getSuperAdminPermissionGroup() {
|
||||
return configRepository
|
||||
.findByName(INSTANCE_CONFIG)
|
||||
.switchIfEmpty(Mono.defer(() -> createInstanceConfigForSuperUser()))
|
||||
.flatMap(instanceConfig -> {
|
||||
JSONObject config = instanceConfig.getConfig();
|
||||
String defaultPermissionGroup = (String) config.getOrDefault(DEFAULT_PERMISSION_GROUP, "");
|
||||
return permissionGroupRepository.findById(defaultPermissionGroup);
|
||||
});
|
||||
public Mono<PermissionGroup> getDefaultOrganizationAdminPermissionGroup() {
|
||||
return cacheableRepositoryHelper.getDefaultOrganizationId().flatMap(orgId -> {
|
||||
String permissionGroupId = inMemoryCacheableRepositoryHelper.getOrganizationAdminPermissionGroupId(orgId);
|
||||
if (hasLength(permissionGroupId)) {
|
||||
return permissionGroupRepository.findById(permissionGroupId);
|
||||
}
|
||||
return permissionGroupRepository
|
||||
.findByDefaultDomainIdAndDefaultDomainType(orgId, Organization.class.getSimpleName())
|
||||
.next()
|
||||
.doOnSuccess(
|
||||
permissionGroup -> inMemoryCacheableRepositoryHelper.setOrganizationAdminPermissionGroupId(
|
||||
orgId, permissionGroup.getId()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ import static com.appsmith.server.acl.AclPermission.MANAGE_INSTANCE_ENV;
|
|||
import static com.appsmith.server.acl.AclPermission.READ_INSTANCE_CONFIGURATION;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_PERMISSION_GROUP_MEMBERS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_THEMES;
|
||||
import static com.appsmith.server.acl.AppsmithRole.TENANT_ADMIN;
|
||||
import static com.appsmith.server.acl.AppsmithRole.ORGANIZATION_ADMIN;
|
||||
import static com.appsmith.server.constants.FieldName.DEFAULT_PERMISSION_GROUP;
|
||||
import static com.appsmith.server.constants.FieldName.PERMISSION_GROUP_ID;
|
||||
import static com.appsmith.server.migrations.DatabaseChangelog1.dropIndexIfExists;
|
||||
|
|
@ -550,7 +550,7 @@ public class DatabaseChangelog2 {
|
|||
policySolution.addPoliciesToExistingObject(readPermissionGroupPolicyMap, instanceAdminPGBeforeChanges);
|
||||
|
||||
// Now add admin permissions to the tenant
|
||||
Set<Permission> tenantPermissions = TENANT_ADMIN.getPermissions().stream()
|
||||
Set<Permission> tenantPermissions = ORGANIZATION_ADMIN.getPermissions().stream()
|
||||
.map(permission -> new Permission(defaultTenant.getId(), permission))
|
||||
.collect(Collectors.toSet());
|
||||
HashSet<Permission> permissions = new HashSet<>(instanceAdminPG.getPermissions());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
package com.appsmith.server.migrations.db.ce;
|
||||
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Config;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.PermissionGroup;
|
||||
import io.mongock.api.annotations.ChangeUnit;
|
||||
import io.mongock.api.annotations.Execution;
|
||||
import io.mongock.api.annotations.RollbackExecution;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minidev.json.JSONObject;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static com.appsmith.external.models.BaseDomain.policySetToMap;
|
||||
|
||||
@Slf4j
|
||||
@ChangeUnit(id = "rename-instance-admin-role-to-org-admin-role", order = "066")
|
||||
public class Migration066_RenameInstanceAdminRoleToOrgAdmin {
|
||||
|
||||
private final MongoTemplate mongoTemplate;
|
||||
|
||||
public Migration066_RenameInstanceAdminRoleToOrgAdmin(MongoTemplate mongoTemplate) {
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
}
|
||||
|
||||
@RollbackExecution
|
||||
public void rollbackExecution() {}
|
||||
|
||||
@Execution
|
||||
public void execute() {
|
||||
// 1. Find instanceConfig
|
||||
Config instanceConfig =
|
||||
mongoTemplate.findOne(Query.query(Criteria.where("name").is("instanceConfig")), Config.class);
|
||||
|
||||
if (instanceConfig == null || instanceConfig.getConfig() == null) {
|
||||
log.error("Instance config not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Find default organization - add null check
|
||||
Organization defaultOrganization = mongoTemplate.findOne(new Query(), Organization.class);
|
||||
if (defaultOrganization == null) {
|
||||
log.error("Default organization not found");
|
||||
return;
|
||||
}
|
||||
|
||||
renameInstanceAdminPermissionGroupToOrganizationAdmin(instanceConfig, defaultOrganization);
|
||||
createInstanceAdminRole(instanceConfig, defaultOrganization);
|
||||
}
|
||||
|
||||
private void createInstanceAdminRole(Config instanceConfig, Organization defaultOrganization) {
|
||||
|
||||
// Create instance management permission group
|
||||
PermissionGroup instanceManagerPermissionGroup = new PermissionGroup();
|
||||
instanceManagerPermissionGroup.setName(FieldName.INSTANCE_ADMIN_ROLE);
|
||||
|
||||
// No permissions given to instance admin role. This would be a hidden permission group.
|
||||
|
||||
// Fetch the organization admin role
|
||||
PermissionGroup organizationAdminRole = mongoTemplate.findOne(
|
||||
Query.query(Criteria.where("defaultDomainId").is(defaultOrganization.getId())), PermissionGroup.class);
|
||||
|
||||
// Assign the permission group to all the users and user groups who already are assigned to the org admin role
|
||||
instanceManagerPermissionGroup.setAssignedToUserIds(organizationAdminRole.getAssignedToUserIds());
|
||||
instanceManagerPermissionGroup.setAssignedToGroupIds(organizationAdminRole.getAssignedToGroupIds());
|
||||
|
||||
// Save the permission group
|
||||
PermissionGroup savedInstanceAdminRole = mongoTemplate.save(instanceManagerPermissionGroup);
|
||||
|
||||
// Update the config document
|
||||
JSONObject config = instanceConfig.getConfig();
|
||||
config.put("defaultPermissionGroup", savedInstanceAdminRole.getId());
|
||||
instanceConfig.setConfig(config);
|
||||
|
||||
Set<Policy> policies = instanceConfig.getPolicies();
|
||||
policies.stream().forEach(policy -> {
|
||||
policy.getPermissionGroups().remove(organizationAdminRole.getId());
|
||||
policy.getPermissionGroups().add(savedInstanceAdminRole.getId());
|
||||
});
|
||||
instanceConfig.setPolicies(policies);
|
||||
instanceConfig.setPolicyMap(policySetToMap(policies));
|
||||
mongoTemplate.save(instanceConfig);
|
||||
}
|
||||
|
||||
private void renameInstanceAdminPermissionGroupToOrganizationAdmin(
|
||||
Config instanceConfig, Organization defaultOrganization) {
|
||||
try {
|
||||
|
||||
String instanceAdminRoleId =
|
||||
instanceConfig.getConfig().get("defaultPermissionGroup").toString();
|
||||
|
||||
// 3. Update permission group with all fields in a single update
|
||||
Update update = Update.update("name", "Organization Administrator Role")
|
||||
.set("defaultDomainId", defaultOrganization.getId())
|
||||
.set("defaultDomainType", "Organization"); // Use string directly instead of class name
|
||||
|
||||
long modifiedCount = mongoTemplate
|
||||
.updateFirst(
|
||||
Query.query(Criteria.where("_id").is(instanceAdminRoleId)), update, PermissionGroup.class)
|
||||
.getModifiedCount();
|
||||
|
||||
if (modifiedCount > 0) {
|
||||
log.info(
|
||||
"Successfully renamed instance admin role to organization admin role for group: {}",
|
||||
instanceAdminRoleId);
|
||||
} else {
|
||||
log.warn("No permission group was updated for id: {}", instanceAdminRoleId);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error while renaming instance admin role", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,17 +70,24 @@ public class Migration10000_UpdateSuperUser {
|
|||
String instanceAdminPermissionGroupId =
|
||||
(String) instanceAdminConfiguration.getConfig().get(DEFAULT_PERMISSION_GROUP);
|
||||
|
||||
Query permissionGroupQuery = new Query();
|
||||
permissionGroupQuery
|
||||
Query instanceAdminPgQuery = new Query();
|
||||
instanceAdminPgQuery
|
||||
.addCriteria(where(PermissionGroup.Fields.id).is(instanceAdminPermissionGroupId))
|
||||
.fields()
|
||||
.include(PermissionGroup.Fields.assignedToUserIds);
|
||||
PermissionGroup instanceAdminPG = mongoTemplate.findOne(permissionGroupQuery, PermissionGroup.class);
|
||||
PermissionGroup instanceAdminPG = mongoTemplate.findOne(instanceAdminPgQuery, PermissionGroup.class);
|
||||
|
||||
Query organizationQuery = new Query();
|
||||
organizationQuery.addCriteria(where(Organization.Fields.slug).is("default"));
|
||||
Organization organization = mongoTemplate.findOne(organizationQuery, Organization.class);
|
||||
|
||||
// Find the default organization admin permission group
|
||||
Query orgAdminPermissionGroupQuery = new Query();
|
||||
orgAdminPermissionGroupQuery.addCriteria(
|
||||
where(PermissionGroup.Fields.defaultDomainType).is(Organization.class.getSimpleName()));
|
||||
orgAdminPermissionGroupQuery.addCriteria(
|
||||
where(PermissionGroup.Fields.defaultDomainId).is(organization.getId()));
|
||||
|
||||
Set<String> userIds = adminEmails.stream()
|
||||
.map(email -> email.trim())
|
||||
.map(String::toLowerCase)
|
||||
|
|
@ -103,8 +110,13 @@ public class Migration10000_UpdateSuperUser {
|
|||
Set<String> updatedUserIds = findSymmetricDiff(oldSuperUsers, userIds);
|
||||
evictPermissionCacheForUsers(updatedUserIds, mongoTemplate, cacheableRepositoryHelper);
|
||||
|
||||
// Assign the users to the instance admin pg
|
||||
Update update = new Update().set(PermissionGroup.Fields.assignedToUserIds, userIds);
|
||||
mongoTemplate.updateFirst(permissionGroupQuery, update, PermissionGroup.class);
|
||||
mongoTemplate.updateFirst(instanceAdminPgQuery, update, PermissionGroup.class);
|
||||
|
||||
// Assign the users to the default organization admin pg
|
||||
Update orgAdminUpdate = new Update().set(PermissionGroup.Fields.assignedToUserIds, userIds);
|
||||
mongoTemplate.updateFirst(orgAdminPermissionGroupQuery, orgAdminUpdate, PermissionGroup.class);
|
||||
|
||||
// Assign all super users to the default role
|
||||
updateSuperUserMigrationHelper.assignAllSuperUsersToDefaultRole(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ public interface CacheableRepositoryHelperCE {
|
|||
|
||||
Mono<Set<String>> getPermissionGroupsOfUser(User user);
|
||||
|
||||
Mono<String> getOrganizationAdminPermissionGroupId(String organizationId);
|
||||
|
||||
Mono<Set<String>> preFillAnonymousUserPermissionGroupIdsCache();
|
||||
|
||||
Mono<Set<String>> getPermissionGroupsOfAnonymousUser();
|
||||
|
|
@ -19,8 +21,6 @@ public interface CacheableRepositoryHelperCE {
|
|||
|
||||
Mono<String> getDefaultOrganizationId();
|
||||
|
||||
Mono<String> getInstanceAdminPermissionGroupId();
|
||||
|
||||
Mono<Organization> fetchDefaultOrganization(String organizationId);
|
||||
|
||||
Mono<Void> evictCachedOrganization(String organizationId);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import com.appsmith.server.helpers.ce.bridge.BridgeQuery;
|
|||
import io.micrometer.observation.ObservationRegistry;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minidev.json.JSONObject;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
|
@ -32,9 +31,8 @@ import java.util.stream.Collectors;
|
|||
import static com.appsmith.external.constants.spans.OrganizationSpan.FETCH_ORGANIZATION_FROM_DB_SPAN;
|
||||
import static com.appsmith.server.constants.FieldName.PERMISSION_GROUP_ID;
|
||||
import static com.appsmith.server.constants.ce.FieldNameCE.ANONYMOUS_USER;
|
||||
import static com.appsmith.server.constants.ce.FieldNameCE.DEFAULT_PERMISSION_GROUP;
|
||||
import static com.appsmith.server.constants.ce.FieldNameCE.INSTANCE_CONFIG;
|
||||
import static com.appsmith.server.repositories.ce.BaseAppsmithRepositoryCEImpl.notDeleted;
|
||||
import static org.springframework.util.StringUtils.hasLength;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
|
|
@ -58,33 +56,35 @@ public class CacheableRepositoryHelperCEImpl implements CacheableRepositoryHelpe
|
|||
if (user.getEmail() == null
|
||||
|| user.getEmail().isEmpty()
|
||||
|| user.getId() == null
|
||||
|| user.getId().isEmpty()) {
|
||||
|| user.getId().isEmpty()
|
||||
|| user.getOrganizationId() == null) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.SESSION_BAD_STATE));
|
||||
}
|
||||
|
||||
Mono<Query> createQueryMono = getInstanceAdminPermissionGroupId().map(instanceAdminPermissionGroupId -> {
|
||||
BridgeQuery<PermissionGroup> assignedToUserIdsCriteria =
|
||||
Bridge.equal(PermissionGroup.Fields.assignedToUserIds, user.getId());
|
||||
Mono<Query> createQueryMono = getOrganizationAdminPermissionGroupId(user.getOrganizationId())
|
||||
.map(organizationAdminPermissionGroupId -> {
|
||||
BridgeQuery<PermissionGroup> assignedToUserIdsCriteria =
|
||||
Bridge.equal(PermissionGroup.Fields.assignedToUserIds, user.getId());
|
||||
|
||||
BridgeQuery<PermissionGroup> notDeletedCriteria = notDeleted();
|
||||
BridgeQuery<PermissionGroup> notDeletedCriteria = notDeleted();
|
||||
|
||||
// The roles should be either workspace default roles, user management role, or instance admin role
|
||||
BridgeQuery<PermissionGroup> ceSupportedRolesCriteria = Bridge.or(
|
||||
Bridge.equal(PermissionGroup.Fields.defaultDomainType, Workspace.class.getSimpleName()),
|
||||
Bridge.equal(PermissionGroup.Fields.defaultDomainType, User.class.getSimpleName()),
|
||||
Bridge.equal(PermissionGroup.Fields.id, instanceAdminPermissionGroupId));
|
||||
// The roles should be either workspace default roles, user management role, or instance admin role
|
||||
BridgeQuery<PermissionGroup> ceSupportedRolesCriteria = Bridge.or(
|
||||
Bridge.equal(PermissionGroup.Fields.defaultDomainType, Workspace.class.getSimpleName()),
|
||||
Bridge.equal(PermissionGroup.Fields.defaultDomainType, User.class.getSimpleName()),
|
||||
Bridge.equal(PermissionGroup.Fields.id, organizationAdminPermissionGroupId));
|
||||
|
||||
BridgeQuery<PermissionGroup> andCriteria =
|
||||
Bridge.and(assignedToUserIdsCriteria, notDeletedCriteria, ceSupportedRolesCriteria);
|
||||
BridgeQuery<PermissionGroup> andCriteria =
|
||||
Bridge.and(assignedToUserIdsCriteria, notDeletedCriteria, ceSupportedRolesCriteria);
|
||||
|
||||
Query query = new Query();
|
||||
query.addCriteria(andCriteria);
|
||||
Query query = new Query();
|
||||
query.addCriteria(andCriteria);
|
||||
|
||||
// Since we are only interested in the permission group ids, we can project only the id field.
|
||||
query.fields().include(PermissionGroup.Fields.id);
|
||||
// Since we are only interested in the permission group ids, we can project only the id field.
|
||||
query.fields().include(PermissionGroup.Fields.id);
|
||||
|
||||
return query;
|
||||
});
|
||||
return query;
|
||||
});
|
||||
|
||||
return createQueryMono
|
||||
.map(query -> mongoOperations.find(query, PermissionGroup.class))
|
||||
|
|
@ -93,6 +93,35 @@ public class CacheableRepositoryHelperCEImpl implements CacheableRepositoryHelpe
|
|||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> getOrganizationAdminPermissionGroupId(String organizationId) {
|
||||
|
||||
String organizationAdminPermissionGroupId =
|
||||
inMemoryCacheableRepositoryHelper.getOrganizationAdminPermissionGroupId(organizationId);
|
||||
if (hasLength(organizationAdminPermissionGroupId)) {
|
||||
return Mono.just(organizationAdminPermissionGroupId);
|
||||
}
|
||||
|
||||
// Find the permission group id of the organization admin
|
||||
BridgeQuery<PermissionGroup> andCriteria = Bridge.and(
|
||||
Bridge.equal(PermissionGroup.Fields.defaultDomainType, Organization.class.getSimpleName()),
|
||||
Bridge.equal(PermissionGroup.Fields.defaultDomainId, organizationId));
|
||||
|
||||
Query query = new Query();
|
||||
query.addCriteria(andCriteria);
|
||||
|
||||
// Since we are only interested in the permission group ids, we can project only the id field.
|
||||
query.fields().include(PermissionGroup.Fields.id);
|
||||
|
||||
return mongoOperations
|
||||
.find(query, PermissionGroup.class)
|
||||
.map(permissionGroup -> permissionGroup.getId())
|
||||
.next()
|
||||
.doOnSuccess(
|
||||
permissionGroupId -> inMemoryCacheableRepositoryHelper.setOrganizationAdminPermissionGroupId(
|
||||
organizationId, permissionGroupId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Set<String>> preFillAnonymousUserPermissionGroupIdsCache() {
|
||||
Set<String> roleIdsForAnonymousUser = inMemoryCacheableRepositoryHelper.getAnonymousUserPermissionGroupIds();
|
||||
|
|
@ -152,25 +181,6 @@ public class CacheableRepositoryHelperCEImpl implements CacheableRepositoryHelpe
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> getInstanceAdminPermissionGroupId() {
|
||||
String instanceAdminPermissionGroupId = inMemoryCacheableRepositoryHelper.getInstanceAdminPermissionGroupId();
|
||||
if (instanceAdminPermissionGroupId != null && !instanceAdminPermissionGroupId.isEmpty()) {
|
||||
return Mono.just(instanceAdminPermissionGroupId);
|
||||
}
|
||||
|
||||
BridgeQuery<Config> configName = Bridge.equal(Config.Fields.name, INSTANCE_CONFIG);
|
||||
|
||||
return mongoOperations
|
||||
.findOne(new Query().addCriteria(configName), Config.class)
|
||||
.map(instanceConfig -> {
|
||||
JSONObject config = instanceConfig.getConfig();
|
||||
return (String) config.getOrDefault(DEFAULT_PERMISSION_GROUP, "");
|
||||
})
|
||||
.doOnSuccess(permissionGroupId ->
|
||||
inMemoryCacheableRepositoryHelper.setInstanceAdminPermissionGroupId(permissionGroupId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default organization from the cache if present.
|
||||
* If not present in cache, then it fetches the default organization from the database and adds to redis.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
package com.appsmith.server.repositories.ce;
|
||||
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.repositories.AppsmithRepository;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface CustomOrganizationRepositoryCE extends AppsmithRepository<Organization> {
|
||||
Mono<Integer> disableRestartForAllTenants();
|
||||
|
||||
Mono<Organization> findByIdAsUser(User user, String id, AclPermission permission);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.appsmith.server.repositories.ce;
|
||||
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.helpers.ce.bridge.Bridge;
|
||||
import com.appsmith.server.repositories.BaseAppsmithRepositoryImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -19,4 +22,13 @@ public class CustomOrganizationRepositoryCEImpl extends BaseAppsmithRepositoryIm
|
|||
.criteria(Bridge.isTrue(organizationConfiguration_isRestartRequired))
|
||||
.updateAll(Bridge.update().set(organizationConfiguration_isRestartRequired, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Organization> findByIdAsUser(User user, String id, AclPermission permission) {
|
||||
return getAllPermissionGroupsForUser(user).flatMap(permissionGroups -> queryBuilder()
|
||||
.criteria(Bridge.equal(BaseDomain.Fields.id, id))
|
||||
.permission(permission)
|
||||
.permissionGroups(permissionGroups)
|
||||
.one());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -417,7 +417,9 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
|
|||
.flatMap(this::addUserPoliciesAndSaveToRepo)
|
||||
.flatMap(crudUser -> {
|
||||
if (isAdminUser) {
|
||||
return userUtils.makeSuperUser(List.of(crudUser)).then(Mono.just(crudUser));
|
||||
return userUtils
|
||||
.makeInstanceAdministrator(List.of(crudUser))
|
||||
.then(Mono.just(crudUser));
|
||||
}
|
||||
return Mono.just(crudUser);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -611,7 +611,7 @@ public class EnvManagerCEImpl implements EnvManagerCE {
|
|||
Mono<Boolean> removedUsersMono = Flux.fromIterable(removedUsers)
|
||||
.flatMap(userService::findByEmail)
|
||||
.collectList()
|
||||
.flatMap(userUtils::removeSuperUser);
|
||||
.flatMap(userUtils::removeInstanceAdmin);
|
||||
|
||||
Flux<Tuple2<User, Boolean>> usersFlux = Flux.fromIterable(newUsers)
|
||||
.flatMap(email -> userService
|
||||
|
|
@ -644,7 +644,7 @@ public class EnvManagerCEImpl implements EnvManagerCE {
|
|||
.map(results -> results.stream().allMatch(result -> result));
|
||||
|
||||
Mono<Boolean> existingUsersMono = existingUsersWhichAreNotAlreadySuperUsersMono.flatMap(users -> userUtils
|
||||
.makeSuperUser(users)
|
||||
.makeInstanceAdministrator(users)
|
||||
.flatMap(
|
||||
success -> Flux.fromIterable(users)
|
||||
.flatMap(user -> sessionUserService
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ public class UserSignupCEImpl implements UserSignupCE {
|
|||
})
|
||||
.flatMap(user -> {
|
||||
Mono<Boolean> makeSuperUserMono = userUtils
|
||||
.makeSuperUser(List.of(user))
|
||||
.makeInstanceAdministrator(List.of(user))
|
||||
.elapsed()
|
||||
.map(pair -> {
|
||||
log.debug(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
package com.appsmith.server.helpers;
|
||||
|
||||
import com.appsmith.server.domains.PermissionGroup;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.helpers.ce.UserUtilsCE;
|
||||
import com.appsmith.server.repositories.PermissionGroupRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
class UserUtilsTest {
|
||||
|
||||
@Autowired
|
||||
private UserUtilsCE userUtils;
|
||||
|
||||
@Autowired
|
||||
private PermissionGroupRepository permissionGroupRepository;
|
||||
|
||||
private User user1;
|
||||
private User user2;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Create test users
|
||||
user1 = new User();
|
||||
user1.setId("user1");
|
||||
user1.setEmail("user1@test.com");
|
||||
user1.setOrganizationId("org1");
|
||||
|
||||
user2 = new User();
|
||||
user2.setId("user2");
|
||||
user2.setEmail("user2@test.com");
|
||||
user2.setOrganizationId("org1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void makeInstanceAdministrator_WhenUsersProvided_AssignsPermissionsSuccessfully() {
|
||||
List<User> users = Arrays.asList(user1, user2);
|
||||
|
||||
StepVerifier.create(userUtils
|
||||
.makeInstanceAdministrator(users)
|
||||
.then(Mono.zip(
|
||||
userUtils.getInstanceAdminPermissionGroup(),
|
||||
userUtils.getDefaultOrganizationAdminPermissionGroup())))
|
||||
.assertNext(tuple -> {
|
||||
PermissionGroup instanceAdminPG = tuple.getT1();
|
||||
PermissionGroup organizationAdminPG = tuple.getT2();
|
||||
|
||||
// Verify instance admin assignments
|
||||
assertThat(instanceAdminPG.getAssignedToUserIds())
|
||||
.contains(user1.getId(), user2.getId())
|
||||
.as("Users should be assigned to instance admin group");
|
||||
|
||||
// Verify organization admin assignments
|
||||
assertThat(organizationAdminPG.getAssignedToUserIds())
|
||||
.contains(user1.getId(), user2.getId())
|
||||
.as("Users should be assigned to organization admin group");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeInstanceAdmin_WhenUsersProvided_RemovesPermissionsSuccessfully() {
|
||||
List<User> users = Arrays.asList(user1, user2);
|
||||
|
||||
// First add the users as admins
|
||||
StepVerifier.create(userUtils
|
||||
.makeInstanceAdministrator(users)
|
||||
.then(userUtils.removeInstanceAdmin(users))
|
||||
.then(Mono.zip(
|
||||
userUtils.getInstanceAdminPermissionGroup(),
|
||||
userUtils.getDefaultOrganizationAdminPermissionGroup())))
|
||||
.assertNext(tuple -> {
|
||||
PermissionGroup instanceAdminPG = tuple.getT1();
|
||||
PermissionGroup organizationAdminPG = tuple.getT2();
|
||||
|
||||
// Verify instance admin unassignments
|
||||
assertThat(instanceAdminPG.getAssignedToUserIds())
|
||||
.doesNotContain(user1.getId(), user2.getId())
|
||||
.as("Users should be removed from instance admin group");
|
||||
|
||||
// Verify organization admin unassignments
|
||||
assertThat(organizationAdminPG.getAssignedToUserIds())
|
||||
.doesNotContain(user1.getId(), user2.getId())
|
||||
.as("Users should be removed from organization admin group");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void makeInstanceAdministrator_WhenUserAlreadyAdmin_MaintainsPermissionsSuccessfully() {
|
||||
List<User> users = List.of(user1);
|
||||
|
||||
// Add user as admin twice
|
||||
StepVerifier.create(userUtils
|
||||
.makeInstanceAdministrator(users)
|
||||
.then(userUtils.makeInstanceAdministrator(users))
|
||||
.then(Mono.zip(
|
||||
userUtils.getInstanceAdminPermissionGroup(),
|
||||
userUtils.getDefaultOrganizationAdminPermissionGroup())))
|
||||
.assertNext(tuple -> {
|
||||
PermissionGroup instanceAdminPG = tuple.getT1();
|
||||
PermissionGroup organizationAdminPG = tuple.getT2();
|
||||
|
||||
// Verify user is still assigned exactly once
|
||||
assertThat(instanceAdminPG.getAssignedToUserIds())
|
||||
.contains(user1.getId())
|
||||
.hasSize(1)
|
||||
.as("User should be assigned exactly once to instance admin group");
|
||||
|
||||
assertThat(organizationAdminPG.getAssignedToUserIds())
|
||||
.contains(user1.getId())
|
||||
.hasSize(1)
|
||||
.as("User should be assigned exactly once to organization admin group");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ public class CacheableRepositoryTest {
|
|||
@BeforeEach()
|
||||
public void setup() {
|
||||
User api_user = userRepository.findByEmail("api_user").block();
|
||||
userUtils.makeSuperUser(List.of(api_user)).block();
|
||||
userUtils.makeInstanceAdministrator(List.of(api_user)).block();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public class PermissionGroupServiceTest {
|
|||
// Make api_user instance admin before running the test
|
||||
userRepository
|
||||
.findByEmail("api_user")
|
||||
.flatMap(user -> userUtils.makeSuperUser(List.of(user)))
|
||||
.flatMap(user -> userUtils.makeInstanceAdministrator(List.of(user)))
|
||||
.block();
|
||||
|
||||
PermissionGroup testPermissionGroup = new PermissionGroup();
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ public class UserWorkspaceServiceTest {
|
|||
// Ensure neither of the users are super users
|
||||
User api_user = userRepository.findByEmail("api_user").block();
|
||||
User test_user = userRepository.findByEmail("usertest@usertest.com").block();
|
||||
userUtils.removeSuperUser(List.of(api_user, test_user)).block();
|
||||
userUtils.removeInstanceAdmin(List.of(api_user, test_user)).block();
|
||||
Workspace workspace = new Workspace();
|
||||
workspace.setName("Test org");
|
||||
Workspace createdWorkspace = workspaceService.create(workspace).block();
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class OrganizationServiceCETest {
|
|||
// Todo change this to organization admin once we introduce multitenancy
|
||||
userRepository
|
||||
.findByEmail("api_user")
|
||||
.flatMap(user -> userUtils.makeSuperUser(List.of(user)))
|
||||
.flatMap(user -> userUtils.makeInstanceAdministrator(List.of(user)))
|
||||
.block();
|
||||
doReturn(Mono.empty()).when(cacheManager).get(anyString(), anyString());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user