From 5ab829ff64d7336d542e859718bfc238682eac2d Mon Sep 17 00:00:00 2001 From: florian Date: Sun, 1 Sep 2019 18:10:57 +0200 Subject: [PATCH] Add application creation route. --- .../controllers/ApplicationController.java | 31 ++++++++++++++ .../cerberus/controllers/UserController.java | 16 ++++--- .../security/SecurityConfiguration.java | 20 +++++---- .../core/exceptions/ForbiddenException.java | 16 +++++++ .../InternalServerErrorException.java | 16 +++++++ .../entities/persistence/Application.java | 11 ++++- .../entities/persistence/ApplicationRole.java | 2 + .../cerberus/entities/persistence/User.java | 2 +- .../repositories/ApplicationRepository.java | 9 ++++ .../ApplicationRoleRepository.java | 11 +++++ .../cerberus/repositories/UserRepository.java | 6 ++- .../services/ApplicationRoleService.java | 26 ++++++++++++ .../cerberus/services/ApplicationService.java | 40 ++++++++++++++++++ .../cerberus/services/SecurityService.java | 42 +++++++++++++++++++ .../org/cerberus/services/UserService.java | 26 ++++++++++-- .../validators/ApplicationValidator.java | 17 ++++++++ 16 files changed, 267 insertions(+), 24 deletions(-) create mode 100644 src/main/java/org/cerberus/controllers/ApplicationController.java create mode 100644 src/main/java/org/cerberus/core/exceptions/ForbiddenException.java create mode 100644 src/main/java/org/cerberus/core/exceptions/InternalServerErrorException.java create mode 100644 src/main/java/org/cerberus/repositories/ApplicationRepository.java create mode 100644 src/main/java/org/cerberus/repositories/ApplicationRoleRepository.java create mode 100644 src/main/java/org/cerberus/services/ApplicationRoleService.java create mode 100644 src/main/java/org/cerberus/services/ApplicationService.java create mode 100644 src/main/java/org/cerberus/services/SecurityService.java create mode 100644 src/main/java/org/cerberus/validators/ApplicationValidator.java diff --git a/src/main/java/org/cerberus/controllers/ApplicationController.java b/src/main/java/org/cerberus/controllers/ApplicationController.java new file mode 100644 index 0000000..b1205d7 --- /dev/null +++ b/src/main/java/org/cerberus/controllers/ApplicationController.java @@ -0,0 +1,31 @@ +package org.cerberus.controllers; + +import org.cerberus.entities.persistence.Application; +import org.cerberus.entities.persistence.User; +import org.cerberus.services.ApplicationService; +import org.cerberus.services.SecurityService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; + +@RestController +@RequestMapping("/api/applications") +public class ApplicationController { + private ApplicationService applicationService; + private SecurityService securityService; + + public ApplicationController(ApplicationService applicationService, + SecurityService securityService) { + this.applicationService = applicationService; + this.securityService = securityService; + } + + @PostMapping + public Application create(@RequestBody Application application, Principal connectedUser) { + User user = securityService.getAdminUser(connectedUser); + return applicationService.create(application, user); + } +} diff --git a/src/main/java/org/cerberus/controllers/UserController.java b/src/main/java/org/cerberus/controllers/UserController.java index f90a1d0..2d97926 100644 --- a/src/main/java/org/cerberus/controllers/UserController.java +++ b/src/main/java/org/cerberus/controllers/UserController.java @@ -1,13 +1,9 @@ package org.cerberus.controllers; -import org.cerberus.core.config.security.CustomAuthenticationProvider; import org.cerberus.entities.dto.SignUpDTO; import org.cerberus.entities.persistence.User; -import org.cerberus.repositories.UserRepository; import org.cerberus.services.UserService; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.web.bind.annotation.*; @@ -15,6 +11,8 @@ import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import static org.springframework.http.HttpStatus.NO_CONTENT; + @RestController @RequestMapping("/api/users") public class UserController { @@ -25,23 +23,23 @@ public class UserController { } @PostMapping("/login") - public void login(@RequestBody User user, HttpServletResponse response) { + @ResponseStatus(NO_CONTENT) + public void login(@RequestBody User user) { userService.authenticate(user); - response.setStatus(HttpServletResponse.SC_NO_CONTENT); } @GetMapping("/disconnection") + @ResponseStatus(NO_CONTENT) public void disconnection(HttpServletRequest request, HttpServletResponse response) { final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if(auth != null) { new SecurityContextLogoutHandler().logout(request, response, auth); } - response.setStatus(HttpServletResponse.SC_NO_CONTENT); } @PostMapping("/signup") - public void signUp(@RequestBody SignUpDTO inputData, HttpServletResponse response) { + @ResponseStatus(NO_CONTENT) + public void signUp(@RequestBody SignUpDTO inputData) { userService.signUp(inputData); - response.setStatus(HttpServletResponse.SC_NO_CONTENT); } } diff --git a/src/main/java/org/cerberus/core/config/security/SecurityConfiguration.java b/src/main/java/org/cerberus/core/config/security/SecurityConfiguration.java index 3891faa..03135fa 100755 --- a/src/main/java/org/cerberus/core/config/security/SecurityConfiguration.java +++ b/src/main/java/org/cerberus/core/config/security/SecurityConfiguration.java @@ -1,5 +1,6 @@ package org.cerberus.core.config.security; +import org.cerberus.core.constant.RoleSecurity; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -19,7 +20,10 @@ import static org.springframework.http.HttpMethod.POST; @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) +@EnableGlobalMethodSecurity( + prePostEnabled = true, + securedEnabled = true +) @Order(SecurityProperties.BASIC_AUTH_ORDER) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @@ -41,8 +45,9 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests() - // Permits all + http.httpBasic() + .and() + .authorizeRequests() .antMatchers( "/robots.txt" ).permitAll() @@ -51,8 +56,10 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { ).permitAll() .antMatchers(POST, "/api/users/login", - "/api/users/signup" + "/api/users/signup", + "/api/applications" ).permitAll() + .antMatchers("/api/**").authenticated() .anyRequest().permitAll() .and() // Allow to avoid login form at authentication failure from Angular app @@ -60,9 +67,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .and() .addFilterAfter(new XSRFTokenFilter(), CsrfFilter.class) .csrf() - .csrfTokenRepository(xsrfTokenRepository()); - http.httpBasic(); - http.csrf().disable(); + .csrfTokenRepository(xsrfTokenRepository()) + .disable(); } private CsrfTokenRepository xsrfTokenRepository() { diff --git a/src/main/java/org/cerberus/core/exceptions/ForbiddenException.java b/src/main/java/org/cerberus/core/exceptions/ForbiddenException.java new file mode 100644 index 0000000..aafc54b --- /dev/null +++ b/src/main/java/org/cerberus/core/exceptions/ForbiddenException.java @@ -0,0 +1,16 @@ +package org.cerberus.core.exceptions; + + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.FORBIDDEN) +public class ForbiddenException extends BusinessException { + public ForbiddenException(String message) { + super(message); + } + + public ForbiddenException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/cerberus/core/exceptions/InternalServerErrorException.java b/src/main/java/org/cerberus/core/exceptions/InternalServerErrorException.java new file mode 100644 index 0000000..c0a41b8 --- /dev/null +++ b/src/main/java/org/cerberus/core/exceptions/InternalServerErrorException.java @@ -0,0 +1,16 @@ +package org.cerberus.core.exceptions; + + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) +public class InternalServerErrorException extends BusinessException { + public InternalServerErrorException(String message) { + super(message); + } + + public InternalServerErrorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/cerberus/entities/persistence/Application.java b/src/main/java/org/cerberus/entities/persistence/Application.java index e6e40df..d6d7f58 100644 --- a/src/main/java/org/cerberus/entities/persistence/Application.java +++ b/src/main/java/org/cerberus/entities/persistence/Application.java @@ -3,6 +3,7 @@ package org.cerberus.entities.persistence; import org.hibernate.annotations.Proxy; import javax.persistence.*; +import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -19,10 +20,10 @@ public class Application { @Column(nullable = false) private String serviceName; - @OneToMany(mappedBy = "application") + @OneToMany(mappedBy = "application", cascade = CascadeType.ALL) private List configurationFileList; - @OneToMany(mappedBy = "application") + @OneToMany(mappedBy = "application", cascade = CascadeType.ALL) private List administratorList; @PrePersist @@ -55,6 +56,9 @@ public class Application { } public List getConfigurationFileList() { + if(configurationFileList == null) { + configurationFileList = new LinkedList<>(); + } return configurationFileList; } @@ -63,6 +67,9 @@ public class Application { } public List getAdministratorList() { + if(administratorList == null) { + administratorList = new LinkedList<>(); + } return administratorList; } diff --git a/src/main/java/org/cerberus/entities/persistence/ApplicationRole.java b/src/main/java/org/cerberus/entities/persistence/ApplicationRole.java index af68bf4..9ab82e3 100644 --- a/src/main/java/org/cerberus/entities/persistence/ApplicationRole.java +++ b/src/main/java/org/cerberus/entities/persistence/ApplicationRole.java @@ -73,6 +73,7 @@ public class ApplicationRole { } public void setUser(User user) { + getId().setUserId(user.getId()); this.user = user; } @@ -81,6 +82,7 @@ public class ApplicationRole { } public void setApplication(Application application) { + getId().setApplicationId(application.getId()); this.application = application; } } diff --git a/src/main/java/org/cerberus/entities/persistence/User.java b/src/main/java/org/cerberus/entities/persistence/User.java index ae98ec9..39505d5 100644 --- a/src/main/java/org/cerberus/entities/persistence/User.java +++ b/src/main/java/org/cerberus/entities/persistence/User.java @@ -72,7 +72,7 @@ public class User { this.password = password; } - public Boolean getAdmin() { + public Boolean isAdmin() { return isAdmin; } diff --git a/src/main/java/org/cerberus/repositories/ApplicationRepository.java b/src/main/java/org/cerberus/repositories/ApplicationRepository.java new file mode 100644 index 0000000..25b7b09 --- /dev/null +++ b/src/main/java/org/cerberus/repositories/ApplicationRepository.java @@ -0,0 +1,9 @@ +package org.cerberus.repositories; + +import org.cerberus.entities.persistence.Application; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface ApplicationRepository extends JpaRepository { +} diff --git a/src/main/java/org/cerberus/repositories/ApplicationRoleRepository.java b/src/main/java/org/cerberus/repositories/ApplicationRoleRepository.java new file mode 100644 index 0000000..7e2b6d2 --- /dev/null +++ b/src/main/java/org/cerberus/repositories/ApplicationRoleRepository.java @@ -0,0 +1,11 @@ +package org.cerberus.repositories; + +import org.cerberus.entities.persistence.ApplicationRole; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface ApplicationRoleRepository extends JpaRepository { +} diff --git a/src/main/java/org/cerberus/repositories/UserRepository.java b/src/main/java/org/cerberus/repositories/UserRepository.java index 50c96c4..ca732d8 100644 --- a/src/main/java/org/cerberus/repositories/UserRepository.java +++ b/src/main/java/org/cerberus/repositories/UserRepository.java @@ -8,8 +8,9 @@ import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; +import java.util.UUID; -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.email = :email") Optional findByEmail(@Param("email") String email); @@ -18,4 +19,7 @@ public interface UserRepository extends JpaRepository { @Query(value = "SELECT EXISTS(SELECT id FROM \"user\" WHERE email = :email)", nativeQuery = true) boolean isEmailAlreadyExists(@Param("email") String email); + + @Query("SELECT isAdmin FROM User u WHERE u.id = :id") + boolean isAdmin(UUID id); } diff --git a/src/main/java/org/cerberus/services/ApplicationRoleService.java b/src/main/java/org/cerberus/services/ApplicationRoleService.java new file mode 100644 index 0000000..0c47325 --- /dev/null +++ b/src/main/java/org/cerberus/services/ApplicationRoleService.java @@ -0,0 +1,26 @@ +package org.cerberus.services; + +import org.cerberus.core.constant.Role; +import org.cerberus.entities.persistence.Application; +import org.cerberus.entities.persistence.ApplicationRole; +import org.cerberus.entities.persistence.User; +import org.cerberus.repositories.ApplicationRoleRepository; +import org.springframework.stereotype.Service; + +@Service +public class ApplicationRoleService { + private ApplicationRoleRepository applicationRoleRepository; + + public ApplicationRoleService(ApplicationRoleRepository applicationRoleRepository) { + this.applicationRoleRepository = applicationRoleRepository; + } + + public void create(Application application, User user, Role role) { + ApplicationRole applicationRole = new ApplicationRole(); + applicationRole.setApplication(application); + applicationRole.setUser(user); + applicationRole.setRole(role); + + applicationRoleRepository.save(applicationRole); + } +} diff --git a/src/main/java/org/cerberus/services/ApplicationService.java b/src/main/java/org/cerberus/services/ApplicationService.java new file mode 100644 index 0000000..d2a5414 --- /dev/null +++ b/src/main/java/org/cerberus/services/ApplicationService.java @@ -0,0 +1,40 @@ +package org.cerberus.services; + +import org.cerberus.core.constant.Role; +import org.cerberus.core.exceptions.InternalServerErrorException; +import org.cerberus.entities.persistence.Application; +import org.cerberus.entities.persistence.ApplicationRole; +import org.cerberus.entities.persistence.User; +import org.cerberus.repositories.ApplicationRepository; +import org.cerberus.validators.ApplicationValidator; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.cerberus.core.constant.Role.MAINTAINER; + +@Service +public class ApplicationService { + + private ApplicationRepository applicationRepository; + private ApplicationRoleService applicationRoleService; + private ApplicationValidator applicationValidator; + + public ApplicationService(ApplicationRepository applicationRepository, + ApplicationRoleService applicationRoleService, + ApplicationValidator applicationValidator) { + this.applicationRepository = applicationRepository; + this.applicationRoleService = applicationRoleService; + this.applicationValidator = applicationValidator; + } + + @Transactional + public Application create(Application application, User user) { + applicationValidator.checkAllAttributsConstraints(application); + + applicationRepository.save(application); + // Application creator is by default a maintainer + applicationRoleService.create(application, user, MAINTAINER); + + return application; + } +} diff --git a/src/main/java/org/cerberus/services/SecurityService.java b/src/main/java/org/cerberus/services/SecurityService.java new file mode 100644 index 0000000..3175579 --- /dev/null +++ b/src/main/java/org/cerberus/services/SecurityService.java @@ -0,0 +1,42 @@ +package org.cerberus.services; + +import org.cerberus.core.exceptions.ForbiddenException; +import org.cerberus.entities.persistence.User; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.security.Principal; +import java.util.Optional; + +@Service +public class SecurityService { + + private UserService userService; + + public SecurityService(UserService userService) { + this.userService = userService; + } + + /** + * Returns the connected user if he's an administrator. Otherwise, a {@link ForbiddenException} will be thrown. + * @param connectedUser The connectedUser + */ + public User getAdminUser(Principal connectedUser) { + Optional user = getUserByPrincipal(connectedUser); + if(user.isEmpty() || !userService.isAdmin(user.get())) { + throw new ForbiddenException("Illegal access attempt."); + } + return user.get(); + } + + public Optional getUserByPrincipal(final Principal pPrincipal) { + Optional result = Optional.empty(); + + if(pPrincipal != null) { + SecurityContextHolder.getContext().getAuthentication(); + result = userService.findByEmail(pPrincipal.getName()); + } + + return result; + } +} diff --git a/src/main/java/org/cerberus/services/UserService.java b/src/main/java/org/cerberus/services/UserService.java index 0fe8feb..4936748 100644 --- a/src/main/java/org/cerberus/services/UserService.java +++ b/src/main/java/org/cerberus/services/UserService.java @@ -2,6 +2,7 @@ package org.cerberus.services; import org.cerberus.core.constant.Role; import org.cerberus.core.config.security.CustomAuthenticationProvider; +import org.cerberus.core.constant.RoleSecurity; import org.cerberus.core.exceptions.BadRequestException; import org.cerberus.entities.dto.SignUpDTO; import org.cerberus.entities.persistence.ApplicationRole; @@ -36,29 +37,38 @@ public class UserService { } public void authenticate(User user) { - checkCredentials(user.getEmail(), user.getPassword()); + User authenticatedUser = checkCredentials(user.getEmail(), user.getPassword()); authenticationProvider.authenticate(new UsernamePasswordAuthenticationToken( user.getEmail(), user.getPassword(), - fetchGrantedAuthorities(user) + fetchGrantedAuthorities(authenticatedUser) )); } - void checkCredentials(String email, String password) { + User checkCredentials(String email, String password) { Optional optUser = userRepository.findByEmail(email); if(optUser.isEmpty() || !optUser.get().getPassword().equals(password)) { throw new BadRequestException("Credentials are incorrect."); } + + return optUser.get(); } Collection fetchGrantedAuthorities(User user) { - return userRepository.getApplicationRolesByEmail(user.getEmail()).stream() + Collection grantedAuthorityCollection = userRepository.getApplicationRolesByEmail(user.getEmail()) + .stream() .map(ApplicationRole::getRole) .map(Role::name) .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); + + if(user.isAdmin()) { + grantedAuthorityCollection.add(new SimpleGrantedAuthority(RoleSecurity.ADMIN)); + } + + return grantedAuthorityCollection; } public void signUp(SignUpDTO inputData) { @@ -70,4 +80,12 @@ public class UserService { userRepository.save(signUpMapper.toUser(inputData)); } + + public boolean isAdmin(User user) { + return userRepository.isAdmin(user.getId()); + } + + public Optional findByEmail(String email) { + return userRepository.findByEmail(email); + } } diff --git a/src/main/java/org/cerberus/validators/ApplicationValidator.java b/src/main/java/org/cerberus/validators/ApplicationValidator.java new file mode 100644 index 0000000..ddb1b41 --- /dev/null +++ b/src/main/java/org/cerberus/validators/ApplicationValidator.java @@ -0,0 +1,17 @@ +package org.cerberus.validators; + +import org.cerberus.core.exceptions.BadRequestException; +import org.cerberus.core.utils.StringUtils; +import org.cerberus.entities.persistence.Application; +import org.springframework.stereotype.Component; + +@Component +public class ApplicationValidator { + + public void checkAllAttributsConstraints(Application application) { + if(StringUtils.isNull(application.getName()) + || StringUtils.isNull(application.getServiceName())) { + throw new BadRequestException("Please fill up all required fields."); + } + } +}