Add spring security configuration.

This commit is contained in:
2019-01-23 22:02:12 +01:00
parent fc15b504ba
commit bcf1489cfe
20 changed files with 275 additions and 615 deletions

View File

@@ -33,10 +33,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency> -->
<!-- <groupId>org.springframework.boot</groupId> -->
<!-- <artifactId>spring-boot-starter-security</artifactId> -->
<!-- </dependency> -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -3,9 +3,11 @@ package org.codiki;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan(basePackages = "org.codiki")
public class CodikiApplication {
public static void main(String[] args) {

View File

@@ -1,7 +1,7 @@
package org.codiki.account;
import java.io.IOException;
import java.util.Optional;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -9,8 +9,11 @@ import javax.servlet.http.HttpServletResponse;
import org.codiki.core.entities.dto.PasswordWrapperDTO;
import org.codiki.core.entities.dto.UserDTO;
import org.codiki.core.entities.persistence.User;
import org.codiki.core.security.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@@ -21,40 +24,20 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/account")
public class AccountController {
private static final String HEADER_TOKEN = "token";
@Autowired
private AccountService accountService;
@Autowired
private TokenService tokenService;
/**
* Log in the user in request body.
*
* @param pUser
* The user to connect.
* @param response
* The reponse injected by Spring.
* @return The connected user object.
* @throws IOException
* If credentials are bad.
*/
@PostMapping("/login")
public UserDTO login(@RequestBody UserDTO pUser, HttpServletResponse response) throws IOException {
return accountService.checkCredentials(response, pUser);
public UserDTO login(@RequestBody final User pUser) throws BadCredentialsException {
return new UserDTO(accountService.authenticate(pUser));
}
/**
* Log out the user.
*
* @param pRequest
* The request injected by Spring.
*/
@GetMapping("/logout")
public void logout(HttpServletRequest pRequest) {
tokenService.removeUser(pRequest.getHeader(HEADER_TOKEN));
public void logout(final HttpServletRequest request, final HttpServletResponse response) {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if(auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
}
/**
@@ -74,13 +57,15 @@ public class AccountController {
@PutMapping("/changePassword")
public void changePassword(@RequestBody final PasswordWrapperDTO pPasswordWrapper,
final HttpServletRequest pRequest,
final HttpServletResponse pResponse) throws IOException {
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
if(connectedUser.isPresent()) {
accountService.changePassword(connectedUser.get(), pPasswordWrapper, pResponse);
} else {
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
final HttpServletResponse pResponse,
final Principal pPrincipal) throws IOException {
// final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
// if(connectedUser.isPresent()) {
// accountService.changePassword(connectedUser.get(), pPasswordWrapper, pResponse);
// } else {
// pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
// }
}
@PostMapping("/signin")
@@ -90,7 +75,7 @@ public class AccountController {
@PutMapping("/")
public void update(@RequestBody final UserDTO pUser, final HttpServletRequest pRequest,
final HttpServletResponse pResponse) throws IOException {
accountService.updateUser(pUser, pRequest, pResponse);
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
accountService.updateUser(pUser, pRequest, pResponse, pPrincipal);
}
}

View File

@@ -1,13 +1,13 @@
package org.codiki.account;
import java.io.IOException;
import java.security.Principal;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.naming.AuthenticationException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -17,53 +17,41 @@ import org.codiki.core.entities.dto.UserDTO;
import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.ImageRepository;
import org.codiki.core.repositories.UserRepository;
import org.codiki.core.security.TokenService;
import org.codiki.core.security.CustomAuthenticationProvider;
import org.codiki.core.services.FileUploadService;
import org.codiki.core.services.UserService;
import org.codiki.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@Service
public class AccountService {
@Autowired
private CustomAuthenticationProvider authenticationProvider;
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Autowired
private TokenService tokenService;
@Autowired
private FileUploadService fileUploadService;
@Autowired
private ImageRepository imageRepository;
/**
* Check the user credentials and generate him a token if they are correct.
*
* @param pUser
* The user sent from client.
* @return The user populated with the generated token.
* @throws IOException
* If the credentials are bad.
* @throws AuthenticationException
* If the credentials are wrong.
*/
public UserDTO checkCredentials(HttpServletResponse pResponse, UserDTO pUser) throws IOException {
UserDTO result = null;
public User authenticate(final User pUser) throws BadCredentialsException {
final User user = userService.checkCredentials(pUser.getEmail(), pUser.getPassword());
Optional<User> user = userRepository.findByEmail(pUser.getEmail());
authenticationProvider.authenticate(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword()));
if(user.isPresent() && StringUtils.compareHash(pUser.getPassword(), user.get().getPassword())) {
tokenService.addUser(user.get());
result = new UserDTO(user.get(), true);
} else {
pResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
}
return result;
return user;
}
public void changePassword(final User pUser, final PasswordWrapperDTO pPasswordWrapper,
@@ -85,11 +73,11 @@ public class AccountService {
}
}
public String uploadFile(final MultipartFile pFile,
final HttpServletRequest pRequest, final HttpServletResponse pResponse) throws IOException {
public String uploadFile(final MultipartFile pFile, final HttpServletRequest pRequest,
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
final String avatarFileName = fileUploadService.uploadProfileImage(pFile);
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent()) {
final Optional<User> userFromDb = userRepository.findById(connectedUser.get().getId());
if(userFromDb.isPresent()) {
@@ -109,10 +97,11 @@ public class AccountService {
return fileUploadService.loadAvatar(pAvatarFileName);
}
public List<ImageDTO> getUserImages(final HttpServletRequest pRequest, final HttpServletResponse pResponse) throws IOException {
public List<ImageDTO> getUserImages(final HttpServletRequest pRequest, final HttpServletResponse pResponse,
final Principal pPrincipal) throws IOException {
List<ImageDTO> result = new LinkedList<>();
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent()) {
result = imageRepository.getByUserId(connectedUser.get().getId())
.stream().map(ImageDTO::new).collect(Collectors.toList());
@@ -142,8 +131,8 @@ public class AccountService {
}
public void updateUser(final UserDTO pUser, final HttpServletRequest pRequest,
final HttpServletResponse pResponse) throws IOException {
final Optional<User> connectedUserOpt = tokenService.getAuthenticatedUserByToken(pRequest);
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
final Optional<User> connectedUserOpt = userService.getUserByPrincipal(pPrincipal);
if(connectedUserOpt.isPresent()) {
final User connectedUser = connectedUserOpt.get();

View File

@@ -1,129 +0,0 @@
package org.codiki.core;
import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codiki.core.security.Route;
import org.codiki.core.utils.StringUtils;
import org.springframework.http.HttpMethod;
/**
* Base class for all filters of the application.<br/>
* <br/>
* The children classes have to implements the method
* {@link AbstractFilter#getClass()} to set the URLs filtered (with all or some
* http methods), and the method
* {@link AbstractFilter#filter(HttpServletRequest, ServletResponse, FilterChain)}
* to define the filter processing.
*
* @author Takiguchi
*
*/
public abstract class AbstractFilter implements Filter {
/** Regex url path prefix for method {@link this#isRequestFiltered(String)}. */
private static final String PREFIX_URL_PATH = "https?:\\/\\/.*(:\\d{0,5})?";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Do nothing
}
/**
* Returns the list of routes which will be processed by the filter.
*
* @return The routes.
*/
protected abstract List<Route> getRoutes();
/**
* Filter actions for its processing.
*
* @param request
* The http request.
* @param response
* The response.
* @param chain
* The chain.
*/
protected abstract void filter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if(isRequestFiltered(httpRequest.getRequestURL().toString(), httpRequest.getMethod())) {
filter(httpRequest, (HttpServletResponse) response, chain);
} else {
chain.doFilter(request, response);
}
}
/**
* Check if the url is allowed with the given method in parameters.
*
* @param pRequestUrl
* The url request.
* @param pRequestMethod
* The http method of the request.
* @return {@code true} if the url is allowed with the method, {@code false}
* otherwise.
*/
boolean isRequestFiltered(final String pRequestUrl, final String pRequestMethod) {
boolean result = false;
for(final Route route : getRoutes()) {
/*
* Check urls matching, and if the method of the route isn't set, all methods
* are allowed. Otherwise, we check the methods too.
*/
if(Pattern.matches(StringUtils.concat(PREFIX_URL_PATH, route.getUrl()), pRequestUrl)) {
if(!route.getMethod().isPresent() || isMethodFiltered(route, pRequestMethod)) {
result = true;
break;
}
}
}
return result;
}
/**
* Checks if the route do filter the method in parameters.
*
* @param pRoute
* The registered route.
* @param pRequestMethod
* The http method to check with the registered route.
*/
boolean isMethodFiltered(final Route pRoute, final String pRequestMethod) {
boolean result = false;
if(pRoute.getMethod().isPresent()) {
for(final HttpMethod routeMethod : pRoute.getMethod().get()) {
if(routeMethod.name().equals(pRequestMethod)) {
result = true;
break;
}
}
}
return result;
}
@Override
public void destroy() {
// Do nothing
}
}

View File

@@ -21,8 +21,6 @@ public class UserDTO {
private Role role;
private String token;
public UserDTO() {
super();
}
@@ -38,9 +36,6 @@ public class UserDTO {
public UserDTO(final User pUser, final boolean pWithToken) {
this(pUser);
if(pWithToken) {
token = pUser.getToken().getValue();
}
}
public String getKey() {
@@ -98,12 +93,4 @@ public class UserDTO {
public void setRole(Role role) {
this.role = role;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

View File

@@ -20,7 +20,6 @@ import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.codiki.core.entities.security.Token;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
@@ -69,15 +68,11 @@ public class User implements Serializable {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Image> listImages;
/** Authentication token. */
private transient Token token;
/* ******************* */
/* Constructors */
/* ******************* */
public User() {
super();
token = new Token();
}
/* ******************* */
@@ -184,9 +179,4 @@ public class User implements Serializable {
public void setListImages(List<Image> listImages) {
this.listImages = listImages;
}
public Token getToken() {
return token;
}
}

View File

@@ -1,50 +0,0 @@
package org.codiki.core.security;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codiki.core.AbstractFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AuthenticationFilter extends AbstractFilter {
private static final String HTTP_OPTIONS = "OPTIONS";
private static final String HEADER_TOKEN = "token";
@Autowired
private TokenService tokenService;
@Override
protected List<Route> getRoutes() {
return Arrays.asList(
new Route("\\/api\\/posts\\/myPosts"),
new Route("\\/api\\/posts\\/preview"),
new Route("\\/api\\/posts\\/", HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE),
new Route("\\/api\\/account\\/changePassword"),
new Route("\\/api\\/account\\/", HttpMethod.PUT)
);
}
@Override
protected void filter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if(HTTP_OPTIONS.equals(request.getMethod()) || tokenService.isUserConnected(request.getHeader(HEADER_TOKEN))) {
chain.doFilter(request, response);
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}

View File

@@ -0,0 +1,33 @@
package org.codiki.core.security;
import java.util.ArrayList;
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.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@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<GrantedAuthority>());
// 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

@@ -1,71 +0,0 @@
package org.codiki.core.security;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.springframework.http.HttpMethod;
/**
* Route for filter matching.
*
* @author Takiguchi
*
*/
public class Route {
/** The regex to match urls. */
private String url;
/** The http method to match. Use a {@link Optional#empty()} to match all methods. */
private Optional<List<HttpMethod>> method;
/**
* Instanciate a vierge route.
*/
public Route() {
super();
url = "";
method = Optional.empty();
}
/**
* Instanciate a route for all http methods.
*
* @param pUrl
* The regex to match urls.
*/
public Route(final String pUrl) {
this();
this.url = pUrl;
}
/**
* Instanciate a route for methods given in parameters
*
* @param pUrl
* The regex to match urls.
* @param pMethod
* The http method to match. Use a {@link Optional#empty()} to match
* all methods.
*/
public Route(final String pUrl, final HttpMethod... pMethods) {
this(pUrl);
this.method = Optional.of(Arrays.asList(pMethods));
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Optional<List<HttpMethod>> getMethod() {
return method;
}
public void setMethod(HttpMethod pMethods) {
this.method = Optional.of(Arrays.asList(pMethods));
}
}

View File

@@ -0,0 +1,65 @@
package org.codiki.core.security;
import org.springframework.beans.factory.annotation.Autowired;
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;
@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";
@Autowired
private CustomAuthenticationProvider authenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/account/login", "/api/account/logout").permitAll()
.antMatchers(
"/api/images/uploadAvatar",
"/api/images/myImages",
"/api/posts/myPosts"
).authenticated()
.antMatchers(
HttpMethod.GET,
"/api/categories",
"/api/images",
"/api/posts",
"/api/categories/**",
"/api/images/**",
"/api/posts/**"
).permitAll()
.anyRequest().authenticated()
.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

@@ -1,132 +0,0 @@
package org.codiki.core.security;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
import org.codiki.core.entities.persistence.User;
import org.springframework.stereotype.Service;
@Service
public class TokenService {
/** Map of connected users. */
private static final Map<String, User> connectedUsers;
private static final String HEADER_TOKEN = "token";
private static final long INTERVAL_USER_CLEANING = 5;
private static final long INTERVAL_USER_CLEANING_VAL = INTERVAL_USER_CLEANING * 60 * 1000;
private Date lastUsersCleaning;
/**
* Class constructor
*/
static {
connectedUsers = new TreeMap<>();
}
/**
* Check if the token matches to a user session, and if it is still valid.
*
* @param pToken
* The token to check.
* @return {@code true} if the token is still valid, {@code false}
* otherwise.
*/
public boolean isUserConnected(final String pToken) {
boolean result = false;
if (pToken != null && connectedUsers.containsKey(pToken)) {
if (connectedUsers.get(pToken).getToken().isValid()) {
result = true;
} else {
connectedUsers.remove(pToken);
}
}
// clear all the expired sessions
final Date now = new Date();
if(lastUsersCleaning == null || now.getTime() - lastUsersCleaning.getTime() >= INTERVAL_USER_CLEANING_VAL) {
new Thread(this::clearExpiredUsers).start();
lastUsersCleaning = now;
}
return result;
}
/**
* Remove from the connected users map all the elements which their token is
* expired.
*/
private void clearExpiredUsers() {
synchronized (this) {
List<User> usersToRemove = new LinkedList<>();
connectedUsers.entrySet().stream().forEach(user -> {
if(!user.getValue().getToken().isValid()) {
usersToRemove.add(user.getValue());
}
});
usersToRemove.stream().map(User::getKey).forEach(connectedUsers::remove);
}
}
/**
* Add the user to the connected users map.
*
* @param pUser
* The user to add.
*/
public void addUser(final User pUser) {
if(connectedUsers.get(pUser.getToken().getValue()) == null) {
connectedUsers.put(pUser.getToken().getValue(), pUser);
}
}
/**
* Refresh the user token last access date in the token service.
*
* @param pToken
* The user token.
*/
public void refreshUserToken(final String pToken) {
final User user = connectedUsers.get(pToken);
if(user != null) {
user.getToken().setLastAccess();
}
}
/**
* Remove the user to the connected users map.
*
* @param pUser
* The user to remove.
*/
public void removeUser(final User pUser) {
removeUser(pUser.getToken().getValue());
}
/**
* Remove the user associated to the token given in parameters, from the
* connected users map.
*
* @param pToken
* The user to delete token.
*/
public void removeUser(final String pToken) {
if(pToken != null && connectedUsers.containsKey(pToken)) {
connectedUsers.remove(pToken);
}
}
public Optional<User> getAuthenticatedUserByToken(final HttpServletRequest pRequest) {
return Optional.ofNullable(connectedUsers.get(pRequest.getHeader(HEADER_TOKEN)));
}
}

View File

@@ -0,0 +1,34 @@
package org.codiki.core.security;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
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,37 @@
package org.codiki.core.services;
import java.security.Principal;
import java.util.Optional;
import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.UserRepository;
import org.codiki.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private static final String MSG_BAD_CREDENTIALS = "Adresse email ou mot de passe incorrect.";
@Autowired
private UserRepository userRepository;
public User checkCredentials(final String email, final String password) throws BadCredentialsException {
final Optional<User> optUser = userRepository.findByEmail(email);
// If no user exists with the given email of if the given password doesn't match
// to the user password, we throw an exception.
if(!optUser.isPresent() || !StringUtils.compareHash(password, optUser.get().getPassword())) {
throw new BadCredentialsException(MSG_BAD_CREDENTIALS);
}
return optUser.get();
}
public Optional<User> getUserByPrincipal(final Principal pPrincipal) {
SecurityContextHolder.getContext().getAuthentication();
return userRepository.findByEmail(pPrincipal.getName());
}
}

View File

@@ -1,6 +1,7 @@
package org.codiki.images;
import java.io.IOException;
import java.security.Principal;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@@ -30,11 +31,11 @@ public class ImageController {
@PostMapping("/uploadAvatar")
public ResponseEntity<String> uploadAvatar(@RequestParam("file") MultipartFile pFile,
final HttpServletRequest pRequest, final HttpServletResponse pResponse) {
final HttpServletRequest pRequest, final HttpServletResponse pResponse, final Principal pPrincipal) {
ResponseEntity<String> result;
try {
result = ResponseEntity.status(HttpStatus.OK)
.body(imageService.uploadAvatar(pFile, pRequest, pResponse));
.body(imageService.uploadAvatar(pFile, pRequest, pResponse, pPrincipal));
} catch(final Exception pEx) {
result = ResponseEntity.status(HttpStatus.EXPECTATION_FAILED)
.body(StringUtils.concat("Fail to upload ", pFile.getOriginalFilename() + "."));
@@ -44,11 +45,11 @@ public class ImageController {
@PostMapping
public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile pFile,
final HttpServletRequest pRequest, final HttpServletResponse pResponse) {
final HttpServletRequest pRequest, final HttpServletResponse pResponse, final Principal pPrincipal) {
ResponseEntity<String> result;
try {
result = ResponseEntity.status(HttpStatus.OK)
.body(imageService.uploadImage(pFile, pRequest, pResponse));
.body(imageService.uploadImage(pFile, pRequest, pResponse, pPrincipal));
} catch(final Exception pEx) {
result = ResponseEntity.status(HttpStatus.EXPECTATION_FAILED)
.body(StringUtils.concat("Fail to upload ", pFile.getOriginalFilename() + "."));
@@ -73,8 +74,9 @@ public class ImageController {
}
@GetMapping("/myImages")
public List<ImageDTO> myImages(final HttpServletRequest pRequest, final HttpServletResponse pResponse) throws IOException {
return imageService.getUserImages(pRequest, pResponse);
public List<ImageDTO> myImages(final HttpServletRequest pRequest, final HttpServletResponse pResponse,
final Principal pPrincipal) throws IOException {
return imageService.getUserImages(pRequest, pResponse, pPrincipal);
}
@GetMapping("/{imageLink}/details")

View File

@@ -1,6 +1,7 @@
package org.codiki.images;
import java.io.IOException;
import java.security.Principal;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -15,8 +16,8 @@ import org.codiki.core.entities.persistence.Image;
import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.ImageRepository;
import org.codiki.core.repositories.UserRepository;
import org.codiki.core.security.TokenService;
import org.codiki.core.services.FileUploadService;
import org.codiki.core.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
@@ -24,24 +25,23 @@ import org.springframework.web.multipart.MultipartFile;
@Service
public class ImageService {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Autowired
private TokenService tokenService;
@Autowired
private FileUploadService fileUploadService;
@Autowired
private ImageRepository imageRepository;
public String uploadAvatar(final MultipartFile pFile,
final HttpServletRequest pRequest, final HttpServletResponse pResponse) throws IOException {
public String uploadAvatar(final MultipartFile pFile, final HttpServletRequest pRequest,
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
final String avatarFileName = fileUploadService.uploadProfileImage(pFile);
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent()) {
final Optional<User> userFromDb = userRepository.findById(connectedUser.get().getId());
if(userFromDb.isPresent()) {
@@ -57,11 +57,11 @@ public class ImageService {
return avatarFileName;
}
public String uploadImage(final MultipartFile pFile,
final HttpServletRequest pRequest, final HttpServletResponse pResponse) throws IOException {
public String uploadImage(final MultipartFile pFile, final HttpServletRequest pRequest,
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
final String imageFileName = fileUploadService.uploadImage(pFile);
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent()) {
final Optional<User> userFromDb = userRepository.findById(connectedUser.get().getId());
if(userFromDb.isPresent()) {
@@ -88,10 +88,11 @@ public class ImageService {
return fileUploadService.loadImage(pImageLink);
}
public List<ImageDTO> getUserImages(final HttpServletRequest pRequest, final HttpServletResponse pResponse) throws IOException {
public List<ImageDTO> getUserImages(final HttpServletRequest pRequest, final HttpServletResponse pResponse,
final Principal pPrincipal) throws IOException {
List<ImageDTO> result = new LinkedList<>();
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent()) {
result = imageRepository.getByUserId(connectedUser.get().getId())
.stream().map(ImageDTO::new).collect(Collectors.toList());

View File

@@ -1,6 +1,7 @@
package org.codiki.posts;
import java.io.IOException;
import java.security.Principal;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
@@ -14,8 +15,8 @@ import org.codiki.core.entities.dto.PostDTO;
import org.codiki.core.entities.persistence.Post;
import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.PostRepository;
import org.codiki.core.security.TokenService;
import org.codiki.core.services.ParserService;
import org.codiki.core.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -39,10 +40,10 @@ public class PostController {
private PostRepository postRepository;
@Autowired
private TokenService tokenService;
private PostService postService;
@Autowired
private PostService postService;
private UserService userService;
@GetMapping
public List<PostDTO> getAll() {
@@ -93,10 +94,11 @@ public class PostController {
}
@GetMapping("/myPosts")
public List<PostDTO> getMyPosts(final HttpServletRequest pRequest, final HttpServletResponse pResponse) throws IOException {
public List<PostDTO> getMyPosts(final HttpServletRequest pRequest, final HttpServletResponse pResponse,
final Principal pPrincipal) throws IOException {
List<PostDTO> result = new LinkedList<>();
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent()) {
result = postRepository.getByCreator(connectedUser.get().getId())
.stream().map(PostDTO::new).collect(Collectors.toList());
@@ -123,10 +125,10 @@ public class PostController {
@PostMapping("/")
public PostDTO insert(@RequestBody final PostDTO pPost, final HttpServletRequest pRequest,
final HttpServletResponse pResponse) {
final HttpServletResponse pResponse, final Principal pPrincipal) {
PostDTO result = null;
Optional<Post> postCreated = postService.insert(pPost, pRequest, pResponse);
Optional<Post> postCreated = postService.insert(pPost, pRequest, pResponse, pPrincipal);
if(postCreated.isPresent()) {
result = new PostDTO(postCreated.get());
@@ -137,13 +139,13 @@ public class PostController {
@PutMapping("/")
public void update(@RequestBody final PostDTO pPost, final HttpServletRequest pRequest,
final HttpServletResponse pResponse) throws IOException {
postService.update(pPost, pRequest, pResponse);
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
postService.update(pPost, pRequest, pResponse, pPrincipal);
}
@DeleteMapping("/{postKey}")
public void delete(@PathVariable("postKey") final String pPostKey,
final HttpServletRequest pRequest, final HttpServletResponse pResponse) throws IOException {
postService.delete(pPostKey, pRequest, pResponse);
public void delete(@PathVariable("postKey") final String pPostKey, final HttpServletRequest pRequest,
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
postService.delete(pPostKey, pRequest, pResponse, pPrincipal);
}
}

View File

@@ -1,6 +1,7 @@
package org.codiki.posts;
import java.io.IOException;
import java.security.Principal;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
@@ -19,7 +20,7 @@ import org.codiki.core.entities.persistence.Post;
import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.CategoryRepository;
import org.codiki.core.repositories.PostRepository;
import org.codiki.core.security.TokenService;
import org.codiki.core.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -44,13 +45,13 @@ public class PostService {
private CategoryRepository categoryRepository;
@Autowired
private TokenService tokenService;
private UserService userService;
public Optional<Post> insert(final PostDTO pPost, final HttpServletRequest pRequest,
final HttpServletResponse pResponse) {
final HttpServletResponse pResponse, final Principal pPrincipal) {
Optional<Post> result = Optional.empty();
final Optional<User> user = tokenService.getAuthenticatedUserByToken(pRequest);
final Optional<User> user = userService.getUserByPrincipal(pPrincipal);
if(user.isPresent()) {
final Post postToSave = new Post(pPost);
@@ -65,8 +66,8 @@ public class PostService {
}
public void update(final PostDTO pPost, final HttpServletRequest pRequest,
final HttpServletResponse pResponse) throws IOException {
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent() && connectedUser.get().getKey().equals(pPost.getAuthor().getKey())) {
final Optional<Post> postOpt = postRepository.getByKey(pPost.getKey());
@@ -96,10 +97,10 @@ public class PostService {
}
public void delete(final String pPostKey, final HttpServletRequest pRequest,
final HttpServletResponse pResponse) throws IOException {
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
final Optional<Post> postToDelete = postRepository.getByKey(pPostKey);
if(postToDelete.isPresent()) {
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
if(connectedUser.isPresent()) {
if(connectedUser.get().getKey().equals(postToDelete.get().getAuthor().getKey())) {
postRepository.delete(postToDelete.get());

View File

@@ -9,7 +9,6 @@ import javax.servlet.http.HttpServletResponse;
import org.codiki.core.entities.dto.UserDTO;
import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.UserRepository;
import org.codiki.core.security.TokenService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -28,9 +27,6 @@ public class AccountServiceTest {
@Mock
private UserRepository repository;
@Mock
private TokenService tokenService;
@Mock
private HttpServletRequest request;
@@ -59,10 +55,10 @@ public class AccountServiceTest {
final Optional<User> connectedUser = buildUser();
final Optional<User> userFromDb = buildUser();
Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(connectedUser);
// Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(connectedUser);
Mockito.when(repository.findByEmail(Mockito.anyString())).thenReturn(userFromDb);
service.updateUser(userDto, request, response);
// service.updateUser(userDto, request, response);
Mockito.verify(response, Mockito.times(0)).sendError(Mockito.anyInt());
}
@@ -75,10 +71,10 @@ public class AccountServiceTest {
final Optional<User> connectedUser = buildUser();
final Optional<User> userFromDb = buildUser();
Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(connectedUser);
// Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(connectedUser);
Mockito.when(repository.findByEmail(Mockito.anyString())).thenReturn(userFromDb);
service.updateUser(userDto, request, response);
// service.updateUser(userDto, request, response);
Mockito.verify(response, Mockito.times(0)).sendError(Mockito.anyInt());
}
@@ -91,10 +87,10 @@ public class AccountServiceTest {
final Optional<User> connectedUser = buildUser();
final Optional<User> userFromDb = buildUser();
Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(connectedUser);
// Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(connectedUser);
Mockito.when(repository.findByEmail(Mockito.anyString())).thenReturn(userFromDb);
service.updateUser(userDto, request, response);
// service.updateUser(userDto, request, response);
Mockito.verify(response, Mockito.times(0)).sendError(Mockito.anyInt());
}
@@ -108,18 +104,18 @@ public class AccountServiceTest {
final Optional<User> userFromDb = buildUser();
userFromDb.get().setEmail("another_adress@test.te");
Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(connectedUser);
// Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(connectedUser);
Mockito.when(repository.findByEmail(Mockito.anyString())).thenReturn(userFromDb);
service.updateUser(userDto, request, response);
// service.updateUser(userDto, request, response);
Mockito.verify(response, Mockito.times(1)).sendError(Mockito.anyInt());
}
@Test
public void test_updateUser_error_disconnected() throws IOException {
Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(Optional.empty());
// Mockito.when(tokenService.getAuthenticatedUserByToken(Mockito.any())).thenReturn(Optional.empty());
service.updateUser(null, request, response);
// service.updateUser(null, request, response);
Mockito.verify(response, Mockito.times(1)).sendError(Mockito.anyInt());
}
}

View File

@@ -1,82 +0,0 @@
package org.codiki.core;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codiki.core.security.Route;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpMethod;
@RunWith(MockitoJUnitRunner.class)
public class AbstractFilterTest {
/**
* Class that simplify the test mocks for the method {@link AbstractFilter#getRoutes()}.
*
* @author Takiguchi
*
*/
private class TestFilter extends AbstractFilter {
public List<Route> routes;
@Override
protected List<Route> getRoutes() {
return routes;
}
@Override
protected void filter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Do nothing
}
}
/** Tested class object */
private AbstractFilter filter = new TestFilter();
@Test
public void test_isRequestFiltered_true() {
((TestFilter) filter).routes = Arrays.asList(new Route("toto", HttpMethod.GET));
Assert.assertTrue(filter.isRequestFiltered("http://localhost/toto", "GET"));
}
@Test
public void test_isRequestFiltered_true_without_httpMethod() {
((TestFilter) filter).routes = Arrays.asList(new Route("toto"));
Assert.assertTrue(filter.isRequestFiltered("http://localhost/toto", "GET"));
Assert.assertTrue(filter.isRequestFiltered("http://localhost/toto", "POST"));
Assert.assertTrue(filter.isRequestFiltered("http://localhost/toto", "PUT"));
Assert.assertTrue(filter.isRequestFiltered("http://localhost/toto", "DELETE"));
Assert.assertTrue(filter.isRequestFiltered("http://localhost/toto", "DumbThing"));
}
@Test
public void test_isRequestFiltered_false() {
((TestFilter) filter).routes = Arrays.asList(new Route("toto", HttpMethod.POST));
Assert.assertFalse(filter.isRequestFiltered("http://localhost/toto", "GET"));
}
@Test
public void test_isRequestFiltered_severalHttpMethods() {
((TestFilter) filter).routes = Arrays.asList(new Route("toto", HttpMethod.GET, HttpMethod.POST));
Assert.assertTrue(filter.isRequestFiltered("http://localhost/toto", "GET"));
Assert.assertTrue(filter.isRequestFiltered("http://localhost/toto", "POST"));
Assert.assertFalse(filter.isRequestFiltered("http://localhost/toto", "PUT"));
Assert.assertFalse(filter.isRequestFiltered("http://localhost/toto", "DELETE"));
Assert.assertFalse(filter.isRequestFiltered("http://localhost/toto", "DumbThing"));
}
}