Merge branch 'release' of https://github.com/appsmithorg/appsmith into release
This commit is contained in:
commit
245721deb9
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import CustomizedDropdown, {
|
||||
CustomizedDropdownProps,
|
||||
} from "pages/common/CustomizedDropdown/index";
|
||||
|
|
@ -8,7 +8,7 @@ type SelectComponentProps = {
|
|||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
};
|
||||
options?: Array<{ id: string; name: string }>;
|
||||
options?: Array<{ id: string; name: string; content?: ReactNode }>;
|
||||
placeholder?: string;
|
||||
size?: "large" | "small";
|
||||
outline?: boolean;
|
||||
|
|
@ -22,7 +22,7 @@ export const SelectComponent = (props: SelectComponentProps) => {
|
|||
options:
|
||||
props.options &&
|
||||
props.options.map(option => ({
|
||||
content: option.name,
|
||||
content: option.content ? option.content : option.name,
|
||||
onSelect: () => {
|
||||
props.input.onChange && props.input.onChange(option.id);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -35,6 +35,19 @@ const OrgInviteTitle = styled.div`
|
|||
padding: 10px 0px;
|
||||
`;
|
||||
|
||||
const DropDownOption = styled.div`
|
||||
padding: 10px 0;
|
||||
`;
|
||||
|
||||
const OptionTitle = styled.div`
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const OptionDescription = styled.div`
|
||||
padding: 5px 0px;
|
||||
max-width: 250px;
|
||||
`;
|
||||
|
||||
const StyledForm = styled.form`
|
||||
width: 100%;
|
||||
background: white;
|
||||
|
|
@ -158,6 +171,19 @@ const InviteUsersForm = (props: any) => {
|
|||
fetchCurrentOrg(props.orgId);
|
||||
}, [props.orgId, fetchUser, fetchAllRoles, fetchCurrentOrg]);
|
||||
|
||||
const styledRoles = props.roles.map((role: any) => {
|
||||
return {
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
content: (
|
||||
<DropDownOption>
|
||||
<OptionTitle>{role.name}</OptionTitle>
|
||||
<OptionDescription>{role.description}</OptionDescription>
|
||||
</DropDownOption>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{applicationId && (
|
||||
|
|
@ -199,7 +225,7 @@ const InviteUsersForm = (props: any) => {
|
|||
<SelectField
|
||||
name="role"
|
||||
placeholder="Select a role"
|
||||
options={props.roles}
|
||||
options={styledRoles}
|
||||
size="small"
|
||||
outline={false}
|
||||
data-cy="t--invite-role-input"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export const getRolesForField = createSelector(getAllRoles, (roles?: any) => {
|
|||
return {
|
||||
id: role[0],
|
||||
name: role[0],
|
||||
description: role[1],
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,10 +41,16 @@ public enum AclPermission {
|
|||
ORGANIZATION_READ_APPLICATIONS("read:orgApplications", Organization.class),
|
||||
ORGANIZATION_PUBLISH_APPLICATIONS("publish:orgApplications", Organization.class),
|
||||
|
||||
// Invitation related permissions
|
||||
ORGANIZATION_INVITE_USERS("inviteUsers:organization", Organization.class),
|
||||
|
||||
MANAGE_APPLICATIONS("manage:applications", Application.class),
|
||||
READ_APPLICATIONS("read:applications", Application.class),
|
||||
PUBLISH_APPLICATIONS("publish:applications", Application.class),
|
||||
|
||||
// Making an application public permission at Organization level
|
||||
MAKE_PUBLIC_APPLICATIONS("makePublic:applications", Application.class),
|
||||
|
||||
MANAGE_PAGES("manage:pages", Page.class),
|
||||
READ_PAGES("read:pages", Page.class),
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import java.util.Set;
|
|||
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_ORGANIZATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_PUBLISH_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_READ_APPLICATIONS;
|
||||
|
|
@ -19,9 +20,9 @@ public enum AppsmithRole {
|
|||
APPLICATION_ADMIN("Application Administrator", "", Set.of(MANAGE_APPLICATIONS)),
|
||||
APPLICATION_VIEWER("Application Viewer", "", Set.of(READ_APPLICATIONS)),
|
||||
ORGANIZATION_ADMIN("Administrator", "Can edit, view applications and invite other user to organization",
|
||||
Set.of(MANAGE_ORGANIZATIONS)),
|
||||
Set.of(MANAGE_ORGANIZATIONS, ORGANIZATION_INVITE_USERS)),
|
||||
ORGANIZATION_DEVELOPER("Developer", "Can edit and view applications", Set.of(READ_ORGANIZATIONS,
|
||||
ORGANIZATION_MANAGE_APPLICATIONS, ORGANIZATION_READ_APPLICATIONS, ORGANIZATION_PUBLISH_APPLICATIONS)),
|
||||
ORGANIZATION_MANAGE_APPLICATIONS, ORGANIZATION_READ_APPLICATIONS, ORGANIZATION_PUBLISH_APPLICATIONS, ORGANIZATION_INVITE_USERS)),
|
||||
ORGANIZATION_VIEWER("App Viewer", "Can view applications", Set.of(READ_ORGANIZATIONS, ORGANIZATION_READ_APPLICATIONS));
|
||||
|
||||
private Set<AclPermission> permissions;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import static com.appsmith.server.acl.AclPermission.EXECUTE_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES;
|
||||
import static com.appsmith.server.acl.AclPermission.MAKE_PUBLIC_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES;
|
||||
|
|
@ -107,6 +108,7 @@ public class PolicyGenerator {
|
|||
hierarchyGraph.addEdge(ORGANIZATION_MANAGE_APPLICATIONS, MANAGE_APPLICATIONS);
|
||||
hierarchyGraph.addEdge(ORGANIZATION_READ_APPLICATIONS, READ_APPLICATIONS);
|
||||
hierarchyGraph.addEdge(ORGANIZATION_PUBLISH_APPLICATIONS, PUBLISH_APPLICATIONS);
|
||||
hierarchyGraph.addEdge(MANAGE_ORGANIZATIONS, MAKE_PUBLIC_APPLICATIONS);
|
||||
|
||||
// If the user is being given MANAGE_APPLICATION permission, they must also be given READ_APPLICATION perm
|
||||
lateralGraph.addEdge(MANAGE_APPLICATIONS, READ_APPLICATIONS);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
package com.appsmith.server.acl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jgrapht.Graph;
|
||||
import org.jgrapht.graph.DefaultEdge;
|
||||
import org.jgrapht.graph.DirectedMultigraph;
|
||||
import org.jgrapht.traverse.BreadthFirstIterator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.appsmith.server.acl.AppsmithRole.APPLICATION_ADMIN;
|
||||
import static com.appsmith.server.acl.AppsmithRole.APPLICATION_VIEWER;
|
||||
import static com.appsmith.server.acl.AppsmithRole.ORGANIZATION_ADMIN;
|
||||
import static com.appsmith.server.acl.AppsmithRole.ORGANIZATION_DEVELOPER;
|
||||
import static com.appsmith.server.acl.AppsmithRole.ORGANIZATION_VIEWER;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RoleGraph {
|
||||
/**
|
||||
* This graph defines the hierarchy of permissions from parent objects
|
||||
*/
|
||||
Graph<AppsmithRole, DefaultEdge> hierarchyGraph = new DirectedMultigraph<>(DefaultEdge.class);
|
||||
|
||||
@PostConstruct
|
||||
public void createPolicyGraph() {
|
||||
|
||||
// Initialization of the hierarchical and lateral graphs by adding all the vertices
|
||||
EnumSet.allOf(AppsmithRole.class)
|
||||
.forEach(role -> {
|
||||
hierarchyGraph.addVertex(role);
|
||||
});
|
||||
|
||||
hierarchyGraph.addEdge(ORGANIZATION_ADMIN, ORGANIZATION_DEVELOPER);
|
||||
hierarchyGraph.addEdge(ORGANIZATION_DEVELOPER, ORGANIZATION_VIEWER);
|
||||
hierarchyGraph.addEdge(APPLICATION_ADMIN, APPLICATION_VIEWER);
|
||||
}
|
||||
|
||||
public Set<AppsmithRole> generateHierarchicalRoles(String roleName) {
|
||||
AppsmithRole role = AppsmithRole.generateAppsmithRoleFromName(roleName);
|
||||
|
||||
Set<AppsmithRole> childrenRoles = new HashSet<>();
|
||||
childrenRoles.add(role);
|
||||
BreadthFirstIterator<AppsmithRole, DefaultEdge> breadthFirstIterator = new BreadthFirstIterator<>(hierarchyGraph, role);
|
||||
while(breadthFirstIterator.hasNext()) {
|
||||
childrenRoles.add(breadthFirstIterator.next());
|
||||
}
|
||||
|
||||
return childrenRoles;
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
|
@ -35,8 +36,8 @@ public class OrganizationController extends BaseController<OrganizationService,
|
|||
* @return
|
||||
*/
|
||||
@GetMapping("/roles")
|
||||
public Mono<ResponseDTO<Map<String, String>>> getUserRolesForOrganization() {
|
||||
return service.getUserRolesForOrganization()
|
||||
public Mono<ResponseDTO<Map<String, String>>> getUserRolesForOrganization(@RequestParam String organizationId) {
|
||||
return service.getUserRolesForOrganization(organizationId)
|
||||
.map(permissions -> new ResponseDTO<>(HttpStatus.OK.value(), permissions, null));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ public enum AppsmithError {
|
|||
DUPLICATE_KEY(409, 4024, "Duplicate key error"),
|
||||
USER_ALREADY_EXISTS_SIGNUP(409, 4025, "There is already an account registered with this username {0}. Please sign in."),
|
||||
UNAUTHORIZED_ACCESS(403, 4025, "Unauthorized access"),
|
||||
ACTION_IS_NOT_AUTHORIZED(403, 4026, "Sorry. You do not have permissions to perform this action"),
|
||||
INVALID_DATASOURCE_NAME(400, 4026, "Invalid datasource name. Check again."),
|
||||
NO_RESOURCE_FOUND(404, 4027, "Unable to find {0} with id {1}"),
|
||||
ACL_NO_RESOURCE_FOUND(404, 4028, "Unable to find {0} with id {1}. Either the asset doesn't exist or you don't have required permissions"),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.appsmith.server.migrations;
|
|||
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.acl.AppsmithRole;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Action;
|
||||
import com.appsmith.server.domains.Application;
|
||||
|
|
@ -47,6 +48,8 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.EXECUTE_ACTIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MAKE_PUBLIC_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
import static org.springframework.data.mongodb.core.query.Query.query;
|
||||
|
|
@ -577,4 +580,68 @@ public class DatabaseChangelog {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ChangeSet(order = "021", id = "invite-and-public-permissions", author = "")
|
||||
public void giveInvitePermissionToOrganizationsAndPublicPermissionsToApplications(MongoTemplate mongoTemplate) {
|
||||
final List<Organization> organizations = mongoTemplate.find(
|
||||
query(where("userRoles").exists(true)),
|
||||
Organization.class
|
||||
);
|
||||
|
||||
final List<Application> applications = mongoTemplate.find(
|
||||
query(where("policies").exists(true)),
|
||||
Application.class
|
||||
);
|
||||
|
||||
for (final Organization organization : organizations) {
|
||||
Set<String> adminUsernames = organization.getUserRoles()
|
||||
.stream()
|
||||
.filter(role -> (role.getRole().equals(AppsmithRole.ORGANIZATION_ADMIN)))
|
||||
.map(role -> role.getUsername())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> developerUsernames = organization.getUserRoles()
|
||||
.stream()
|
||||
.filter(role -> (role.getRole().equals(AppsmithRole.ORGANIZATION_DEVELOPER)))
|
||||
.map(role -> role.getUsername())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// All the developers and administrators of the organization should be allowed to get invite permissions
|
||||
Set<String> invitePermissionUsernames = new HashSet<>();
|
||||
invitePermissionUsernames.addAll(developerUsernames);
|
||||
invitePermissionUsernames.addAll(adminUsernames);
|
||||
|
||||
Set<Policy> policies = organization.getPolicies();
|
||||
if (policies == null) {
|
||||
policies = new HashSet<>();
|
||||
}
|
||||
|
||||
Policy inviteUserPolicy = Policy.builder().permission(ORGANIZATION_INVITE_USERS.getValue())
|
||||
.users(invitePermissionUsernames).build();
|
||||
|
||||
policies.add(inviteUserPolicy);
|
||||
organization.setPolicies(policies);
|
||||
mongoTemplate.save(organization);
|
||||
|
||||
// Update the applications with public view policy for all administrators of the organization
|
||||
Set<Application> orgApplications = applications
|
||||
.stream()
|
||||
.filter(application -> application.getOrganizationId().equals(organization.getId()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (final Application application : orgApplications) {
|
||||
Set<Policy> applicationPolicies = application.getPolicies();
|
||||
if (applicationPolicies == null) {
|
||||
applicationPolicies = new HashSet<>();
|
||||
}
|
||||
|
||||
Policy newPublicAppPolicy = Policy.builder().permission(MAKE_PUBLIC_APPLICATIONS.getValue())
|
||||
.users(adminUsernames).build();
|
||||
applicationPolicies.add(newPublicAppPolicy);
|
||||
application.setPolicies(applicationPolicies);
|
||||
|
||||
mongoTemplate.save(application);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,15 +218,7 @@ public class ApplicationPageServiceImpl implements ApplicationPageService {
|
|||
|
||||
return orgMono.map(org -> {
|
||||
application.setOrganizationId(org.getId());
|
||||
// At the organization level, filter out all the application specific policies and apply them
|
||||
// to the new application that we are creating.
|
||||
Set<Policy> policySet = org.getPolicies().stream()
|
||||
.filter(policy ->
|
||||
policy.getPermission().equals(ORGANIZATION_MANAGE_APPLICATIONS.getValue()) ||
|
||||
policy.getPermission().equals(ORGANIZATION_READ_APPLICATIONS.getValue())
|
||||
).collect(Collectors.toSet());
|
||||
|
||||
Set<Policy> documentPolicies = policyGenerator.getAllChildPolicies(policySet, Organization.class, Application.class);
|
||||
Set<Policy> documentPolicies = policyGenerator.getAllChildPolicies(org.getPolicies(), Organization.class, Application.class);
|
||||
application.setPolicies(documentPolicies);
|
||||
return application;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MAKE_PUBLIC_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
|
||||
|
||||
|
|
@ -184,7 +184,7 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
|
|||
@Override
|
||||
public Mono<Application> changeViewAccess(String id, ApplicationAccessDTO applicationAccessDTO) {
|
||||
return repository
|
||||
.findById(id, MANAGE_APPLICATIONS)
|
||||
.findById(id, MAKE_PUBLIC_APPLICATIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, id)))
|
||||
.flatMap(application -> {
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public interface OrganizationService extends CrudService<Organization, String> {
|
|||
|
||||
Flux<Organization> findByIdsIn(Set<String> ids,AclPermission permission);
|
||||
|
||||
Mono<Map<String, String>> getUserRolesForOrganization();
|
||||
Mono<Map<String, String>> getUserRolesForOrganization(String orgId);
|
||||
|
||||
Mono<List<UserRole>> getOrganizationMembers(String orgId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.appsmith.server.services;
|
|||
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.acl.AppsmithRole;
|
||||
import com.appsmith.server.acl.RoleGraph;
|
||||
import com.appsmith.server.constants.AnalyticsEvents;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
|
|
@ -29,13 +30,14 @@ import reactor.core.scheduler.Scheduler;
|
|||
|
||||
import javax.validation.Validator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_ORGANIZATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.USER_MANAGE_ORGANIZATIONS;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
|
@ -51,6 +53,7 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
|
|||
private final SessionUserService sessionUserService;
|
||||
private final UserOrganizationService userOrganizationService;
|
||||
private final UserRepository userRepository;
|
||||
private final RoleGraph roleGraph;
|
||||
|
||||
@Autowired
|
||||
public OrganizationServiceImpl(Scheduler scheduler,
|
||||
|
|
@ -64,7 +67,8 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
|
|||
PluginRepository pluginRepository,
|
||||
SessionUserService sessionUserService,
|
||||
UserOrganizationService userOrganizationService,
|
||||
UserRepository userRepository) {
|
||||
UserRepository userRepository,
|
||||
RoleGraph roleGraph) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.repository = repository;
|
||||
this.settingService = settingService;
|
||||
|
|
@ -73,6 +77,7 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
|
|||
this.sessionUserService = sessionUserService;
|
||||
this.userOrganizationService = userOrganizationService;
|
||||
this.userRepository = userRepository;
|
||||
this.roleGraph = roleGraph;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -241,30 +246,50 @@ public class OrganizationServiceImpl extends BaseService<OrganizationRepository,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, String>> getUserRolesForOrganization() {
|
||||
// Get all the roles for Organization entity from the enum AppsmithRole
|
||||
Map<String, String> appsmithRoles = Arrays.asList(AppsmithRole.values())
|
||||
.stream()
|
||||
.filter(role -> {
|
||||
Set<AclPermission> permissions = role.getPermissions();
|
||||
if (permissions != null && !permissions.isEmpty()) {
|
||||
for (AclPermission permission : permissions) {
|
||||
if (permission.getEntity().equals(Organization.class)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.collect(toMap(role -> role.getName(), AppsmithRole::getDescription));
|
||||
public Mono<Map<String, String>> getUserRolesForOrganization(String orgId) {
|
||||
if (orgId == null || orgId.isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORGANIZATION_ID));
|
||||
}
|
||||
|
||||
return Mono.just(appsmithRoles);
|
||||
Mono<Organization> organizationMono = repository.findById(orgId, ORGANIZATION_INVITE_USERS);
|
||||
Mono<String> usernameMono = sessionUserService
|
||||
.getCurrentUser()
|
||||
.map(user -> user.getUsername());
|
||||
|
||||
return organizationMono
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, orgId)))
|
||||
.zipWith(usernameMono)
|
||||
.flatMap(tuple -> {
|
||||
Organization organization = tuple.getT1();
|
||||
String username = tuple.getT2();
|
||||
|
||||
List<UserRole> userRoles = organization.getUserRoles();
|
||||
if (userRoles == null || userRoles.isEmpty()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
Optional<UserRole> optionalUserRole = userRoles.stream().filter(role -> role.getUsername().equals(username)).findFirst();
|
||||
if (!optionalUserRole.isPresent()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
UserRole currentUserRole = optionalUserRole.get();
|
||||
String roleName = currentUserRole.getRoleName();
|
||||
|
||||
Set<AppsmithRole> appsmithRoles = roleGraph.generateHierarchicalRoles(roleName);
|
||||
|
||||
Map<String, String> appsmithRolesMap = appsmithRoles
|
||||
.stream()
|
||||
.collect(toMap(role -> role.getName(), AppsmithRole::getDescription));
|
||||
|
||||
return Mono.just(appsmithRolesMap);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<List<UserRole>> getOrganizationMembers(String orgId) {
|
||||
return repository
|
||||
.findById(orgId, MANAGE_ORGANIZATIONS)
|
||||
.findById(orgId, ORGANIZATION_INVITE_USERS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, orgId)))
|
||||
.map(organization -> {
|
||||
final List<UserRole> userRoles = organization.getUserRoles();
|
||||
|
|
|
|||
|
|
@ -246,7 +246,9 @@ public class UserOrganizationServiceImpl implements UserOrganizationService {
|
|||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "username"));
|
||||
}
|
||||
|
||||
Mono<Organization> organizationMono = organizationRepository.findById(orgId, MANAGE_ORGANIZATIONS);
|
||||
Mono<Organization> organizationMono = organizationRepository
|
||||
.findById(orgId, MANAGE_ORGANIZATIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.ACTION_IS_NOT_AUTHORIZED)));
|
||||
Mono<User> userMono = userRepository.findByEmail(userRole.getUsername());
|
||||
Mono<User> currentUserMono = sessionUserService.getCurrentUser();
|
||||
|
||||
|
|
@ -266,7 +268,7 @@ public class UserOrganizationServiceImpl implements UserOrganizationService {
|
|||
if (role.getUsername().equals(userRole.getUsername())) {
|
||||
// User found in the organization.
|
||||
|
||||
if (role.getRoleName().equals(userRole.getRoleName())) {
|
||||
if (role.getRole().equals(userRole)) {
|
||||
// No change in the role. Do nothing.
|
||||
Mono.just(userRole);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.appsmith.server.services;
|
|||
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.acl.AppsmithRole;
|
||||
import com.appsmith.server.acl.RoleGraph;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.InviteUser;
|
||||
|
|
@ -9,6 +11,7 @@ import com.appsmith.server.domains.LoginSource;
|
|||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.PasswordResetToken;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.domains.UserRole;
|
||||
import com.appsmith.server.dtos.InviteUsersDTO;
|
||||
import com.appsmith.server.dtos.ResetUserPasswordDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
|
|
@ -44,8 +47,8 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_ORGANIZATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.USER_MANAGE_ORGANIZATIONS;
|
||||
|
||||
@Slf4j
|
||||
|
|
@ -62,6 +65,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
private final PolicyUtils policyUtils;
|
||||
private final OrganizationRepository organizationRepository;
|
||||
private final UserOrganizationService userOrganizationService;
|
||||
private final RoleGraph roleGraph;
|
||||
|
||||
private static final String WELCOME_USER_EMAIL_TEMPLATE = "email/welcomeUserTemplate.html";
|
||||
private static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "email/forgotPasswordTemplate.html";
|
||||
|
|
@ -87,7 +91,8 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
ApplicationRepository applicationRepository,
|
||||
PolicyUtils policyUtils,
|
||||
OrganizationRepository organizationRepository,
|
||||
UserOrganizationService userOrganizationService) {
|
||||
UserOrganizationService userOrganizationService,
|
||||
RoleGraph roleGraph) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.organizationService = organizationService;
|
||||
this.analyticsService = analyticsService;
|
||||
|
|
@ -99,6 +104,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
this.policyUtils = policyUtils;
|
||||
this.organizationRepository = organizationRepository;
|
||||
this.userOrganizationService = userOrganizationService;
|
||||
this.roleGraph = roleGraph;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -567,11 +573,23 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
return Flux.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ROLE));
|
||||
}
|
||||
|
||||
Mono<Organization> organizationMono = organizationRepository.findById(inviteUsersDTO.getOrgId(), MANAGE_ORGANIZATIONS)
|
||||
Mono<User> currentUserMono = sessionUserService.getCurrentUser().cache();
|
||||
|
||||
// Check if the current user has invite permissions
|
||||
Mono<Organization> organizationMono = organizationRepository.findById(inviteUsersDTO.getOrgId(), ORGANIZATION_INVITE_USERS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, inviteUsersDTO.getOrgId())))
|
||||
.zipWith(currentUserMono)
|
||||
.flatMap(tuple -> {
|
||||
Organization organization = tuple.getT1();
|
||||
User currentUser = tuple.getT2();
|
||||
|
||||
// This code segment checks if the current user can invite for the invited role.
|
||||
|
||||
return isUserPermittedToInviteForGivenRole(organization, currentUser.getUsername(), inviteUsersDTO.getRoleName())
|
||||
.thenReturn(organization);
|
||||
})
|
||||
.cache();
|
||||
|
||||
Mono<User> currentUserMono = sessionUserService.getCurrentUser();
|
||||
|
||||
// Check if the invited user exists. If yes, return the user, else create a new user by triggering
|
||||
// createNewUserAndSendInviteEmail. In both the cases, send the appropriate emails
|
||||
|
|
@ -684,4 +702,35 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
});
|
||||
}
|
||||
|
||||
private Mono<Boolean> isUserPermittedToInviteForGivenRole(Organization organization, String username, String invitedRoleName) {
|
||||
List<UserRole> userRoles = organization.getUserRoles();
|
||||
|
||||
// The current organization has no members. Clearly the current user is also not present
|
||||
if (userRoles == null || userRoles.isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.ACTION_IS_NOT_AUTHORIZED));
|
||||
}
|
||||
|
||||
Optional<UserRole> optionalUserRole = userRoles.stream().filter(role -> role.getUsername().equals(username)).findFirst();
|
||||
// If the current user is not present in the organization, the user would also not be permitted to invite
|
||||
if (!optionalUserRole.isPresent()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.ACTION_IS_NOT_AUTHORIZED));
|
||||
}
|
||||
|
||||
UserRole currentUserRole = optionalUserRole.get();
|
||||
String currentUserRoleName = currentUserRole.getRoleName();
|
||||
|
||||
AppsmithRole invitedRole = AppsmithRole.generateAppsmithRoleFromName(invitedRoleName);
|
||||
|
||||
// Generate all the roles for which the current user can invite other users
|
||||
Set<AppsmithRole> appsmithRoles = roleGraph.generateHierarchicalRoles(currentUserRoleName);
|
||||
|
||||
// If the role for which users are being invited is not in the list of permissible roles that the
|
||||
// current user can invite for, throw an error
|
||||
if (!appsmithRoles.contains(invitedRole)) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.ACTION_IS_NOT_AUTHORIZED));
|
||||
}
|
||||
|
||||
return Mono.just(Boolean.TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.appsmith.server.configurations;
|
||||
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.acl.AppsmithRole;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.OrganizationPlugin;
|
||||
|
|
@ -8,6 +9,7 @@ import com.appsmith.server.domains.Page;
|
|||
import com.appsmith.server.domains.Plugin;
|
||||
import com.appsmith.server.domains.PluginType;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.domains.UserRole;
|
||||
import com.appsmith.server.domains.UserState;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.OrganizationRepository;
|
||||
|
|
@ -32,6 +34,7 @@ import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
|
|||
import static com.appsmith.server.acl.AclPermission.MANAGE_ORGANIZATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
|
||||
import static com.appsmith.server.acl.AclPermission.MANAGE_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_MANAGE_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
|
||||
import static com.appsmith.server.acl.AclPermission.READ_ORGANIZATIONS;
|
||||
|
|
@ -72,6 +75,10 @@ public class SeedMongoData {
|
|||
.users(Set.of(API_USER_EMAIL, TEST_USER_EMAIL))
|
||||
.build();
|
||||
|
||||
Policy inviteUserOrgPolicy = Policy.builder().permission(ORGANIZATION_INVITE_USERS.getValue())
|
||||
.users(Set.of(API_USER_EMAIL))
|
||||
.build();
|
||||
|
||||
Policy managePagePolicy = Policy.builder().permission(MANAGE_PAGES.getValue())
|
||||
.users(Set.of(API_USER_EMAIL))
|
||||
.build();
|
||||
|
|
@ -106,9 +113,9 @@ public class SeedMongoData {
|
|||
};
|
||||
Object[][] orgData = {
|
||||
{"Spring Test Organization", "appsmith-spring-test.com", "appsmith.com", "spring-test-organization",
|
||||
Set.of(manageOrgAppPolicy, manageOrgPolicy, readOrgPolicy)},
|
||||
Set.of(manageOrgAppPolicy, manageOrgPolicy, readOrgPolicy, inviteUserOrgPolicy)},
|
||||
{"Another Test Organization", "appsmith-another-test.com", "appsmith.com", "another-test-organization",
|
||||
Set.of(manageOrgAppPolicy, manageOrgPolicy, readOrgPolicy)}
|
||||
Set.of(manageOrgAppPolicy, manageOrgPolicy, readOrgPolicy, inviteUserOrgPolicy)}
|
||||
};
|
||||
|
||||
Object[][] appData = {
|
||||
|
|
@ -173,6 +180,17 @@ public class SeedMongoData {
|
|||
List<OrganizationPlugin> orgPlugins = new ArrayList<>();
|
||||
orgPlugins.add(orgPlugin);
|
||||
organization.setPlugins(orgPlugins);
|
||||
|
||||
List<UserRole> userRoles = new ArrayList<>();
|
||||
UserRole userRole = new UserRole();
|
||||
String roleName = "Administrator";
|
||||
userRole.setRole(AppsmithRole.generateAppsmithRoleFromName(roleName));
|
||||
userRole.setUsername(API_USER_EMAIL);
|
||||
userRole.setRoleName(roleName);
|
||||
userRoles.add(userRole);
|
||||
organization.setUserRoles(userRoles);
|
||||
|
||||
|
||||
log.debug("In the orgFlux. Create Organization: {}", organization);
|
||||
return organization;
|
||||
}).flatMap(organizationRepository::save)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.appsmith.server.services;
|
|||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.acl.AppsmithRole;
|
||||
import com.appsmith.server.acl.RoleGraph;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.Datasource;
|
||||
|
|
@ -76,6 +77,9 @@ public class OrganizationServiceTest {
|
|||
@Autowired
|
||||
DatasourceRepository datasourceRepository;
|
||||
|
||||
@Autowired
|
||||
RoleGraph roleGraph;
|
||||
|
||||
Organization organization;
|
||||
|
||||
@Before
|
||||
|
|
@ -248,8 +252,10 @@ public class OrganizationServiceTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void getAllUserRolesForOrganizationDomain() {
|
||||
Mono<Map<String, String>> userRolesForOrganization = organizationService.getUserRolesForOrganization();
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void getAllUserRolesForOrganizationDomainAsAdministrator() {
|
||||
Mono<Map<String, String>> userRolesForOrganization = organizationService.create(organization)
|
||||
.flatMap(createdOrg -> organizationService.getUserRolesForOrganization(createdOrg.getId()));
|
||||
|
||||
StepVerifier.create(userRolesForOrganization)
|
||||
.assertNext(roles -> {
|
||||
|
|
@ -319,7 +325,7 @@ public class OrganizationServiceTest {
|
|||
|
||||
assertThat(org).isNotNull();
|
||||
assertThat(org.getName()).isEqualTo("Spring Test Organization");
|
||||
assertThat(org.getUserRoles().get(0).getUsername()).isEqualTo("usertest@usertest.com");
|
||||
assertThat(org.getUserRoles().get(1).getUsername()).isEqualTo("usertest@usertest.com");
|
||||
|
||||
Policy manageOrgAppPolicy = Policy.builder().permission(ORGANIZATION_MANAGE_APPLICATIONS.getValue())
|
||||
.users(Set.of("api_user", "usertest@usertest.com"))
|
||||
|
|
@ -874,4 +880,50 @@ public class OrganizationServiceTest {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void inviteRolesGivenAdministrator() {
|
||||
Set<AppsmithRole> roles = roleGraph.generateHierarchicalRoles("Administrator");
|
||||
AppsmithRole administratorRole = AppsmithRole.generateAppsmithRoleFromName("Administrator");
|
||||
AppsmithRole developerRole = AppsmithRole.generateAppsmithRoleFromName("Developer");
|
||||
AppsmithRole viewerRole = AppsmithRole.generateAppsmithRoleFromName("App Viewer");
|
||||
|
||||
StepVerifier.create(Mono.just(roles))
|
||||
.assertNext(appsmithRoles -> {
|
||||
assertThat(appsmithRoles).isNotNull();
|
||||
assertThat(appsmithRoles).containsAll(Set.of(administratorRole, developerRole, viewerRole));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void inviteRolesGivenDeveloper() {
|
||||
Set<AppsmithRole> roles = roleGraph.generateHierarchicalRoles("Developer");
|
||||
AppsmithRole developerRole = AppsmithRole.generateAppsmithRoleFromName("Developer");
|
||||
AppsmithRole viewerRole = AppsmithRole.generateAppsmithRoleFromName("App Viewer");
|
||||
|
||||
StepVerifier.create(Mono.just(roles))
|
||||
.assertNext(appsmithRoles -> {
|
||||
assertThat(appsmithRoles).isNotNull();
|
||||
assertThat(appsmithRoles).containsAll(Set.of(developerRole, viewerRole));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void inviteRolesGivenViewer() {
|
||||
Set<AppsmithRole> roles = roleGraph.generateHierarchicalRoles("App Viewer");
|
||||
AppsmithRole viewerRole = AppsmithRole.generateAppsmithRoleFromName("App Viewer");
|
||||
|
||||
StepVerifier.create(Mono.just(roles))
|
||||
.assertNext(appsmithRoles -> {
|
||||
assertThat(appsmithRoles).isNotNull();
|
||||
assertThat(appsmithRoles).hasSize(1);
|
||||
assertThat(appsmithRoles).containsAll(Set.of(viewerRole));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user