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:
Trisha Anand 2020-06-01 17:39:27 +00:00
parent b0b72d965c
commit f8cf87e3c8
10 changed files with 661 additions and 263 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: &quot;lucida sans unicode&quot;, &quot;lucida grande&quot;, 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: &quot;lucida sans unicode&quot;, &quot;lucida grande&quot;, 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: &quot;lucida sans unicode&quot;, &quot;lucida grande&quot;, 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: &quot;lucida sans unicode&quot;, &quot;lucida grande&quot;, 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>

View File

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

View File

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

View File

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