Add application creation route.

This commit is contained in:
2019-09-01 18:10:57 +02:00
parent 006ce222d0
commit 5ab829ff64
16 changed files with 267 additions and 24 deletions

View File

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

View File

@@ -1,13 +1,9 @@
package org.cerberus.controllers; package org.cerberus.controllers;
import org.cerberus.core.config.security.CustomAuthenticationProvider;
import org.cerberus.entities.dto.SignUpDTO; import org.cerberus.entities.dto.SignUpDTO;
import org.cerberus.entities.persistence.User; import org.cerberus.entities.persistence.User;
import org.cerberus.repositories.UserRepository;
import org.cerberus.services.UserService; import org.cerberus.services.UserService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.bind.annotation.*; 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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import static org.springframework.http.HttpStatus.NO_CONTENT;
@RestController @RestController
@RequestMapping("/api/users") @RequestMapping("/api/users")
public class UserController { public class UserController {
@@ -25,23 +23,23 @@ public class UserController {
} }
@PostMapping("/login") @PostMapping("/login")
public void login(@RequestBody User user, HttpServletResponse response) { @ResponseStatus(NO_CONTENT)
public void login(@RequestBody User user) {
userService.authenticate(user); userService.authenticate(user);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
} }
@GetMapping("/disconnection") @GetMapping("/disconnection")
@ResponseStatus(NO_CONTENT)
public void disconnection(HttpServletRequest request, HttpServletResponse response) { public void disconnection(HttpServletRequest request, HttpServletResponse response) {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if(auth != null) { if(auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth); new SecurityContextLogoutHandler().logout(request, response, auth);
} }
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
} }
@PostMapping("/signup") @PostMapping("/signup")
public void signUp(@RequestBody SignUpDTO inputData, HttpServletResponse response) { @ResponseStatus(NO_CONTENT)
public void signUp(@RequestBody SignUpDTO inputData) {
userService.signUp(inputData); userService.signUp(inputData);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
} }
} }

View File

@@ -1,5 +1,6 @@
package org.cerberus.core.config.security; package org.cerberus.core.config.security;
import org.cerberus.core.constant.RoleSecurity;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
@@ -19,7 +20,10 @@ import static org.springframework.http.HttpMethod.POST;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true
)
@Order(SecurityProperties.BASIC_AUTH_ORDER) @Order(SecurityProperties.BASIC_AUTH_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@@ -41,8 +45,9 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() http.httpBasic()
// Permits all .and()
.authorizeRequests()
.antMatchers( .antMatchers(
"/robots.txt" "/robots.txt"
).permitAll() ).permitAll()
@@ -51,8 +56,10 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
).permitAll() ).permitAll()
.antMatchers(POST, .antMatchers(POST,
"/api/users/login", "/api/users/login",
"/api/users/signup" "/api/users/signup",
"/api/applications"
).permitAll() ).permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll() .anyRequest().permitAll()
.and() .and()
// Allow to avoid login form at authentication failure from Angular app // Allow to avoid login form at authentication failure from Angular app
@@ -60,9 +67,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.and() .and()
.addFilterAfter(new XSRFTokenFilter(), CsrfFilter.class) .addFilterAfter(new XSRFTokenFilter(), CsrfFilter.class)
.csrf() .csrf()
.csrfTokenRepository(xsrfTokenRepository()); .csrfTokenRepository(xsrfTokenRepository())
http.httpBasic(); .disable();
http.csrf().disable();
} }
private CsrfTokenRepository xsrfTokenRepository() { private CsrfTokenRepository xsrfTokenRepository() {

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ package org.cerberus.entities.persistence;
import org.hibernate.annotations.Proxy; import org.hibernate.annotations.Proxy;
import javax.persistence.*; import javax.persistence.*;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -19,10 +20,10 @@ public class Application {
@Column(nullable = false) @Column(nullable = false)
private String serviceName; private String serviceName;
@OneToMany(mappedBy = "application") @OneToMany(mappedBy = "application", cascade = CascadeType.ALL)
private List<ConfigurationFile> configurationFileList; private List<ConfigurationFile> configurationFileList;
@OneToMany(mappedBy = "application") @OneToMany(mappedBy = "application", cascade = CascadeType.ALL)
private List<ApplicationRole> administratorList; private List<ApplicationRole> administratorList;
@PrePersist @PrePersist
@@ -55,6 +56,9 @@ public class Application {
} }
public List<ConfigurationFile> getConfigurationFileList() { public List<ConfigurationFile> getConfigurationFileList() {
if(configurationFileList == null) {
configurationFileList = new LinkedList<>();
}
return configurationFileList; return configurationFileList;
} }
@@ -63,6 +67,9 @@ public class Application {
} }
public List<ApplicationRole> getAdministratorList() { public List<ApplicationRole> getAdministratorList() {
if(administratorList == null) {
administratorList = new LinkedList<>();
}
return administratorList; return administratorList;
} }

View File

@@ -73,6 +73,7 @@ public class ApplicationRole {
} }
public void setUser(User user) { public void setUser(User user) {
getId().setUserId(user.getId());
this.user = user; this.user = user;
} }
@@ -81,6 +82,7 @@ public class ApplicationRole {
} }
public void setApplication(Application application) { public void setApplication(Application application) {
getId().setApplicationId(application.getId());
this.application = application; this.application = application;
} }
} }

View File

@@ -72,7 +72,7 @@ public class User {
this.password = password; this.password = password;
} }
public Boolean getAdmin() { public Boolean isAdmin() {
return isAdmin; return isAdmin;
} }

View File

@@ -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<Application, UUID> {
}

View File

@@ -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<ApplicationRole, UUID> {
}

View File

@@ -8,8 +8,9 @@ import org.springframework.data.repository.query.Param;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
public interface UserRepository extends JpaRepository<User, String> { public interface UserRepository extends JpaRepository<User, UUID> {
@Query("SELECT u FROM User u WHERE u.email = :email") @Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email); Optional<User> findByEmail(@Param("email") String email);
@@ -18,4 +19,7 @@ public interface UserRepository extends JpaRepository<User, String> {
@Query(value = "SELECT EXISTS(SELECT id FROM \"user\" WHERE email = :email)", nativeQuery = true) @Query(value = "SELECT EXISTS(SELECT id FROM \"user\" WHERE email = :email)", nativeQuery = true)
boolean isEmailAlreadyExists(@Param("email") String email); boolean isEmailAlreadyExists(@Param("email") String email);
@Query("SELECT isAdmin FROM User u WHERE u.id = :id")
boolean isAdmin(UUID id);
} }

View File

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

View File

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

View File

@@ -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> user = getUserByPrincipal(connectedUser);
if(user.isEmpty() || !userService.isAdmin(user.get())) {
throw new ForbiddenException("Illegal access attempt.");
}
return user.get();
}
public Optional<User> getUserByPrincipal(final Principal pPrincipal) {
Optional<User> result = Optional.empty();
if(pPrincipal != null) {
SecurityContextHolder.getContext().getAuthentication();
result = userService.findByEmail(pPrincipal.getName());
}
return result;
}
}

View File

@@ -2,6 +2,7 @@ package org.cerberus.services;
import org.cerberus.core.constant.Role; import org.cerberus.core.constant.Role;
import org.cerberus.core.config.security.CustomAuthenticationProvider; import org.cerberus.core.config.security.CustomAuthenticationProvider;
import org.cerberus.core.constant.RoleSecurity;
import org.cerberus.core.exceptions.BadRequestException; import org.cerberus.core.exceptions.BadRequestException;
import org.cerberus.entities.dto.SignUpDTO; import org.cerberus.entities.dto.SignUpDTO;
import org.cerberus.entities.persistence.ApplicationRole; import org.cerberus.entities.persistence.ApplicationRole;
@@ -36,29 +37,38 @@ public class UserService {
} }
public void authenticate(User user) { public void authenticate(User user) {
checkCredentials(user.getEmail(), user.getPassword()); User authenticatedUser = checkCredentials(user.getEmail(), user.getPassword());
authenticationProvider.authenticate(new UsernamePasswordAuthenticationToken( authenticationProvider.authenticate(new UsernamePasswordAuthenticationToken(
user.getEmail(), user.getEmail(),
user.getPassword(), user.getPassword(),
fetchGrantedAuthorities(user) fetchGrantedAuthorities(authenticatedUser)
)); ));
} }
void checkCredentials(String email, String password) { User checkCredentials(String email, String password) {
Optional<User> optUser = userRepository.findByEmail(email); Optional<User> optUser = userRepository.findByEmail(email);
if(optUser.isEmpty() || !optUser.get().getPassword().equals(password)) { if(optUser.isEmpty() || !optUser.get().getPassword().equals(password)) {
throw new BadRequestException("Credentials are incorrect."); throw new BadRequestException("Credentials are incorrect.");
} }
return optUser.get();
} }
Collection<GrantedAuthority> fetchGrantedAuthorities(User user) { Collection<GrantedAuthority> fetchGrantedAuthorities(User user) {
return userRepository.getApplicationRolesByEmail(user.getEmail()).stream() Collection<GrantedAuthority> grantedAuthorityCollection = userRepository.getApplicationRolesByEmail(user.getEmail())
.stream()
.map(ApplicationRole::getRole) .map(ApplicationRole::getRole)
.map(Role::name) .map(Role::name)
.map(SimpleGrantedAuthority::new) .map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if(user.isAdmin()) {
grantedAuthorityCollection.add(new SimpleGrantedAuthority(RoleSecurity.ADMIN));
}
return grantedAuthorityCollection;
} }
public void signUp(SignUpDTO inputData) { public void signUp(SignUpDTO inputData) {
@@ -70,4 +80,12 @@ public class UserService {
userRepository.save(signUpMapper.toUser(inputData)); userRepository.save(signUpMapper.toUser(inputData));
} }
public boolean isAdmin(User user) {
return userRepository.isAdmin(user.getId());
}
public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
} }

View File

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