Merge branch 'release' of https://github.com/appsmithorg/appsmith into release

This commit is contained in:
Trisha Anand 2020-08-12 13:24:11 +05:30
commit 245721deb9
18 changed files with 349 additions and 51 deletions

View File

@ -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);
},

View File

@ -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"

View File

@ -35,6 +35,7 @@ export const getRolesForField = createSelector(getAllRoles, (roles?: any) => {
return {
id: role[0],
name: role[0],
description: role[1],
};
});
});

View File

@ -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),

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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));
}

View File

@ -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"),

View File

@ -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);
}
}
}
}

View File

@ -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;
});

View File

@ -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 -> {

View File

@ -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);
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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();
}
}