Init project.

This commit is contained in:
2019-09-01 12:16:49 +02:00
commit dcb5b73599
24 changed files with 866 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
package org.cerberus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableAutoConfiguration
public class CerberusApplication {
public static void main(String[] args) {
SpringApplication.run(CerberusApplication.class, args);
}
}

View File

@@ -0,0 +1,24 @@
package org.cerberus.controllers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
public class RobotsTxtController {
private static final Logger LOG = LoggerFactory.getLogger(RobotsTxtController.class);
@RequestMapping(value = "/robots.txt")
public void robots(HttpServletResponse response) {
try {
response.getWriter().write("User-agent: *\nDisallow: /\n");
} catch (IOException ex) {
LOG.info("Error during robots.txt serving.", ex);
}
}
}

View File

@@ -0,0 +1,41 @@
package org.cerberus.controllers;
import org.cerberus.core.config.security.CustomAuthenticationProvider;
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.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
@RestController
@RequestMapping("/api/users")
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/login")
public void login(@RequestBody User user, HttpServletResponse response) {
userService.authenticate(user);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
@GetMapping("/disconnection")
public void disconnection(final HttpServletRequest request, final HttpServletResponse response) {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if(auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
}

View File

@@ -0,0 +1,56 @@
package org.cerberus.core.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* Controller that catch errors from spring rest or spring security and others, and transform them to JSON response.
*/
@RestController
@RequestMapping("/error")
public class CustomErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
@Autowired
public CustomErrorController(ErrorAttributes errorAttributes) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
this.errorAttributes = errorAttributes;
}
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping
public Map<String, Object> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, getTraceParameter(request));
String trace = (String) body.get("trace");
if (trace != null) {
String[] lines = trace.split("\n\t");
body.put("trace", lines);
}
return body;
}
private boolean getTraceParameter(HttpServletRequest request) {
String parameter = request.getParameter("trace");
if (parameter == null) {
return false;
}
return !"false".equals(parameter.toLowerCase());
}
private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
return errorAttributes.getErrorAttributes(new ServletWebRequest(request), includeStackTrace);
}
}

View File

@@ -0,0 +1,41 @@
package org.cerberus.core.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EntityScan("org.cerberus.entities.persistence")
@EnableTransactionManagement
@EnableJpaRepositories("org.cerberus.repositories")
@PropertySource("classpath:application.yml")
public class JpaConfiguration {
@Value("${spring.jpa.datasource.driverClassName}")
private String driverClassName;
@Value("${spring.jpa.datasource.url}")
private String url;
@Value("${spring.jpa.datasource.username}")
private String username;
@Value("${spring.jpa.datasource.password}")
private String password;
@Bean(name = "dataSource")
public DataSource getDataSource() {
return DataSourceBuilder.create()
.username(username)
.password(password)
.url(url)
.driverClassName(driverClassName)
.build();
}
}

View File

@@ -0,0 +1,34 @@
package org.cerberus.core.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;
import java.io.IOException;
/**
* This configuration class serves Angular app if the url isn't available in sprint REST module.
*
* @author florian
*
*/
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**/*")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
Resource requestedResource = location.createRelative(resourcePath);
return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
: new ClassPathResource("/static/index.html");
}
});
}
}

View File

@@ -0,0 +1,38 @@
package org.cerberus.core.config.security;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
// Do nothing
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.addHeader("Access-Control-Allow-Origin", "*");
httpResponse.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, HEAD");
httpResponse.addHeader("Access-Control-Allow-Headers", "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
httpResponse.addHeader("Access-Control-Expose-Headers", "Access-Control-Allow-Origin, Access-Control-Allow-Credentials");
httpResponse.addHeader("Access-Control-Allow-Credentials", "true");
httpResponse.addIntHeader("Access-Control-Max-Age", 10);
chain.doFilter(request, httpResponse);
}
@Override
public void destroy() {
// Do nothing
}
}

View File

@@ -0,0 +1,32 @@
package org.cerberus.core.config.security;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// Creation of the authentication bean with its roles
Authentication auth = new UsernamePasswordAuthenticationToken(authentication.getName(),
authentication.getCredentials(), new ArrayList<>());
// Set the auth bean in spring security context
SecurityContextHolder.getContext().setAuthentication(auth);
return auth;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

View File

@@ -0,0 +1,26 @@
package org.cerberus.core.config.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Authentication entry point configured in
* {@link SecurityConfiguration#configure(org.springframework.security.config.annotation.web.builders.HttpSecurity)}
* to avoid yo get a login form at authentication failure from Angular app.
*
* @author takiguchi
*
*/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}

View File

@@ -0,0 +1,72 @@
package org.cerberus.core.config.security;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.BASIC_AUTH_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String XSRF_REPOSITORY_HEADER_NAME = "X-XSRF-TOKEN";
private CustomAuthenticationProvider authenticationProvider;
private RestAuthenticationEntryPoint authenticationEntryPoint;
public SecurityConfiguration(CustomAuthenticationProvider authenticationProvider,
RestAuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationProvider = authenticationProvider;
this.authenticationEntryPoint = authenticationEntryPoint;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// Permits all
.antMatchers(
"/robots.txt"
).permitAll()
.antMatchers(GET,
"/api/users/disconnection"
).permitAll()
.antMatchers(POST,
"/api/users/login"
).permitAll()
.anyRequest().permitAll()
.and()
// Allow to avoid login form at authentication failure from Angular app
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.and()
.addFilterAfter(new XSRFTokenFilter(), CsrfFilter.class)
.csrf()
.csrfTokenRepository(xsrfTokenRepository());
http.httpBasic();
http.csrf().disable();
}
private CsrfTokenRepository xsrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName(XSRF_REPOSITORY_HEADER_NAME);
return repository;
}
}

View File

@@ -0,0 +1,33 @@
package org.cerberus.core.config.security;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class XSRFTokenFilter extends OncePerRequestFilter {
private static final String XSRF_TOKEN_PATH = "/";
private static final String XSRF_TOKEN = "XSRF-TOKEN";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if(csrf != null) {
Cookie cookie = WebUtils.getCookie(request, XSRF_TOKEN);
String token = csrf.getToken();
if(cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie(XSRF_TOKEN, token);
cookie.setPath(XSRF_TOKEN_PATH);
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,15 @@
package org.cerberus.core.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class BadRequestException extends BusinessException {
public BadRequestException(String message) {
super(message);
}
public BadRequestException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,14 @@
package org.cerberus.core.exceptions;
/**
* Abstract class for all business exception that could be thrown at any time.
*/
abstract class BusinessException extends RuntimeException {
BusinessException(String message) {
super(message);
}
BusinessException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,74 @@
package org.cerberus.entities.persistence;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.Proxy;
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name="application")
@Proxy(lazy = false)
public class Application {
@Id
@Generated(GenerationTime.ALWAYS)
private String id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String serviceName;
@OneToMany(mappedBy = "application")
private List<ConfigurationFile> configurationFileList;
@ManyToMany
@JoinTable(
name = "administrator",
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "application_id", referencedColumnName = "id")
)
private List<User> administratorList;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public List<ConfigurationFile> getConfigurationFileList() {
return configurationFileList;
}
public void setConfigurationFileList(List<ConfigurationFile> configurationFileList) {
this.configurationFileList = configurationFileList;
}
public List<User> getAdministratorList() {
return administratorList;
}
public void setAdministratorList(List<User> administratorList) {
this.administratorList = administratorList;
}
}

View File

@@ -0,0 +1,47 @@
package org.cerberus.entities.persistence;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.Proxy;
import javax.persistence.*;
@Entity
@Table(name="configuration_file")
@Proxy(lazy = false)
public class ConfigurationFile {
@Id
@Generated(GenerationTime.ALWAYS)
private String id;
@Column(nullable = false)
private String path;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "application_id")
private Application application;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Application getApplication() {
return application;
}
public void setApplication(Application application) {
this.application = application;
}
}

View File

@@ -0,0 +1,87 @@
package org.cerberus.entities.persistence;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.Proxy;
import javax.persistence.*;
import java.time.LocalDate;
import java.util.List;
@Entity
@Table(name="`user`")
@Proxy(lazy = false)
public class User {
@Id
@Generated(GenerationTime.ALWAYS)
private String id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(name = "inscription_date", nullable = false)
@Generated(GenerationTime.ALWAYS)
private LocalDate inscriptionDate;
@ManyToMany
@JoinTable(
name = "administrator",
joinColumns = @JoinColumn(name = "application_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id")
)
private List<Application> applicationList;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public LocalDate getInscriptionDate() {
return inscriptionDate;
}
public void setInscriptionDate(LocalDate inscriptionDate) {
this.inscriptionDate = inscriptionDate;
}
public List<Application> getApplicationList() {
return applicationList;
}
public void setApplicationList(List<Application> applicationList) {
this.applicationList = applicationList;
}
}

View File

@@ -0,0 +1,13 @@
package org.cerberus.repositories;
import org.cerberus.entities.persistence.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, String> {
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
}

View File

@@ -0,0 +1,42 @@
package org.cerberus.services;
import org.cerberus.core.config.security.CustomAuthenticationProvider;
import org.cerberus.core.exceptions.BadRequestException;
import org.cerberus.entities.persistence.User;
import org.cerberus.repositories.UserRepository;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import javax.swing.text.html.Option;
import java.util.Collections;
import java.util.Optional;
@Service
public class UserService {
private CustomAuthenticationProvider authenticationProvider;
private UserRepository userRepository;
public UserService(CustomAuthenticationProvider authenticationProvider, UserRepository userRepository) {
this.authenticationProvider = authenticationProvider;
this.userRepository = userRepository;
}
public void authenticate(User user) {
checkCredentials(user.getEmail(), user.getPassword());
authenticationProvider.authenticate(new UsernamePasswordAuthenticationToken(
user.getEmail(),
user.getPassword(),
Collections.singleton(new SimpleGrantedAuthority("APPLICATION_ADMIN"))
));
}
void checkCredentials(String email, String password) {
Optional<User> optUser = userRepository.findByEmail(email);
if(optUser.isEmpty() || !optUser.get().getPassword().equals(password)) {
throw new BadRequestException("Credentials are incorrect.");
}
}
}

View File

@@ -0,0 +1,26 @@
logging:
level:
org:
hibernate:
SQL: DEBUG
server:
# use-forward-headers=true
error:
whitelabel:
enabled: false # Disable html error responses.
port: 8080
spring:
jpa:
datasource:
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/db_cerberus
username: cerberus
password: P@ssword
open-in-view: false
# Because detection is disabled you have to set correct dialect by hand.
database-platform: org.hibernate.dialect.PostgreSQL9Dialect
# Disable feature detection by this undocumented parameter.
# Check the org.hibernate.engine.jdbc.internal.JdbcServiceImpl.configure method for more details.
properties.hibernate.temp.use_jdbc_metadata_defaults: false

View File

@@ -0,0 +1,15 @@
***********
*******************
***********************
*********##*****##*********
*****#***###########***#***** ( )
*****#######*#####*#######***** )\ ( ( ( /( ( (
****#######################**** (((_) ))\ )( )\()) ))\ )( ( (
****#*########***########*#**** )\___ /((_|()\((_)\ /((_|()\ )\ )\
****#*#########*#########*#**** ((/ __(_)) ((_) |(_)(_)) ((_)((_|(_)
******##*#####***#####*##****** | (__/ -_)| '_| '_ \/ -_)| '_| | (_-<
******########***########****** \___\___||_| |_.__/\___||_| \___/__/
******#######***#######******
******######***######******
*******###***###*******
*******************

View File

@@ -0,0 +1,34 @@
CREATE TABLE "user" (
id uuid DEFAULT uuid_generate_v4(),
name VARCHAR NOT NULL,
email VARCHAR NOT NULL UNIQUE,
password VARCHAR NOT NULL,
inscription_date DATE DEFAULT current_date,
CONSTRAINT user_pk PRIMARY KEY (id)
);
CREATE TABLE application (
id uuid DEFAULT uuid_generate_v4(),
name VARCHAR NOT NULL,
service_name VARCHAR NOT NULL,
CONSTRAINT application_pk PRIMARY KEY (id)
);
CREATE TABLE configuration_file (
id uuid DEFAULT uuid_generate_v4(),
path VARCHAR NOT NULL,
application_id uuid NOT NULL,
CONSTRAINT configuration_file_pk PRIMARY KEY (id),
CONSTRAINT configuration_file_application_id_fk FOREIGN KEY (application_id) REFERENCES application (id)
);
CREATE INDEX configuration_file_application_id_idx ON configuration_file(application_id);
CREATE TABLE administrator (
user_id uuid NOT NULL,
application_id uuid NOT NULL,
CONSTRAINT administrator_pk PRIMARY KEY (user_id, application_id),
CONSTRAINT administrator_user_id FOREIGN KEY (user_id) REFERENCES "user" (id),
CONSTRAINT administrator_application_id FOREIGN KEY (application_id) REFERENCES application (id)
);
CREATE INDEX administrator_user_id_idx ON administrator(user_id);
CREATE INDEX administrator_application_id_idx ON administrator(application_id);