Invite existing user to an organization works. Updated the test case for adding user to an organization as well to use the new API.
This commit is contained in:
parent
b0b72d965c
commit
f8cf87e3c8
|
|
@ -1,11 +1,10 @@
|
|||
package com.appsmith.server.controllers;
|
||||
|
||||
import com.appsmith.server.constants.Url;
|
||||
import com.appsmith.server.domains.InviteUser;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.InviteUserDTO;
|
||||
import com.appsmith.server.dtos.ResetUserPasswordDTO;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.dtos.UserProfileDTO;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.UserOrganizationService;
|
||||
import com.appsmith.server.services.UserService;
|
||||
|
|
@ -47,7 +46,7 @@ public class UserController extends BaseController<UserService, User, String> {
|
|||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<User>> create(@Valid @RequestBody User resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
return service.createUser(resource, originHeader)
|
||||
return service.createUserAndSendEmail(resource, originHeader)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
}
|
||||
|
||||
|
|
@ -98,26 +97,6 @@ public class UserController extends BaseController<UserService, User, String> {
|
|||
.map(user -> new ResponseDTO<>(HttpStatus.OK.value(), user, null));
|
||||
}
|
||||
|
||||
@GetMapping("/profile")
|
||||
public Mono<ResponseDTO<UserProfileDTO>> getEnhancedUserProfile() {
|
||||
return service.getUserProfile()
|
||||
.map(user -> new ResponseDTO<>(HttpStatus.OK.value(), user, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates an invite for a new user to join the Appsmith platform. We require the Origin header
|
||||
* in order to construct client facing URLs that will be sent to the user via email.
|
||||
*
|
||||
* @param user The user object for the new user being invited to the Appsmith platform
|
||||
* @param originHeader Origin header in the request
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/invite")
|
||||
public Mono<ResponseDTO<User>> inviteUser(@RequestBody User user, @RequestHeader("Origin") String originHeader) {
|
||||
return service.inviteUser(user, originHeader)
|
||||
.map(resUser -> new ResponseDTO<>(HttpStatus.OK.value(), resUser, null));
|
||||
}
|
||||
|
||||
@GetMapping("/invite/verify")
|
||||
public Mono<ResponseDTO<Boolean>> verifyInviteToken(@RequestParam String email, @RequestParam String token) {
|
||||
return service.verifyInviteToken(email, token)
|
||||
|
|
@ -125,9 +104,23 @@ public class UserController extends BaseController<UserService, User, String> {
|
|||
}
|
||||
|
||||
@PutMapping("/invite/confirm")
|
||||
public Mono<ResponseDTO<Boolean>> confirmInviteUser(@RequestBody InviteUser inviteUser,
|
||||
public Mono<ResponseDTO<Boolean>> confirmInviteUser(@RequestBody User inviteUser,
|
||||
@RequestHeader("Origin") String originHeader) {
|
||||
return service.confirmInviteUser(inviteUser, originHeader)
|
||||
.map(result -> new ResponseDTO<>(HttpStatus.OK.value(), result, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates an invite for a new user to join the Appsmith platform. We require the Origin header
|
||||
* in order to construct client facing URLs that will be sent to the user via email.
|
||||
*
|
||||
* @param inviteUserDTO The inviteUserDto object for the new user being invited to the Appsmith platform
|
||||
* @param originHeader Origin header in the request
|
||||
* @return The new user who has been created.
|
||||
*/
|
||||
@PostMapping("/invite")
|
||||
public Mono<ResponseDTO<User>> inviteUserNew(@RequestBody InviteUserDTO inviteUserDTO, @RequestHeader("Origin") String originHeader) {
|
||||
return service.inviteUser(inviteUserDTO, originHeader)
|
||||
.map(resUser -> new ResponseDTO<>(HttpStatus.OK.value(), resUser, null));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.appsmith.external.models.BaseDomain;
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
|
|
@ -55,6 +54,11 @@ public class User extends BaseDomain implements UserDetails {
|
|||
// During evaluation a union of the group permissions and user-specific permissions will take effect.
|
||||
private Set<String> permissions = new HashSet<>();
|
||||
|
||||
// This field is used when a user is invited to appsmith. This inviteToken is used to confirm the identity in verify
|
||||
// token flow.
|
||||
@JsonIgnore
|
||||
private String inviteToken;
|
||||
|
||||
@Transient
|
||||
Boolean isAnonymous = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.appsmith.server.dtos;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class InviteUserDTO {
|
||||
String email;
|
||||
String roleName;
|
||||
String orgId;
|
||||
}
|
||||
|
|
@ -39,7 +39,10 @@ public class UserOrganizationServiceImpl implements UserOrganizationService {
|
|||
|
||||
@Autowired
|
||||
public UserOrganizationServiceImpl(SessionUserService sessionUserService,
|
||||
OrganizationRepository organizationRepository, UserRepository userRepository, PolicyUtils policyUtils) {
|
||||
OrganizationRepository organizationRepository,
|
||||
UserRepository userRepository,
|
||||
PolicyUtils policyUtils
|
||||
) {
|
||||
this.sessionUserService = sessionUserService;
|
||||
this.organizationRepository = organizationRepository;
|
||||
this.userRepository = userRepository;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.appsmith.server.services;
|
|||
|
||||
import com.appsmith.server.domains.InviteUser;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.InviteUserDTO;
|
||||
import com.appsmith.server.dtos.ResetUserPasswordDTO;
|
||||
import com.appsmith.server.dtos.UserProfileDTO;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface UserService extends CrudService<User, String> {
|
||||
|
|
@ -20,13 +20,13 @@ public interface UserService extends CrudService<User, String> {
|
|||
|
||||
Mono<User> inviteUserToApplication(InviteUser inviteUser, String originHeader, String applicationId);
|
||||
|
||||
Mono<User> inviteUser(User user, String originHeader);
|
||||
|
||||
Mono<Boolean> verifyInviteToken(String email, String token);
|
||||
|
||||
Mono<Boolean> confirmInviteUser(InviteUser inviteUser, String originHeader);
|
||||
Mono<Boolean> confirmInviteUser(User inviteUser, String originHeader);
|
||||
|
||||
Mono<UserProfileDTO> getUserProfile();
|
||||
Mono<User> createUserAndSendEmail(User user, String originHeader);
|
||||
|
||||
Mono<User> createUser(User user, String originHeader);
|
||||
Mono<User> userCreate(User user);
|
||||
|
||||
Mono<User> inviteUser(InviteUserDTO inviteUserDTO, String originHeader);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,21 +10,20 @@ 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.dtos.ApplicationNameIdDTO;
|
||||
import com.appsmith.server.domains.UserRole;
|
||||
import com.appsmith.server.dtos.InviteUserDTO;
|
||||
import com.appsmith.server.dtos.ResetUserPasswordDTO;
|
||||
import com.appsmith.server.dtos.UserProfileDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.BeanCopyUtils;
|
||||
import com.appsmith.server.helpers.PolicyUtils;
|
||||
import com.appsmith.server.notifications.EmailSender;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.InviteUserRepository;
|
||||
import com.appsmith.server.repositories.OrganizationRepository;
|
||||
import com.appsmith.server.repositories.PasswordResetTokenRepository;
|
||||
import com.appsmith.server.repositories.UserRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
|
|
@ -46,8 +45,10 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
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.RESET_PASSWORD_USERS;
|
||||
import static com.appsmith.server.acl.AclPermission.USER_MANAGE_ORGANIZATIONS;
|
||||
|
|
@ -63,17 +64,18 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
private final PasswordResetTokenRepository passwordResetTokenRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final EmailSender emailSender;
|
||||
private final InviteUserRepository inviteUserRepository;
|
||||
private final UserOrganizationService userOrganizationService;
|
||||
private final ApplicationRepository applicationRepository;
|
||||
private final PolicyGenerator policyGenerator;
|
||||
private final PolicyUtils policyUtils;
|
||||
private final OrganizationRepository organizationRepository;
|
||||
private final UserOrganizationService userOrganizationService;
|
||||
|
||||
private static final String WELCOME_USER_EMAIL_TEMPLATE = "email/welcomeUserTemplate.html";
|
||||
private static final String INVITE_USER_EMAIL_TEMPLATE = "email/inviteUserCreatorTemplate.html";
|
||||
private static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "email/forgotPasswordTemplate.html";
|
||||
private static final String INVITE_USER_CLIENT_URL_FORMAT = "%s/user/createPassword?token=%s&email=%s";
|
||||
private static final String FORGOT_PASSWORD_CLIENT_URL_FORMAT = "%s/user/resetPassword?token=%s&email=%s";
|
||||
private static final String INVITE_USER_CLIENT_URL_FORMAT = "%s/user/createPassword?token=%s&email=%s";
|
||||
private static final String INVITE_USER_EMAIL_TEMPLATE = "email/inviteUserCreatorTemplate.html";
|
||||
private static final String USER_ADDED_TO_ORGANIZATION_EMAIL_TEMPLATE = "email/inviteExistingUserToOrganizationTemplate.html";
|
||||
// We default the origin header to the production deployment of the client's URL
|
||||
private static final String DEFAULT_ORIGIN_HEADER = "https://app.appsmith.com";
|
||||
|
||||
|
|
@ -89,10 +91,8 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
PasswordResetTokenRepository passwordResetTokenRepository,
|
||||
PasswordEncoder passwordEncoder,
|
||||
EmailSender emailSender,
|
||||
InviteUserRepository inviteUserRepository,
|
||||
UserOrganizationService userOrganizationService,
|
||||
ApplicationRepository applicationRepository,
|
||||
PolicyGenerator policyGenerator, PolicyUtils policyUtils) {
|
||||
PolicyGenerator policyGenerator, PolicyUtils policyUtils, OrganizationRepository organizationRepository, UserOrganizationService userOrganizationService) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.repository = repository;
|
||||
this.organizationService = organizationService;
|
||||
|
|
@ -101,11 +101,11 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
this.passwordResetTokenRepository = passwordResetTokenRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.emailSender = emailSender;
|
||||
this.inviteUserRepository = inviteUserRepository;
|
||||
this.userOrganizationService = userOrganizationService;
|
||||
this.applicationRepository = applicationRepository;
|
||||
this.policyGenerator = policyGenerator;
|
||||
this.policyUtils = policyUtils;
|
||||
this.organizationRepository = organizationRepository;
|
||||
this.userOrganizationService = userOrganizationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -319,7 +319,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
if (newUser.getId() == null) {
|
||||
// The user is not a part of the Appsmith ecosystem. Create an invite token for the user and send an email
|
||||
// TODO: Check if we can still add the user details to the application policies.
|
||||
handleNewUserInvite(inviteUser, application);
|
||||
// TODO : create new user and then add the user to the application
|
||||
}
|
||||
|
||||
Set<AclPermission> invitePermissions = inviteUser.getRole().getPermissions();
|
||||
|
|
@ -346,88 +346,6 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
return userMono;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO : Wire up new user invite : Invite the user to the appmsith world with the email covering the following info
|
||||
* 1. User being invited
|
||||
* 2. User who invited
|
||||
* 3. Application to which the user has been added
|
||||
*
|
||||
* @param inviteUser
|
||||
* @param application
|
||||
* @return
|
||||
*/
|
||||
private User handleNewUserInvite(InviteUser inviteUser, Application application) {
|
||||
return inviteUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates an invite for a user by generating a unique token and then sending him/her a notification
|
||||
* requesting them to sign in.
|
||||
*
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<User> inviteUser(User user, String originHeader) {
|
||||
if (originHeader == null || originHeader.isBlank()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORIGIN));
|
||||
}
|
||||
|
||||
// Create an invite token for the user. This token is linked to the email ID and the organization to which the
|
||||
// user was invited.
|
||||
String token = UUID.randomUUID().toString();
|
||||
|
||||
// Caching the response from sessionUserService because it's re-used multiple times in this flow
|
||||
Mono<User> currentUserMono = sessionUserService.getCurrentUser().cache();
|
||||
Mono<InviteUser> inviteUserMono = currentUserMono
|
||||
.map(currentUser -> {
|
||||
log.debug("Got request to invite user {} by user: {} for org: {}",
|
||||
user.getEmail(), currentUser.getEmail(), currentUser.getCurrentOrganizationId());
|
||||
|
||||
InviteUser inviteUser = new InviteUser();
|
||||
inviteUser.setEmail(user.getEmail());
|
||||
inviteUser.setCurrentOrganizationId(currentUser.getCurrentOrganizationId());
|
||||
inviteUser.setToken(passwordEncoder.encode(token));
|
||||
inviteUser.setGroupIds(user.getGroupIds());
|
||||
inviteUser.setPermissions(user.getPermissions());
|
||||
inviteUser.setInviterUserId(currentUser.getId());
|
||||
return inviteUser;
|
||||
})
|
||||
// Save the invited user in the DB
|
||||
.flatMap(inviteUserRepository::save);
|
||||
|
||||
Mono<Organization> currentOrgMono = currentUserMono
|
||||
.flatMap(currentUser -> organizationService.findById(currentUser.getCurrentOrganizationId()));
|
||||
|
||||
// Send an email to the invited user with the token
|
||||
return Mono.zip(currentUserMono, inviteUserMono, currentOrgMono)
|
||||
.map(tuple -> {
|
||||
User currentUser = tuple.getT1();
|
||||
InviteUser inviteUser = tuple.getT2();
|
||||
Organization currentUserOrg = tuple.getT3();
|
||||
log.debug("Going to send email for invite user to {} with token {}", inviteUser.getEmail(), token);
|
||||
try {
|
||||
String inviteUrl = String.format(INVITE_USER_CLIENT_URL_FORMAT, originHeader,
|
||||
URLEncoder.encode(token, StandardCharsets.UTF_8),
|
||||
URLEncoder.encode(inviteUser.getEmail(), StandardCharsets.UTF_8));
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("token", token);
|
||||
params.put("inviteUrl", inviteUrl);
|
||||
if (!StringUtils.isEmpty(currentUser.getName())) {
|
||||
params.put("Inviter_First_Name", currentUser.getName());
|
||||
} else {
|
||||
params.put("Inviter_First_Name", currentUser.getEmail());
|
||||
}
|
||||
params.put("inviter_org_name", currentUserOrg.getName());
|
||||
String emailBody = emailSender.replaceEmailTemplate(INVITE_USER_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(inviteUser.getEmail(), "Invite for Appsmith", emailBody);
|
||||
} catch (IOException e) {
|
||||
log.error("Unable to send invite user email to {}. Cause: ", inviteUser.getEmail(), e);
|
||||
}
|
||||
return inviteUser;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if the inviteToken is valid for the user. If the token is incorrect or it as expired,
|
||||
* the client should show the appropriate message to the user
|
||||
|
|
@ -439,25 +357,22 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
@Override
|
||||
public Mono<Boolean> verifyInviteToken(String email, String token) {
|
||||
log.debug("Verifying token: {} for email: {}", token, email);
|
||||
return inviteUserRepository.findByEmail(email)
|
||||
return repository.findByEmail(email)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "email", email)))
|
||||
.flatMap(inviteUser -> passwordEncoder.matches(token, inviteUser.getToken()) ?
|
||||
.flatMap(inviteUser -> passwordEncoder.matches(token, inviteUser.getInviteToken()) ?
|
||||
Mono.just(true) : Mono.just(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function confirms the signup for a new invited user. Primarily it will be used to set the password
|
||||
* for the user
|
||||
* for the user and set the user to enabled. The user should have been created during the invite flow.
|
||||
*
|
||||
* @param inviteUser
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> confirmInviteUser(InviteUser inviteUser, String originHeader) {
|
||||
if (inviteUser.getToken() == null || inviteUser.getToken().isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "token"));
|
||||
}
|
||||
|
||||
public Mono<Boolean> confirmInviteUser(User inviteUser, String originHeader) {
|
||||
|
||||
if (inviteUser.getEmail() == null || inviteUser.getEmail().isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "email"));
|
||||
}
|
||||
|
|
@ -466,44 +381,30 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "password"));
|
||||
}
|
||||
|
||||
log.debug("Confirming the signup for the user: {} and token: {}", inviteUser.getEmail(), inviteUser.getToken());
|
||||
log.debug("Confirming the signup for the user: {} and token: {}", inviteUser.getEmail(), inviteUser.getInviteToken());
|
||||
|
||||
Mono<InviteUser> inviteUserMono = inviteUserRepository.findByEmail(inviteUser.getEmail())
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "email", inviteUser.getEmail())));
|
||||
inviteUser.setPassword(this.passwordEncoder.encode(inviteUser.getPassword()));
|
||||
|
||||
// If the email Id is not found in the users collection, it means this is a new user. We still want the mono to emit
|
||||
// so that the flow can continue. Hence, returning empty user object.
|
||||
Mono<User> userMono = repository.findByEmail(inviteUser.getEmail(), RESET_PASSWORD_USERS)
|
||||
.switchIfEmpty(Mono.just(new User()));
|
||||
return repository.findByEmail(inviteUser.getEmail())
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "email", inviteUser.getEmail())))
|
||||
.flatMap(newUser -> {
|
||||
|
||||
return Mono.zip(inviteUserMono, userMono, (newUser, user) -> {
|
||||
if (user.getId() != null) {
|
||||
// The user already exists in the system. We simply need to append to their current user object
|
||||
log.debug("The user already exists in the system with id: {}", user.getId());
|
||||
user.getGroupIds().addAll(newUser.getGroupIds());
|
||||
user.getPermissions().addAll(newUser.getPermissions());
|
||||
return repository.save(user)
|
||||
.flatMap(savedUser -> userOrganizationService.addUserToOrganization(newUser.getCurrentOrganizationId(), savedUser))
|
||||
.thenReturn(newUser)
|
||||
.flatMap(userToDelete -> inviteUserRepository.delete(userToDelete))
|
||||
// Activate the user now :
|
||||
newUser.setIsEnabled(true);
|
||||
newUser.setPassword(inviteUser.getPassword());
|
||||
// The user has now been invited and has signed up. Delete the invite token because its no longer required
|
||||
newUser.setInviteToken(null);
|
||||
|
||||
return repository.save(newUser)
|
||||
.map(savedUser -> sendWelcomeEmail(savedUser, originHeader))
|
||||
.thenReturn(true);
|
||||
}
|
||||
log.debug("The invited user {} doesn't exist in the system. Creating a new record", inviteUser.getEmail());
|
||||
// The user doesn't exist in the system. Create a new user object
|
||||
newUser.setPassword(inviteUser.getPassword());
|
||||
String invitedOrganizationId = newUser.getCurrentOrganizationId();
|
||||
return this.createUser(newUser, originHeader)
|
||||
.flatMap(createdUser -> userOrganizationService.addUserToOrganization(invitedOrganizationId, createdUser))
|
||||
.thenReturn(newUser)
|
||||
.flatMap(userToDelete -> inviteUserRepository.delete(userToDelete))
|
||||
.thenReturn(true);
|
||||
}).flatMap(result -> result);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> create(User user) {
|
||||
// This is the path that is taken when a new user signs up on its own
|
||||
return createUser(user, null);
|
||||
return createUserAndSendEmail(user, null);
|
||||
}
|
||||
|
||||
private Set<Policy> crudUserPolicy(User user) {
|
||||
|
|
@ -515,26 +416,11 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
return new HashSet<>(userPolicies.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates a new user in the system. Primarily used by new users signing up for the first time on the
|
||||
* platform. This flow also ensures that a personal workspace name is created for the user. The new user is then
|
||||
* given admin permissions to the personal workspace.
|
||||
* <p>
|
||||
* For new user invite flow, please {@link UserService#inviteUser(User user, String originHeader)}
|
||||
*
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<User> createUser(User user, String originHeader) {
|
||||
if (originHeader == null || originHeader.isBlank()) {
|
||||
// Default to the production link
|
||||
originHeader = DEFAULT_ORIGIN_HEADER;
|
||||
}
|
||||
final String finalOriginHeader = originHeader;
|
||||
public Mono<User> userCreate(User user) {
|
||||
|
||||
// Only encode the password if it's a form signup. For OAuth signups, we don't need password
|
||||
if (LoginSource.FORM.equals(user.getSource())) {
|
||||
if (user.getIsEnabled() && LoginSource.FORM.equals(user.getSource())) {
|
||||
if (user.getPassword() == null || user.getPassword().isBlank()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_CREDENTIALS));
|
||||
}
|
||||
|
|
@ -567,24 +453,45 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
return organizationService.create(personalOrg, savedUser)
|
||||
.thenReturn(savedUser);
|
||||
})
|
||||
.map(savedUser -> {
|
||||
// Send an email to the user welcoming them to the Appsmith platform
|
||||
try {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("personalOrganizationName", personalOrganizationName);
|
||||
params.put("firstName", savedUser.getName());
|
||||
params.put("appsmithLink", finalOriginHeader);
|
||||
String emailBody = emailSender.replaceEmailTemplate(WELCOME_USER_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(savedUser.getEmail(), "Welcome to Appsmith", emailBody);
|
||||
} catch (IOException e) {
|
||||
// Catching and swallowing this exception because we don't want this to affect the rest of the flow
|
||||
log.error("Unable to send welcome email to the user {}. Cause: ", savedUser.getEmail(), e);
|
||||
}
|
||||
return savedUser;
|
||||
})
|
||||
.flatMap(analyticsService::trackNewUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates a new user in the system. Primarily used by new users signing up for the first time on the
|
||||
* platform. This flow also ensures that a personal workspace name is created for the user. The new user is then
|
||||
* given admin permissions to the personal workspace.
|
||||
* <p>
|
||||
* For new user invite flow, please {@link UserOrganizationService#inviteUserNew(InviteUserDTO, String)}
|
||||
*
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<User> createUserAndSendEmail(User user, String originHeader) {
|
||||
if (originHeader == null || originHeader.isBlank()) {
|
||||
// Default to the production link
|
||||
originHeader = DEFAULT_ORIGIN_HEADER;
|
||||
}
|
||||
final String finalOriginHeader = originHeader;
|
||||
|
||||
return userCreate(user)
|
||||
.map(savedUser -> sendWelcomeEmail(savedUser, finalOriginHeader));
|
||||
}
|
||||
|
||||
public User sendWelcomeEmail(User user, String originHeader) {
|
||||
try {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("firstName", user.getName());
|
||||
params.put("appsmithLink", originHeader);
|
||||
String emailBody = emailSender.replaceEmailTemplate(WELCOME_USER_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(user.getEmail(), "Welcome to Appsmith", emailBody);
|
||||
} catch (IOException e) {
|
||||
// Catching and swallowing this exception because we don't want this to affect the rest of the flow
|
||||
log.error("Unable to send welcome email to the user {}. Cause: ", user.getEmail(), e);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> update(String id, User userUpdate) {
|
||||
Mono<User> userFromRepository = repository.findById(id, MANAGE_USERS)
|
||||
|
|
@ -595,10 +502,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
userUpdate.setPassword(passwordEncoder.encode(userUpdate.getPassword()));
|
||||
}
|
||||
|
||||
return Mono.just(userUpdate)
|
||||
.flatMap(this::validateUpdate)
|
||||
//Once the new update has been validated, update the user with the new fields.
|
||||
.then(userFromRepository)
|
||||
return userFromRepository
|
||||
.map(existingUser -> {
|
||||
BeanCopyUtils.copyNewFieldValuesIntoOldObject(userUpdate, existingUser);
|
||||
return existingUser;
|
||||
|
|
@ -606,20 +510,6 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
.flatMap(repository::save);
|
||||
}
|
||||
|
||||
//Validation for user update. Right now it only validates the organization id. Other checks can be added
|
||||
//here in the future.
|
||||
private Mono<User> validateUpdate(User updateUser) {
|
||||
if (updateUser.getCurrentOrganizationId() == null) {
|
||||
//No organization present implies the update to the user is not to the organization id. No checks currently
|
||||
//for this scenario. Return the user successfully.
|
||||
return Mono.just(updateUser);
|
||||
}
|
||||
return organizationService.findById(updateUser.getCurrentOrganizationId())
|
||||
//If the organization is not found in the repository, throw an error
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, updateUser.getCurrentOrganizationId())))
|
||||
.then(Mono.just(updateUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used by {@link ReactiveUserDetailsService} in order to load the user from the DB. Will be used
|
||||
* in cases of username, password logins only. By default, the email ID is the username for the user.
|
||||
|
|
@ -636,37 +526,142 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
.map(user -> (UserDetails) user);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO :
|
||||
* 1. User doesn't exist :
|
||||
* a. Create a new user.
|
||||
* b. Set isEnabled to false
|
||||
* c. Generate a token. Send out an email informing the user to sign up with token.
|
||||
* d. Follow the steps for User which already exists
|
||||
* 2. User exists :
|
||||
* a. Add user to the organization
|
||||
* b. Add organization to the user
|
||||
*
|
||||
* Imp : In case of new user : Confirm token function should change to verify and then set isEnabled to true. Don't
|
||||
* allow login of non enabled users.
|
||||
*/
|
||||
@Override
|
||||
public Mono<UserProfileDTO> getUserProfile() {
|
||||
return sessionUserService.getCurrentUser()
|
||||
.flatMap(user -> {
|
||||
if (user.getIsAnonymous()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.USER_NOT_SIGNED_IN));
|
||||
public Mono<User> inviteUser(InviteUserDTO inviteUserDTO, String originHeader) {
|
||||
|
||||
if (originHeader == null || originHeader.isBlank()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORIGIN));
|
||||
}
|
||||
|
||||
// This variable will be used to decide if an email should be sent to get a user to sign up for appsmith or the
|
||||
// email would inform the user that the user has been invited to a new organization
|
||||
AtomicBoolean userExisted = new AtomicBoolean(true);
|
||||
|
||||
// If the invited user doesn't exist, create a new user.
|
||||
Mono<User> createNewUserMono = Mono.just(inviteUserDTO)
|
||||
.flatMap(dto -> {
|
||||
User newUser = new User();
|
||||
newUser.setEmail(dto.getEmail());
|
||||
// This is a new user. Till the user signs up, this user would be disabled.
|
||||
newUser.setIsEnabled(false);
|
||||
userExisted.set(false);
|
||||
// Create an invite token for the user. This token is linked to the email ID and the organization to which the
|
||||
// user was invited.
|
||||
newUser.setInviteToken(UUID.randomUUID().toString());
|
||||
// Call user service's userCreate function so that the personal organization, etc are also created along with assigning basic permissions.
|
||||
return userCreate(newUser);
|
||||
});
|
||||
|
||||
// Check if the invited user exists. If yes, return the user, else create a new user by triggering the create
|
||||
// new user Mono.
|
||||
Mono<User> inviteUserMono = repository.findByEmail(inviteUserDTO.getEmail())
|
||||
.switchIfEmpty(createNewUserMono)
|
||||
.cache();
|
||||
|
||||
Mono<Organization> organizationMono = organizationRepository.findById(inviteUserDTO.getOrgId(), MANAGE_ORGANIZATIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, inviteUserDTO.getOrgId())))
|
||||
.cache();
|
||||
|
||||
Mono<User> currentUserMono = sessionUserService.getCurrentUser();
|
||||
|
||||
// Add User to the invited Organization
|
||||
Mono<Organization> organizationWithUserAddedMono = Mono.zip(inviteUserMono, organizationMono)
|
||||
.flatMap(tuple -> {
|
||||
User invitedUser = tuple.getT1();
|
||||
Organization organization = tuple.getT2();
|
||||
|
||||
UserRole userRole = new UserRole();
|
||||
userRole.setUsername(invitedUser.getUsername());
|
||||
userRole.setRoleName(inviteUserDTO.getRoleName());
|
||||
|
||||
return userOrganizationService.addUserToOrganizationGivenUserObject(organization, invitedUser, userRole);
|
||||
});
|
||||
|
||||
// Add invited Organization to the User
|
||||
Mono<User> userUpdatedWithOrgMono = Mono.zip(inviteUserMono, organizationMono)
|
||||
// zipping with organizationMono to ensure that the orgId is checked before updating the user object.
|
||||
.flatMap(tuple -> {
|
||||
User invitedUser = tuple.getT1();
|
||||
Organization organization = tuple.getT2();
|
||||
|
||||
Set<String> organizationIds = invitedUser.getOrganizationIds();
|
||||
if (organizationIds == null) {
|
||||
organizationIds = new HashSet<>();
|
||||
}
|
||||
String currentOrganizationId = user.getCurrentOrganizationId();
|
||||
UserProfileDTO userProfile = new UserProfileDTO();
|
||||
userProfile.setUser(user);
|
||||
|
||||
Mono<UserProfileDTO> userProfileDTOMono = organizationService.findById(currentOrganizationId)
|
||||
.flatMap(org -> {
|
||||
userProfile.setCurrentOrganization(org);
|
||||
organizationIds.add(organization.getId());
|
||||
invitedUser.setOrganizationIds(organizationIds);
|
||||
|
||||
Application applicationExample = new Application();
|
||||
applicationExample.setOrganizationId(org.getId());
|
||||
return applicationRepository.findAll(Example.of(applicationExample))
|
||||
.map(application -> {
|
||||
ApplicationNameIdDTO dto = new ApplicationNameIdDTO();
|
||||
dto.setId(application.getId());
|
||||
dto.setName(application.getName());
|
||||
return dto;
|
||||
}).collectList()
|
||||
.map(dtos -> {
|
||||
userProfile.setApplications(dtos);
|
||||
return userProfile;
|
||||
//Lets save the updated user object
|
||||
return repository.save(invitedUser);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
return userProfileDTOMono;
|
||||
|
||||
return Mono.zip(organizationWithUserAddedMono, userUpdatedWithOrgMono, currentUserMono)
|
||||
.map(tuple -> {
|
||||
// We reached here. This implies that both user and org got updated without any errors. Proceed forward
|
||||
// with communication (email) here.
|
||||
Organization updatedOrg = tuple.getT1();
|
||||
User updatedUser = tuple.getT2();
|
||||
User currentUser = tuple.getT3();
|
||||
|
||||
// Email template parameters initialization below.
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (!StringUtils.isEmpty(currentUser.getName())) {
|
||||
params.put("Inviter_First_Name", currentUser.getName());
|
||||
} else {
|
||||
params.put("Inviter_First_Name", currentUser.getEmail());
|
||||
}
|
||||
params.put("inviter_org_name", updatedOrg.getName());
|
||||
|
||||
if (userExisted.get()) {
|
||||
|
||||
// If the user already existed, just send an email informing that the user has been added
|
||||
// to a new organization
|
||||
log.debug("Going to send email to user {} informing that the user has been added to new organization {}",
|
||||
updatedUser.getEmail(), updatedOrg.getName());
|
||||
try {
|
||||
String inviteUrl = String.format("", originHeader);
|
||||
params.put("inviteUrl", inviteUrl);
|
||||
String emailBody = emailSender.replaceEmailTemplate(USER_ADDED_TO_ORGANIZATION_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(updatedUser.getEmail(), "Appsmith: You have been added to a new organization", emailBody);
|
||||
} catch (IOException e) {
|
||||
log.error("Unable to send invite user email to {}. Cause: ", updatedUser.getEmail(), e);
|
||||
}
|
||||
|
||||
} else {
|
||||
// The user was created and then added to the organization. Send an email to the user to sign
|
||||
// up on Appsmith platform with the token generated during create user.
|
||||
log.debug("Going to send email for invite user to {} with token {}", updatedUser.getEmail(), updatedUser.getInviteToken());
|
||||
try {
|
||||
String inviteUrl = String.format(INVITE_USER_CLIENT_URL_FORMAT, originHeader,
|
||||
URLEncoder.encode(updatedUser.getInviteToken(), StandardCharsets.UTF_8),
|
||||
URLEncoder.encode(updatedUser.getEmail(), StandardCharsets.UTF_8));
|
||||
params.put("token", updatedUser.getInviteToken());
|
||||
params.put("inviteUrl", inviteUrl);
|
||||
String emailBody = emailSender.replaceEmailTemplate(INVITE_USER_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(updatedUser.getEmail(), "Invite for Appsmith", emailBody);
|
||||
} catch (IOException e) {
|
||||
log.error("Unable to send invite user email to {}. Cause: ", updatedUser.getEmail(), e);
|
||||
}
|
||||
}
|
||||
// We have sent out the emails. Just send back the saved user.
|
||||
return updatedUser;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,299 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<!--<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<style type="text/css">
|
||||
body {
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table, td {
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
body, p, div {
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
body a {
|
||||
color: #1188E6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.wrapper {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
img.max-width {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.column.of-2 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.column.of-3 {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
.column.of-4 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.preheader .rightColumnContent,
|
||||
.footer .rightColumnContent {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.preheader .rightColumnContent div,
|
||||
.preheader .rightColumnContent span,
|
||||
.footer .rightColumnContent div,
|
||||
.footer .rightColumnContent span {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.preheader .rightColumnContent,
|
||||
.preheader .leftColumnContent {
|
||||
font-size: 80% !important;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
table.wrapper-mobile {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
img.max-width {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
a.bulletproof-button {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
font-size: 80%;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.columns {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--user entered Head Start--><!--End Head user entered-->
|
||||
</head>
|
||||
<body>
|
||||
<center class="wrapper" data-link-color="#1188E6"
|
||||
data-body-style="font-size:14px; font-family:arial,helvetica,sans-serif; color:#000000; background-color:#FFFFFF;">
|
||||
<div class="webkit">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#FFFFFF">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="top" bgcolor="#FFFFFF" width="100%">
|
||||
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0"
|
||||
cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="100%">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<!--[if mso]>
|
||||
<center>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="600">
|
||||
<![endif]-->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0"
|
||||
style="width:100%; max-width:600px;" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td role="modules-container"
|
||||
style="padding:0px 0px 0px 0px; color:#000000; text-align:left;"
|
||||
bgcolor="#ffffff" width="100%" align="left">
|
||||
<table class="module preheader preheader-hide" role="module"
|
||||
data-type="preheader" border="0" cellpadding="0"
|
||||
cellspacing="0" width="100%"
|
||||
style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td role="module-content">
|
||||
<p></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="wrapper" role="module" data-type="image"
|
||||
border="0" cellpadding="0" cellspacing="0" width="100%"
|
||||
style="table-layout: fixed;"
|
||||
data-muid="40dbb7f1-8428-4188-86b0-1b0245659a17">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:6px; line-height:10px; padding:0px 0px 0px 0px;"
|
||||
valign="top" align="center">
|
||||
<a href="https://www.appsmith.com/"><img
|
||||
class="max-width" border="0"
|
||||
style="display:block; color:#000000; text-decoration:none; font-family:Helvetica, arial, sans-serif; font-size:16px; max-width:25% !important; width:25%; height:auto !important;"
|
||||
width="150" alt=""
|
||||
data-proportionally-constrained="true"
|
||||
data-responsive="true"
|
||||
src="http://cdn.mcauto-images-production.sendgrid.net/4bbae2fffe647858/b21738f2-3a49-4774-aae9-c8e80ad9c26e/924x284.png"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="module" role="module" data-type="text" border="0"
|
||||
cellpadding="0" cellspacing="0" width="100%"
|
||||
style="table-layout: fixed;"
|
||||
data-muid="71d7e9fb-0f3b-43f4-97e1-994b33bfc82a"
|
||||
data-mc-module-version="2019-10-22">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 18px 0px; line-height:22px; text-align:inherit; background-color:#ffffff;"
|
||||
height="100%" valign="top" bgcolor="#ffffff"
|
||||
role="module-content">
|
||||
<div>
|
||||
<div style="font-family: inherit; text-align: center">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959"><strong>You've been invited to collaborate.</strong></span>
|
||||
</div>
|
||||
<div style="font-family: inherit; text-align: center">
|
||||
<br></div>
|
||||
<div style="font-family: inherit; text-align: inherit; margin-left: 0px">
|
||||
<span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">{{Inviter_First_Name}} has invited you to collaborate on the organization "<strong>{{inviter_org_name}}</strong>" in Appsmith.</span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="module"
|
||||
data-role="module-button" data-type="button"
|
||||
role="module" style="table-layout:fixed;" width="100%"
|
||||
data-muid="f00155e0-813d-4e9b-b61d-384e6f99e5b7">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="" class="outer-td"
|
||||
style="padding:0px 0px 0px 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
class="wrapper-mobile"
|
||||
style="text-align:center;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="#ff6d2d"
|
||||
class="inner-td"
|
||||
style="border-radius:6px; font-size:16px; text-align:center; background-color:inherit;">
|
||||
<a href="{{inviteUrl}}"
|
||||
style="background-color:#ff6d2d; border:1px solid #ff6d2d; border-color:#ff6d2d; border-radius:6px; border-width:1px; color:#ffffff; display:inline-block; font-weight:400; letter-spacing:0px; line-height:6px; padding:12px 18px 12px 18px; text-align:center; text-decoration:none; border-style:solid; font-family:tahoma,geneva,sans-serif; font-size:16px;"
|
||||
target="_blank">Go To Appsmith</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="module" role="module" data-type="text" border="0"
|
||||
cellpadding="0" cellspacing="0" width="100%"
|
||||
style="table-layout: fixed;"
|
||||
data-muid="cab2544f-5a6c-49a0-b246-efe5ac8c5208"
|
||||
data-mc-module-version="2019-10-22">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 0px 0px; line-height:22px; text-align:inherit;"
|
||||
height="100%" valign="top" bgcolor=""
|
||||
role="module-content">
|
||||
<div>
|
||||
<div style="font-family: inherit; text-align: start">
|
||||
<span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; font-size: 14px">Cheers</span>
|
||||
</div>
|
||||
<div style="font-family: inherit; text-align: start">
|
||||
<span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; font-size: 14px">Devs at Appsmith</span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
style="border-radius:6px; font-size:16px; text-align:center; background-color:inherit;">
|
||||
<a href="{{inviteUrl}}"
|
||||
style="background-color:#ff6d2d; border:1px solid #ff6d2d; border-color:#ff6d2d; border-radius:6px; border-width:1px; color:#ffffff; display:inline-block; font-weight:400; letter-spacing:0px; line-height:6px; padding:12px 18px 12px 18px; text-align:center; text-decoration:none; border-style:solid; font-family:tahoma,geneva,sans-serif; font-size:16px;"
|
||||
target="_blank">Accept invite</a>
|
||||
target="_blank">Sign Up Now</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import com.appsmith.server.acl.AppsmithRole;
|
|||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.domains.UserRole;
|
||||
import com.appsmith.server.dtos.InviteUserDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.repositories.OrganizationRepository;
|
||||
|
|
@ -56,6 +58,9 @@ public class OrganizationServiceTest {
|
|||
@Autowired
|
||||
ApplicationService applicationService;
|
||||
|
||||
@Autowired
|
||||
UserService userService;
|
||||
|
||||
Organization organization;
|
||||
|
||||
@Before
|
||||
|
|
@ -266,26 +271,35 @@ public class OrganizationServiceTest {
|
|||
|
||||
/**
|
||||
* This test tests for an existing user being added to an organzation as admin.
|
||||
* The organization object should have permissions to
|
||||
* The organization object should have permissions to manage the org for the invited user.
|
||||
*/
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void addUserToOrganizationAsAdmin() {
|
||||
public void addExistingUserToOrganizationAsAdmin() {
|
||||
Mono<Organization> seedOrganization = organizationRepository.findByName("Spring Test Organization", AclPermission.READ_ORGANIZATIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND)));
|
||||
|
||||
Mono<Organization> userAddedToOrgMono = seedOrganization
|
||||
Mono<User> userAddedToOrgMono = seedOrganization
|
||||
.flatMap(organization1 -> {
|
||||
// Add user to organization
|
||||
UserRole userRole = new UserRole();
|
||||
userRole.setRoleName(AppsmithRole.ORGANIZATION_ADMIN.getName());
|
||||
userRole.setUsername("usertest@usertest.com");
|
||||
return userOrganizationService.addUserRoleToOrganization(organization1.getId(), userRole);
|
||||
});
|
||||
InviteUserDTO inviteUserDTO = new InviteUserDTO();
|
||||
inviteUserDTO.setEmail("usertest@usertest.com");
|
||||
inviteUserDTO.setOrgId(organization1.getId());
|
||||
inviteUserDTO.setRoleName(AppsmithRole.ORGANIZATION_ADMIN.getName());
|
||||
|
||||
return userService.inviteUser(inviteUserDTO, "http://localhost:8080");
|
||||
})
|
||||
.cache();
|
||||
|
||||
Mono<Organization> orgAfterUpdateMono = userAddedToOrgMono
|
||||
.then(seedOrganization);
|
||||
|
||||
StepVerifier
|
||||
.create(userAddedToOrgMono)
|
||||
.assertNext(org -> {
|
||||
.create(Mono.zip(userAddedToOrgMono, orgAfterUpdateMono))
|
||||
.assertNext(tuple -> {
|
||||
User user = tuple.getT1();
|
||||
Organization org = tuple.getT2();
|
||||
|
||||
assertThat(org).isNotNull();
|
||||
assertThat(org.getName()).isEqualTo("Spring Test Organization");
|
||||
assertThat(org.getUserRoles().get(0).getUsername()).isEqualTo("usertest@usertest.com");
|
||||
|
|
@ -305,6 +319,68 @@ public class OrganizationServiceTest {
|
|||
assertThat(org.getPolicies()).isNotEmpty();
|
||||
assertThat(org.getPolicies()).containsAll(Set.of(manageOrgAppPolicy, manageOrgPolicy, readOrgPolicy));
|
||||
|
||||
Set<String> organizationIds = user.getOrganizationIds();
|
||||
assertThat(organizationIds).contains(org.getId());
|
||||
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* This test tests for a new user being added to an organzation as admin.
|
||||
* The new user must be created at after invite flow and the new user must be disabled.
|
||||
*/
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void addNewUserToOrganization() {
|
||||
Mono<Organization> seedOrganization = organizationRepository.findByName("Another Test Organization", AclPermission.READ_ORGANIZATIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND)));
|
||||
|
||||
Mono<User> userAddedToOrgMono = seedOrganization
|
||||
.flatMap(organization1 -> {
|
||||
// Add user to organization
|
||||
InviteUserDTO inviteUserDTO = new InviteUserDTO();
|
||||
inviteUserDTO.setEmail("newEmailWhichShouldntExist@usertest.com");
|
||||
inviteUserDTO.setOrgId(organization1.getId());
|
||||
inviteUserDTO.setRoleName(AppsmithRole.ORGANIZATION_ADMIN.getName());
|
||||
|
||||
return userService.inviteUser(inviteUserDTO, "http://localhost:8080");
|
||||
})
|
||||
.cache();
|
||||
|
||||
Mono<Organization> orgAfterUpdateMono = userAddedToOrgMono
|
||||
.then(seedOrganization);
|
||||
|
||||
StepVerifier
|
||||
.create(Mono.zip(userAddedToOrgMono, orgAfterUpdateMono))
|
||||
.assertNext(tuple -> {
|
||||
User user = tuple.getT1();
|
||||
Organization org = tuple.getT2();
|
||||
|
||||
assertThat(org).isNotNull();
|
||||
assertThat(org.getName()).isEqualTo("Another Test Organization");
|
||||
assertThat(org.getUserRoles().get(0).getUsername()).isEqualTo("newEmailWhichShouldntExist@usertest.com");
|
||||
|
||||
Policy manageOrgAppPolicy = Policy.builder().permission(ORGANIZATION_MANAGE_APPLICATIONS.getValue())
|
||||
.users(Set.of("api_user", "newEmailWhichShouldntExist@usertest.com"))
|
||||
.build();
|
||||
|
||||
Policy manageOrgPolicy = Policy.builder().permission(MANAGE_ORGANIZATIONS.getValue())
|
||||
.users(Set.of("api_user", "newEmailWhichShouldntExist@usertest.com"))
|
||||
.build();
|
||||
|
||||
Policy readOrgPolicy = Policy.builder().permission(READ_ORGANIZATIONS.getValue())
|
||||
.users(Set.of("api_user", "newEmailWhichShouldntExist@usertest.com"))
|
||||
.build();
|
||||
|
||||
assertThat(org.getPolicies()).isNotEmpty();
|
||||
assertThat(org.getPolicies()).containsAll(Set.of(manageOrgAppPolicy, manageOrgPolicy, readOrgPolicy));
|
||||
|
||||
assertThat(user).isNotNull();
|
||||
assertThat(user.getIsEnabled()).isFalse();
|
||||
Set<String> organizationIds = user.getOrganizationIds();
|
||||
assertThat(organizationIds).contains(org.getId());
|
||||
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.appsmith.server.domains.Organization;
|
|||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.repositories.UserRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
|
@ -49,6 +50,9 @@ public class UserServiceTest {
|
|||
@Autowired
|
||||
ApplicationService applicationService;
|
||||
|
||||
@Autowired
|
||||
UserRepository userRepository;
|
||||
|
||||
Mono<User> userMono;
|
||||
|
||||
Mono<Organization> organizationMono;
|
||||
|
|
@ -105,17 +109,6 @@ public class UserServiceTest {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateUserWithInvalidOrganization() {
|
||||
User updateUser = new User();
|
||||
updateUser.setCurrentOrganizationId("Random-OrgId-%Not-In_The-System_For_SUre");
|
||||
Mono<User> userMono1 = userMono.flatMap(user -> userService.update(user.getId(), updateUser));
|
||||
StepVerifier.create(userMono1)
|
||||
.expectErrorMatches(throwable -> throwable instanceof AppsmithException &&
|
||||
throwable.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage("Random-OrgId-%Not-In_The-System_For_SUre")))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "anonymousUser", roles = {"ANONYMOUS"})
|
||||
public void createNewUserFormSignupNullPassword() {
|
||||
|
|
@ -251,5 +244,28 @@ public class UserServiceTest {
|
|||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmInviteTokenFlow() {
|
||||
User newUser = new User();
|
||||
newUser.setEmail("newEmail@newEmail.com");
|
||||
newUser.setIsEnabled(false);
|
||||
newUser.setInviteToken("inviteToken");
|
||||
|
||||
userRepository.save(newUser).block();
|
||||
|
||||
newUser.setPassword("newPassword");
|
||||
|
||||
Mono<User> afterConfirmationUserMono = userService.confirmInviteUser(newUser, "http://localhost:8080")
|
||||
.then(userRepository.findByEmail("newEmail@newEmail.com"));
|
||||
|
||||
StepVerifier.create(afterConfirmationUserMono)
|
||||
.assertNext(user -> {
|
||||
assertThat(user).isNotNull();
|
||||
assertThat(user.getIsEnabled()).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user