Signup flow

This commit is contained in:
Trisha Anand 2019-09-06 10:49:48 +00:00
parent f71bd9a4e0
commit bea5f53f46
14 changed files with 285 additions and 99 deletions

View File

@ -47,6 +47,7 @@ public class ClientUserRepository implements ServerOAuth2AuthorizedClientReposit
private final String sessionAttributeName = DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME; private final String sessionAttributeName = DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME;
UserService userService; UserService userService;
OrganizationService organizationService; OrganizationService organizationService;
public ClientUserRepository(UserService userService, OrganizationService organizationService) { public ClientUserRepository(UserService userService, OrganizationService organizationService) {
this.userService = userService; this.userService = userService;
this.organizationService = organizationService; this.organizationService = organizationService;
@ -92,20 +93,8 @@ public class ClientUserRepository implements ServerOAuth2AuthorizedClientReposit
newUser.setState(UserState.ACTIVATED); newUser.setState(UserState.ACTIVATED);
newUser.setIsEnabled(true); newUser.setIsEnabled(true);
/** TODO return userService.findByEmail(user.getEmail())
* Organization here is being hardcoded. This is a stop gap measure .switchIfEmpty(userService.save(newUser)); //In case the user doesnt exist, save the user.
* A flow needs to be added to connect a User to a Organization. Once
* that is done, during the login, the organization should be picked up
* and a user should be hence created.
*/
return organizationService.findById("5d3e90a2dfec7c00047a81ea")
.map(organization -> {
newUser.setOrganization(organization);
return newUser;
})
.then(userService.findByEmail(user.getEmail()))
.switchIfEmpty(userService.save(newUser));
} }
@Override @Override

View File

@ -11,4 +11,5 @@ public interface Url {
String SETTING_URL = BASE_URL + VERSION + "/settings"; String SETTING_URL = BASE_URL + VERSION + "/settings";
String RESOURCE_URL = BASE_URL + VERSION + "/resources"; String RESOURCE_URL = BASE_URL + VERSION + "/resources";
String ACTION_URL = BASE_URL + VERSION + "/actions"; String ACTION_URL = BASE_URL + VERSION + "/actions";
String USER_URL = BASE_URL + VERSION + "/users";
} }

View File

@ -1,6 +1,9 @@
package com.appsmith.server.controllers; package com.appsmith.server.controllers;
import com.appsmith.server.domains.User;
import com.appsmith.server.services.UserService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -13,8 +16,16 @@ import java.security.Principal;
@RequestMapping("") @RequestMapping("")
public class IndexController { public class IndexController {
private final UserService service;
@Autowired
public IndexController(UserService service) {
this.service = service;
}
@GetMapping @GetMapping
public Mono<String> index(Mono<Principal> principal) { public Mono<String> index(Mono<Principal> principal) {
Mono<User> userMono = service.getCurrentUser();
return principal return principal
.map(Principal::getName) .map(Principal::getName)
.map(name -> String.format("Hello %s", name)); .map(name -> String.format("Hello %s", name));

View File

@ -0,0 +1,18 @@
package com.appsmith.server.controllers;
import com.appsmith.server.constants.Url;
import com.appsmith.server.domains.User;
import com.appsmith.server.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(Url.USER_URL)
public class UserController extends BaseController<UserService, User, String> {
@Autowired
public UserController(UserService service) {
super(service);
}
}

View File

@ -33,9 +33,9 @@ public class User extends BaseDomain implements UserDetails {
private UserState state; private UserState state;
private Boolean isEnabled; private Boolean isEnabled = true;
private Organization organization; private String organizationId;
@Override @Override
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {

View File

@ -9,7 +9,7 @@ public enum AppsmithError {
NO_RESOURCE_FOUND(404, 1000, "Unable to find {0} with id {1}"), NO_RESOURCE_FOUND(404, 1000, "Unable to find {0} with id {1}"),
INVALID_PARAMETER(400, 4000, "Invalid parameter {0} provided in the input"), INVALID_PARAMETER(400, 4000, "Invalid parameter {0} provided in the input"),
PLUGIN_NOT_INSTALLED(400, 4001, "Plugin not installed for organization {0}"), PLUGIN_NOT_INSTALLED(400, 4001, "Plugin {0} not installed"),
PLUGIN_ID_NOT_GIVEN(400, 4002, "Missing plugin id. Please input correct plugin id"), PLUGIN_ID_NOT_GIVEN(400, 4002, "Missing plugin id. Please input correct plugin id"),
RESOURCE_ID_NOT_GIVEN(400, 4003, "Missing resource id. Please input correct resource id"), RESOURCE_ID_NOT_GIVEN(400, 4003, "Missing resource id. Please input correct resource id"),
INTERNAL_SERVER_ERROR(500, 5000, "Internal server error while processing request"); INTERNAL_SERVER_ERROR(500, 5000, "Internal server error while processing request");

View File

@ -24,7 +24,7 @@ public class GlobalExceptionHandler {
* *
* @param e AppsmithException that will be caught by the function * @param e AppsmithException that will be caught by the function
* @param exchange ServerWebExchange contract in order to extract the response and set the http status code * @param exchange ServerWebExchange contract in order to extract the response and set the http status code
* @return Mono<ResponseDto < ErrorDTO>> * @return Mono<ResponseDto < ErrorDTO>>
*/ */
@ExceptionHandler @ExceptionHandler
@ResponseBody @ResponseBody
@ -40,7 +40,7 @@ public class GlobalExceptionHandler {
* *
* @param e Exception that will be caught by the function * @param e Exception that will be caught by the function
* @param exchange ServerWebExchange contract in order to extract the response and set the http status code * @param exchange ServerWebExchange contract in order to extract the response and set the http status code
* @return Mono<ResponseDto < ErrorDTO>> * @return Mono<ResponseDto < ErrorDTO>>
*/ */
@ExceptionHandler @ExceptionHandler
@ResponseBody @ResponseBody

View File

@ -0,0 +1,31 @@
package com.appsmith.server.helpers;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.util.HashSet;
import java.util.Set;
public final class BeanCopyUtils {
private static String[] getNullPropertyNames(Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>();
for (java.beans.PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) {
emptyNames.add(pd.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
//Use Spring BeanUtils to copy and ignore null
public static void copyNewFieldValuesIntoOldObject(Object src, Object target) {
BeanUtils.copyProperties(src, target, getNullPropertyNames(src));
}
}

View File

@ -68,7 +68,7 @@ public abstract class BaseService<R extends BaseRepository, T extends BaseDomain
@Override @Override
public Mono<T> getById(ID id) { public Mono<T> getById(ID id) {
if(id == null) { if (id == null) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ID)); return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ID));
} }
@ -98,12 +98,12 @@ public abstract class BaseService<R extends BaseRepository, T extends BaseDomain
*/ */
protected Mono<T> validateObject(T obj) { protected Mono<T> validateObject(T obj) {
return Mono.just(obj) return Mono.just(obj)
.map(o -> validator.validate(o)) .map(o -> validator.validate(o))
.flatMap(constraint -> { .flatMap(constraint -> {
if(constraint.isEmpty()) { if (constraint.isEmpty()) {
return Mono.just(obj); return Mono.just(obj);
} }
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, constraint.stream().findFirst().get().getPropertyPath())); return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, constraint.stream().findFirst().get().getPropertyPath()));
}); });
} }
} }

View File

@ -1,19 +1,17 @@
package com.appsmith.server.services; package com.appsmith.server.services;
import com.appsmith.server.configurations.ClientUserRepository;
import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.OrganizationPlugin; import com.appsmith.server.domains.OrganizationPlugin;
import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.Plugin;
import com.appsmith.server.domains.PluginType; import com.appsmith.server.domains.PluginType;
import com.appsmith.server.domains.User;
import com.appsmith.server.dtos.OrganizationPluginStatus; import com.appsmith.server.dtos.OrganizationPluginStatus;
import com.appsmith.server.dtos.PluginOrgDTO; import com.appsmith.server.dtos.PluginOrgDTO;
import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.repositories.PluginRepository; import com.appsmith.server.repositories.PluginRepository;
import com.appsmith.server.repositories.UserRepository;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter;
@ -30,13 +28,9 @@ import java.util.List;
public class PluginServiceImpl extends BaseService<PluginRepository, Plugin, String> implements PluginService { public class PluginServiceImpl extends BaseService<PluginRepository, Plugin, String> implements PluginService {
private final PluginRepository pluginRepository; private final PluginRepository pluginRepository;
private final UserRepository userRepository;
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
private final ClientUserRepository clientUserRepository;
private final OrganizationService organizationService; private final OrganizationService organizationService;
private final UserService userService;
@Value("${organization.id}")
private String organizationId;
@Autowired @Autowired
public PluginServiceImpl(Scheduler scheduler, public PluginServiceImpl(Scheduler scheduler,
@ -44,16 +38,13 @@ public class PluginServiceImpl extends BaseService<PluginRepository, Plugin, Str
MongoConverter mongoConverter, MongoConverter mongoConverter,
ReactiveMongoTemplate reactiveMongoTemplate, ReactiveMongoTemplate reactiveMongoTemplate,
PluginRepository repository, PluginRepository repository,
UserRepository userRepository,
ApplicationContext applicationContext, ApplicationContext applicationContext,
ClientUserRepository clientUserRepository, OrganizationService organizationService, UserService userService) {
OrganizationService organizationService) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository); super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository);
this.userRepository = userRepository;
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
pluginRepository = repository; pluginRepository = repository;
this.clientUserRepository = clientUserRepository;
this.organizationService = organizationService; this.organizationService = organizationService;
this.userService = userService;
} }
public PluginExecutor getPluginExecutor(PluginType pluginType, String className) { public PluginExecutor getPluginExecutor(PluginType pluginType, String className) {
@ -90,29 +81,17 @@ public class PluginServiceImpl extends BaseService<PluginRepository, Plugin, Str
@Override @Override
public Mono<Organization> uninstallPlugin(PluginOrgDTO pluginDTO) { public Mono<Organization> uninstallPlugin(PluginOrgDTO pluginDTO) {
/*TODO
* Organization & user association is being mocked here by forcefully
* only using a hardcoded organization. This needs to be replaced by
* a user-organization association flow. The Organization needs to be picked
* up from a user object. This is being used in install/uninstall
* plugin from a organization flow. Instead, the current user should be read
* using the following :
* ReactiveSecurityContextHolder.getContext()
* .map(SecurityContext::getAuthentication)
* .map(Authentication::getPrincipal);
* Once the user has been pulled using this, organization should already
* be stored as part of user and this organization should be used to store
* the installed plugin or to delete plugin during uninstallation.
*/
if (pluginDTO.getPluginId() == null) { if (pluginDTO.getPluginId() == null) {
return Mono.error(new AppsmithException(AppsmithError.PLUGIN_ID_NOT_GIVEN)); return Mono.error(new AppsmithException(AppsmithError.PLUGIN_ID_NOT_GIVEN));
} }
//Find the organization using id and plugin id -> This is to find if the organization has the plugin installed //Find the organization using id and plugin id -> This is to find if the organization has the plugin installed
Mono<Organization> organizationMono = organizationService.findByIdAndPluginsPluginId(organizationId, pluginDTO.getPluginId()); Mono<User> userMono = userService.getCurrentUser();
Mono<Organization> organizationMono = userMono.flatMap(user ->
organizationService.findByIdAndPluginsPluginId(user.getOrganizationId(), pluginDTO.getPluginId()));
return organizationMono return organizationMono
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.PLUGIN_NOT_INSTALLED, organizationId))) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.PLUGIN_NOT_INSTALLED, pluginDTO.getPluginId())))
//In case the plugin is not found for the organization, the organizationMono would not emit and the rest of the flow would stop //In case the plugin is not found for the organization, the organizationMono would not emit and the rest of the flow would stop
//i.e. the rest of the code flow would only happen when there is a plugin found for the organization that can //i.e. the rest of the code flow would only happen when there is a plugin found for the organization that can
//be uninstalled. //be uninstalled.
@ -126,40 +105,33 @@ public class PluginServiceImpl extends BaseService<PluginRepository, Plugin, Str
} }
private Mono<Organization> storeOrganizationPlugin(PluginOrgDTO pluginDTO, OrganizationPluginStatus status) { private Mono<Organization> storeOrganizationPlugin(PluginOrgDTO pluginDTO, OrganizationPluginStatus status) {
/*TODO
* Organization & user association is being mocked here by forcefully
* only using a hardcoded organization. This needs to be replaced by
* a user-organization association flow. The Organization needs to be picked
* up from a user object. This is being used in install/uninstall
* plugin from a organization flow. Instead, the current user should be read
* using the following :
* ReactiveSecurityContextHolder.getContext()
* .map(SecurityContext::getAuthentication)
* .map(Authentication::getPrincipal);
* Once the user has been pulled using this, organization should already
* be stored as part of user and this organization should be used to store
* the installed plugin or to delete plugin during uninstalling.
*/
//Find the organization using id and plugin id -> This is to find if the organization already has the plugin installed //Find the organization using id and plugin id -> This is to find if the organization already has the plugin installed
Mono<Organization> organizationMono = organizationService.findByIdAndPluginsPluginId(organizationId, pluginDTO.getPluginId()); Mono<User> userMono = userService.getCurrentUser();
Mono<Organization> organizationMono = userMono.flatMap(user ->
organizationService.findByIdAndPluginsPluginId(user.getOrganizationId(), pluginDTO.getPluginId()));
//If plugin is already present for the organization, just return the organization, else install and return organization
return organizationMono return organizationMono
.switchIfEmpty(Mono.defer(() -> { .switchIfEmpty(Mono.defer(() -> {
//If the plugin is not found in the organization, its not already installed. Install now. //If the plugin is not found in the organization, its not installed already. Install now.
return organizationService.findById(organizationId).map(organization -> { return userMono
List<OrganizationPlugin> organizationPluginList = organization.getPlugins(); .flatMap(user -> organizationService.findById(user.getOrganizationId()))
if (organizationPluginList == null) { .map(organization -> {
organizationPluginList = new ArrayList<OrganizationPlugin>(); List<OrganizationPlugin> organizationPluginList = organization.getPlugins();
} if (organizationPluginList == null) {
log.debug("Installing plugin {} for organization {}", pluginDTO.getPluginId(), organization.getName()); organizationPluginList = new ArrayList<OrganizationPlugin>();
OrganizationPlugin organizationPlugin = new OrganizationPlugin(); }
organizationPlugin.setPluginId(pluginDTO.getPluginId()); log.debug("Installing plugin {} for organization {}", pluginDTO.getPluginId(), organization.getName());
organizationPlugin.setStatus(status); OrganizationPlugin organizationPlugin = new OrganizationPlugin();
organizationPluginList.add(organizationPlugin); organizationPlugin.setPluginId(pluginDTO.getPluginId());
organization.setPlugins(organizationPluginList); organizationPlugin.setStatus(status);
return organization; organizationPluginList.add(organizationPlugin);
}).flatMap(organizationService::save); organization.setPlugins(organizationPluginList);
return organization;
})
.flatMap(organizationService::save);
})); }));
} }

View File

@ -2,12 +2,12 @@ package com.appsmith.server.services;
import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.Resource; import com.appsmith.server.domains.Resource;
import com.appsmith.server.domains.User;
import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.repositories.ResourceRepository; import com.appsmith.server.repositories.ResourceRepository;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -21,19 +21,16 @@ import javax.validation.constraints.NotNull;
@Service @Service
public class ResourceServiceImpl extends BaseService<ResourceRepository, Resource, String> implements ResourceService { public class ResourceServiceImpl extends BaseService<ResourceRepository, Resource, String> implements ResourceService {
@Value("${organization.id}")
private String organizationId;
private final ResourceRepository repository; private final ResourceRepository repository;
private final OrganizationService organizationService; private final OrganizationService organizationService;
private final PluginService pluginService; private final UserService userService;
@Autowired @Autowired
public ResourceServiceImpl(Scheduler scheduler, Validator validator, MongoConverter mongoConverter, ReactiveMongoTemplate reactiveMongoTemplate, ResourceRepository repository, OrganizationService organizationService, PluginService pluginService) { public ResourceServiceImpl(Scheduler scheduler, Validator validator, MongoConverter mongoConverter, ReactiveMongoTemplate reactiveMongoTemplate, ResourceRepository repository, OrganizationService organizationService, PluginService pluginService, UserService userService) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository); super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository);
this.repository = repository; this.repository = repository;
this.organizationService = organizationService; this.organizationService = organizationService;
this.pluginService = pluginService; this.userService = userService;
} }
@Override @Override
@ -44,17 +41,19 @@ public class ResourceServiceImpl extends BaseService<ResourceRepository, Resourc
return Mono.error(new AppsmithException(AppsmithError.PLUGIN_ID_NOT_GIVEN)); return Mono.error(new AppsmithException(AppsmithError.PLUGIN_ID_NOT_GIVEN));
} }
Mono<Organization> organizationMono = organizationService.findByIdAndPluginsPluginId(organizationId, resource.getPluginId()); Mono<User> userMono = userService.getCurrentUser();
Mono<Organization> organizationMono = userMono.flatMap(user -> organizationService.findByIdAndPluginsPluginId(user.getOrganizationId(), resource.getPluginId()));
//Add organization id to the resource. //Add organization id to the resource.
Mono<Resource> updatedResourceMono = Mono.just(resource) Mono<Resource> updatedResourceMono = organizationMono
.map(updatedResource -> { .map(organization -> {
updatedResource.setOrganizationId(organizationId); resource.setOrganizationId(organization.getId());
return updatedResource; return resource;
}); });
return organizationMono return organizationMono
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.PLUGIN_NOT_INSTALLED, organizationId))) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.PLUGIN_NOT_INSTALLED, resource.getPluginId())))
.then(updatedResourceMono) .then(updatedResourceMono)
.flatMap(repository::save); .flatMap(repository::save);
} }

View File

@ -10,4 +10,6 @@ public interface UserService extends CrudService<User, String> {
Mono<User> findByEmail(String email); Mono<User> findByEmail(String email);
Mono<User> save(User newUser); Mono<User> save(User newUser);
Mono<User> getCurrentUser();
} }

View File

@ -1,30 +1,43 @@
package com.appsmith.server.services; package com.appsmith.server.services;
import com.appsmith.server.domains.User; import com.appsmith.server.domains.User;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.BeanCopyUtils;
import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.repositories.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Scheduler;
import javax.validation.Validator; import javax.validation.Validator;
@Slf4j
@Service @Service
public class UserServiceImpl extends BaseService<UserRepository, User, String> implements UserService, UserDetailsService { public class UserServiceImpl extends BaseService<UserRepository, User, String> implements UserService, UserDetailsService {
private UserRepository repository; private UserRepository repository;
private final OrganizationService organizationService;
@Autowired
public UserServiceImpl(Scheduler scheduler, public UserServiceImpl(Scheduler scheduler,
Validator validator, Validator validator,
MongoConverter mongoConverter, MongoConverter mongoConverter,
ReactiveMongoTemplate reactiveMongoTemplate, ReactiveMongoTemplate reactiveMongoTemplate,
UserRepository repository) { UserRepository repository, OrganizationService organizationService) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository); super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository);
this.repository = repository; this.repository = repository;
this.organizationService = organizationService;
} }
@Override @Override
@ -46,4 +59,55 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return repository.findByName(username).block(); return repository.findByName(username).block();
} }
@Override
public Mono<User> getCurrentUser() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getPrincipal)
.flatMap(principal -> {
String email;
if (principal instanceof org.springframework.security.core.userdetails.User) {
org.springframework.security.core.userdetails.User user = (org.springframework.security.core.userdetails.User) principal;
//Assumption that the user has inputted an email as username during user creation and not english passport name
email = user.getUsername();
} else {
DefaultOidcUser defaultOidcUser = (DefaultOidcUser) principal;
email = defaultOidcUser.getEmail();
}
return repository.findByEmail(email);
});
}
@Override
public Mono<User> update(String id, User userUpdate) {
Mono<User> userFromRepository = repository.findById(id);
return Mono.just(userUpdate)
.flatMap(this::validateUpdate)
//Once the new update has been validated, update the user with the new fields.
.then(userFromRepository)
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, id)))
.map(existingUser -> {
BeanCopyUtils.copyNewFieldValuesIntoOldObject(userUpdate, existingUser);
return existingUser;
})
.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.getOrganizationId() == 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.getOrganizationId())
//If the organization is not found in the repository, throw an error
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, updateUser.getOrganizationId())))
.then(Mono.just(updateUser));
}
} }

View File

@ -0,0 +1,99 @@
package com.appsmith.server.services;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.User;
import com.appsmith.server.domains.UserState;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
UserService userService;
@Autowired
OrganizationService organizationService;
Mono<User> userMono;
Mono<Organization> organizationMono;
@Before
public void setup() {
//User init
User user = new User();
user.setName("user test");
user.setEmail("usertest@usertest.com");
user.setState(UserState.ACTIVATED);
//Store the user in case its not present in the database.
userMono = userService.findByEmail("usertest@usertest.com").switchIfEmpty(Mono.defer(() -> userService.save(user)));
//Organization init
Organization organization = new Organization();
organization.setName("Spring Test Organization");
organization.setDomain("appsmith-spring-test.com");
organization.setWebsite("appsmith.com");
//Store the organization in case its not present in the database.
organizationMono = organizationService.getByName("Spring Test Organization").switchIfEmpty(Mono.defer(() -> organizationService.save(organization)));
}
//Test the update organization flow.
@Test
public void updateInvalidUserWithAnything() {
User updateUser = new User();
updateUser.setName("Random User whose updates don't matter");
User existingUser = new User();
existingUser.setId("Random-UserId-%Not-In_The-System_For_SUre");
Mono<User> userMono1 = Mono.just(existingUser).flatMap(user -> userService.update(user.getId(), updateUser));
StepVerifier.create(userMono1)
.expectErrorMatches(throwable -> throwable instanceof AppsmithException &&
throwable.getMessage().equals(AppsmithError.INVALID_PARAMETER.getMessage("Random-UserId-%Not-In_The-System_For_SUre")))
.verify();
}
@Test
public void updateUserWithValidOrganization() {
User updateUser = new User();
//Add valid organization id to the updateUser object.
organizationMono
.map(organization -> {
updateUser.setOrganizationId(organization.getId());
return updateUser;
}).block();
Mono<User> userMono1 = userMono.flatMap(user -> userService.update(user.getId(), updateUser));
StepVerifier.create(userMono1)
.assertNext(updatedUserInRepository -> {
assertThat(updatedUserInRepository.getOrganizationId()).isEqualTo(updateUser.getOrganizationId());
})
.verifyComplete();
}
@Test
public void updateUserWithInvalidOrganization() {
User updateUser = new User();
updateUser.setOrganizationId("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();
}
}