Initial commit.

This commit is contained in:
Florian
2018-03-16 22:49:47 +01:00
commit c587ff09a8
27 changed files with 1902 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,42 @@
package org.codiki.config;
import javax.sql.DataSource;
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;
@Configuration
@EntityScan("org.codiki")
@EnableTransactionManagement
@EnableJpaRepositories("org.codiki")
@PropertySource("classpath:application.properties")
public class JpaConfiguration {
@Value("${spring.datasource.driverClassName}")
private String driverClassName;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.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,103 @@
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 org.codiki.security.Route;
import org.codiki.utils.StringUtils;
/**
* 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, ServletResponse 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, response, chain);
} else {
chain.doFilter(request, response);
}
}
/**
* Check if the url is allowed with the given method in parameters.
*
* @param pUrlRequest
* The url request.
* @param pMethodRequest
* The http method of the request.
* @return {@code true} if the url is allowed with the method, {@code false}
* otherwise.
*/
boolean isRequestFiltered(final String pUrlRequest, final String pMethodRequest) {
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.
*/
result = Pattern.matches(StringUtils.concat(PREFIX_URL_PATH, route.getUrl()), pUrlRequest)
&& (!route.getMethod().isPresent() || pMethodRequest.equals(route.getMethod().get().toString()));
if(result) {
break;
}
}
return result;
}
@Override
public void destroy() {
// Do nothing
}
}

View File

@@ -0,0 +1,103 @@
package org.codiki.entities.dto;
import java.util.Date;
import org.codiki.entities.persistence.Role;
import org.codiki.entities.persistence.User;
public class UserDAO {
private String key;
private String name;
private String email;
private String password;
private String image;
private Date inscriptionDate;
private Role role;
private String token;
public UserDAO() {
super();
}
public UserDAO(final User pUser) {
key = pUser.getKey();
name = pUser.getName();
email = pUser.getEmail();
image = pUser.getImage();
inscriptionDate = pUser.getInscriptionDate();
role = pUser.getRole();
token = pUser.getToken().getValue();
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
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 String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public Date getInscriptionDate() {
return inscriptionDate;
}
public void setInscriptionDate(Date inscriptionDate) {
this.inscriptionDate = inscriptionDate;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

View File

@@ -0,0 +1,78 @@
package org.codiki.entities.persistence;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name="category")
@Inheritance(strategy = InheritanceType.JOINED)
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
/* ******************* */
/* Attributes */
/* ******************* */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
/* ******************* */
/* Relations */
/* ******************* */
@ManyToOne
@JoinColumn(name = "creator_id")
protected User creator;
@OneToMany(mappedBy = "mainCategory")
private List<SubCategory> listSubCategories;
@OneToMany(mappedBy = "category")
protected List<Post> listPosts;
/* ******************* */
/* Getters & Setters */
/* ******************* */
public Long getId() {
return id;
}
public void setId(Long id) {
if(this.id != null) {
throw new IllegalAccessError("It's not allowed to rewrite the id entity.");
}
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getCreator() {
return creator;
}
public void setCreator(User creator) {
this.creator = creator;
}
public List<SubCategory> getListSubCategories() {
return listSubCategories;
}
}

View File

@@ -0,0 +1,102 @@
package org.codiki.entities.persistence;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name="comment")
public class Comment implements Serializable {
private static final long serialVersionUID = 1L;
/* ******************* */
/* Attributes */
/* ******************* */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String key;
private String text;
@Column(name = "creation_date")
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
/* ******************* */
/* Relations */
/* ******************* */
@ManyToOne
@JoinColumn(name = "author")
private User author;
@OneToMany(mappedBy = "comment")
private List<CommentHistory> history;
/* ******************* */
/* Getters & Setters */
/* ******************* */
public Long getId() {
return id;
}
public void setId(Long id) {
if(this.id != null) {
throw new IllegalAccessError("It's not allowed to rewrite the id entity.");
}
this.id = id;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
public List<CommentHistory> getHistory() {
return history;
}
public void setHistory(List<CommentHistory> history) {
this.history = history;
}
}

View File

@@ -0,0 +1,80 @@
package org.codiki.entities.persistence;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name="comment_history")
public class CommentHistory implements Serializable {
private static final long serialVersionUID = 1L;
/* ******************* */
/* Attributes */
/* ******************* */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String text;
/* ******************* */
/* Relations */
/* ******************* */
@Column(name = "update_date")
@Temporal(TemporalType.TIMESTAMP)
private Date updateDate;
@ManyToOne
@JoinColumn(name = "comment_id")
private Comment comment;
/* ******************* */
/* Getters & Setters */
/* ******************* */
public Long getId() {
return id;
}
public void setId(Long id) {
if(this.id != null) {
throw new IllegalAccessError("It's not allowed to rewrite the id entity.");
}
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Date getUpdateDate() {
return updateDate;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
public Comment getComment() {
return comment;
}
public void setComment(Comment comment) {
this.comment = comment;
}
}

View File

@@ -0,0 +1,72 @@
package org.codiki.entities.persistence;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name="image")
public class Image implements Serializable {
private static final long serialVersionUID = 1L;
/* ******************* */
/* Attributes */
/* ******************* */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String link;
@Temporal(TemporalType.TIMESTAMP)
private Date date;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
/* ******************* */
/* Getters & Setters */
/* ******************* */
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

View File

@@ -0,0 +1,177 @@
package org.codiki.entities.persistence;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.codiki.utils.DateUtils;
@Entity
@Table(name="post")
public class Post implements Serializable {
private static final long serialVersionUID = 1L;
/** Number of character that compose the text extract. */
private static final short EXTRACT_LENGTH = 250;
/* ******************* */
/* Attributes */
/* ******************* */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String key;
private String title;
private String text;
@Column(length = 250)
private String description;
private String image;
@Column(name = "creation_date")
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
/* ******************* */
/* Relations */
/* ******************* */
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "creator_id")
private User author;
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE)
private List<PostHistory> history;
/* ******************* */
/* Constructors */
/* ******************* */
public Post() {
super();
}
/* ******************* */
/* Getters & Setters */
/* ******************* */
public Long getId() {
return id;
}
public void setId(Long id) {
if(this.id != null) {
throw new IllegalAccessError("It's not allowed to rewrite the id entity.");
}
this.id = id;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public String getCreationDateStr() {
if(creationDate == null) {
return "";
} else {
return DateUtils.format(creationDate);
}
}
public String getTextExtract() {
String extract = text;
if(extract != null && extract.length() > EXTRACT_LENGTH) {
extract = extract.substring(0, EXTRACT_LENGTH) + "...";
}
return extract;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public List<PostHistory> getHistory() {
return history;
}
public void setHistory(List<PostHistory> history) {
this.history = history;
}
}

View File

@@ -0,0 +1,82 @@
package org.codiki.entities.persistence;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name="post_history")
public class PostHistory implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String text;
@Column(name = "update_date")
@Temporal(TemporalType.TIMESTAMP)
private Date updateDate;
@ManyToOne
@JoinColumn(name = "post_id")
private Post post;
public Long getId() {
return id;
}
public void setId(Long id) {
if(this.id != null) {
throw new IllegalAccessError("It's not allowed to rewrite the id entity.");
}
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Date getUpdateDate() {
return updateDate;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post = post;
}
}

View File

@@ -0,0 +1,38 @@
package org.codiki.entities.persistence;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="role")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,36 @@
package org.codiki.entities.persistence;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = "sub_category")
public class SubCategory extends Category {
private static final long serialVersionUID = 1L;
/* ******************* */
/* Relations */
/* ******************* */
@ManyToOne
@JoinColumn(name = "main_category")
private Category mainCategory;
/* ******************* */
/* Getters & Setters */
/* ******************* */
public Category getMainCategory() {
return mainCategory;
}
public void setMainCategory(Category mainCategory) {
this.mainCategory = mainCategory;
}
public List<Post> getListPosts() {
return listPosts;
}
}

View File

@@ -0,0 +1,188 @@
package org.codiki.entities.persistence;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.codiki.entities.security.Token;
@Entity
@Table(name="`user`")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/* ******************* */
/* Attributes */
/* ******************* */
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="user_id_seq")
@SequenceGenerator(name="user_id_seq", sequenceName="user_id_seq", allocationSize=1)
private Long id;
private String key;
private String name;
private String email;
private String password;
private String image;
@Column(name = "inscription_date")
@Temporal(TemporalType.TIMESTAMP)
private Date inscriptionDate;
/* ******************* */
/* Relations */
/* ******************* */
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "role_id")
private Role role;
@OneToMany(mappedBy = "author")
private List<Post> listPosts;
@OneToMany(mappedBy = "author")
private List<Comment> listComments;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Image> listImages;
/** Authentication token. */
private transient Token token;
/* ******************* */
/* Constructors */
/* ******************* */
public User() {
super();
token = new Token();
}
/* ******************* */
/* Getters & Setters */
/* ******************* */
public Long getId() {
return id;
}
public void setId(Long id) {
if(this.id != null) {
throw new IllegalAccessError("It's not allowed to rewrite the id entity.");
}
this.id = id;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
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 String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public Date getInscriptionDate() {
return inscriptionDate;
}
public void setInscriptionDate(Date inscriptionDate) {
this.inscriptionDate = inscriptionDate;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public List<Post> getListPosts() {
return listPosts;
}
public void setListPosts(List<Post> listPosts) {
this.listPosts = listPosts;
}
public List<Comment> getListComments() {
return listComments;
}
public void setListComments(List<Comment> listComments) {
this.listComments = listComments;
}
public List<Image> getListImages() {
return listImages;
}
public void addImage(final Image pImage) {
if(listImages == null) {
listImages = new ArrayList<>();
}
listImages.add(pImage);
if(pImage.getUser() == null) {
pImage.setUser(this);
}
}
public void setListImages(List<Image> listImages) {
this.listImages = listImages;
}
public Token getToken() {
return token;
}
}

View File

@@ -0,0 +1,82 @@
package org.codiki.entities.security;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Calendar;
public class Token {
/** The metric in which the validation delay is defined. */
private static final int METRIC = Calendar.MINUTE;
/** Number of {@link METRIC} after that the token become invalid. */
private static final int DELAY = 30;
/** The Constant BITS_NUMBER. */
private static final int BITS_NUMBER = 130;
/** The Constant RADIX. */
private static final int RADIX = 32;
/** The value. */
private String value;
/**
* Last access date. For each request to the server, this date is consulted
* and if the valid delay is ok, this date must be updated.
*/
private Calendar lastAccess;
/**
* Instantiates a new token.
*/
public Token() {
super();
value = new BigInteger(BITS_NUMBER, new SecureRandom()).toString(RADIX);
lastAccess = Calendar.getInstance();
}
/**
* Gets the value.
*
* @return the value
*/
public String getValue() {
return value;
}
/**
* Gets the last access date.
*
* @return the last access date
*/
public Calendar getLastAccess() {
return lastAccess;
}
/**
* Sets the last access date.
*/
public void setLastAccess() {
lastAccess = Calendar.getInstance();
}
/**
* Indicate if the token is still valid.<br/>
* A token is valid is its {@link Token#lastAccess} is after the current
* date minus the {@link Token#DELAY} {@link Token#METRIC}.<br/>
* <br/>
* Example:<br/>
* {@link Token#DELAY} = 30 and {@link Token#METRIC} =
* {@link Calendar#MINUTE}.<br/>
* A token is valid only on the 30 minutes after its
* {@link Token#lastAccess}.<br/>
* If the current date-time minus the 30 minutes is before the
* {@link Token#lastAccess}, the token is still valid.
*
* @return {@code true} if the token is still valid, {@code false}
* otherwise.
*/
public boolean isValid() {
final Calendar lastTimeValidation = Calendar.getInstance();
lastTimeValidation.add(METRIC, -DELAY);
return lastAccess.getTime().after(lastTimeValidation.getTime());
}
}

View File

@@ -0,0 +1,37 @@
package org.codiki.login;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codiki.entities.dto.UserDAO;
import org.codiki.security.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
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;
@RestController
@RequestMapping("/api/account")
public class LoginController {
private static final String HEADER_TOKEN = "token";
@Autowired
private TokenService tokenService;
@Autowired
private LoginService loginService;
@PostMapping("/login")
public UserDAO login(@RequestBody UserDAO pUser, HttpServletResponse response) {
return loginService.checkCredentials(response, pUser);
}
@GetMapping("/logout")
public void logout(HttpServletRequest pRequest) {
tokenService.removeUser(pRequest.getHeader(HEADER_TOKEN));
}
}

View File

@@ -0,0 +1,50 @@
package org.codiki.login;
import java.util.Optional;
import javax.naming.AuthenticationException;
import javax.servlet.http.HttpServletResponse;
import org.codiki.entities.dto.UserDAO;
import org.codiki.entities.persistence.User;
import org.codiki.repositories.UserRepository;
import org.codiki.security.TokenService;
import org.codiki.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoginService {
@Autowired
private UserRepository userRepository;
@Autowired
private TokenService tokenService;
/**
* 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 AuthenticationException
* If the credentials are wrong.
*/
public UserDAO checkCredentials(HttpServletResponse pResponse, UserDAO pUser) {
UserDAO result = null;
Optional<User> user = userRepository.findByEmail(pUser.getEmail());
if(user.isPresent() && StringUtils.compareHash(pUser.getPassword(), user.get().getPassword())) {
tokenService.addUser(user.get());
result = new UserDAO(user.get());
} else {
pResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
return result;
}
}

View File

@@ -0,0 +1,13 @@
package org.codiki.repositories;
import java.util.Optional;
import org.codiki.entities.persistence.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByEmail(@Param("email") final String pEmail);
}

View File

@@ -0,0 +1,35 @@
package org.codiki.security;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.codiki.core.AbstractFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AuthenticationFilter extends AbstractFilter {
@Override
protected List<Route> getRoutes() {
return Arrays.asList(
new Route("\\/api\\/account\\/.*")
);
}
@Override
protected void filter(HttpServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Token : " + request.getHeader("token"));
chain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,69 @@
package org.codiki.security;
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<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 pMethod) {
this(pUrl);
this.method = Optional.of(pMethod);
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Optional<HttpMethod> getMethod() {
return method;
}
public void setMethod(HttpMethod method) {
this.method = Optional.of(method);
}
}

View File

@@ -0,0 +1,106 @@
package org.codiki.security;
import java.util.Map;
import java.util.TreeMap;
import org.codiki.entities.persistence.User;
import org.springframework.stereotype.Service;
@Service
public class TokenService {
/** Map of connected users. */
private static final Map<String, User> connectedUsers;
/**
* 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
clearExpiredUsers();
return result;
}
/**
* Remove from the connected users map all the elements which their token is
* expired.
*/
@SuppressWarnings("unlikely-arg-type")
private void clearExpiredUsers() {
connectedUsers.entrySet().stream().forEach(user -> {
if(!user.getValue().getToken().isValid()) {
connectedUsers.remove(user).getKey();
}
});
}
/**
* 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);
}
}
}

View File

@@ -0,0 +1,35 @@
package org.codiki.utils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateUtils {
public static final String FORMAT_DEFAULT = "dd/MM/yyyy HH:mm:ss";
public static Date parseDate(String pSource, String pPattern) throws ParseException {
Date result = null;
if (pSource != null && !pSource.isEmpty()) {
DateFormat formatter = getSimpleDateFormat(pPattern);
formatter.setLenient(false);
result = formatter.parse(pSource);
}
return result;
}
public static String format(Date pDate, String pPattern) {
return getSimpleDateFormat(pPattern).format(pDate);
}
public static String format(Date pDate) {
return getSimpleDateFormat(FORMAT_DEFAULT).format(pDate);
}
public static SimpleDateFormat getSimpleDateFormat(String pPattern) {
SimpleDateFormat result = new SimpleDateFormat(pPattern, Locale.FRENCH);
result.setLenient(false);
return result;
}
}

View File

@@ -0,0 +1,82 @@
package org.codiki.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class RegexUtils {
private static final String EMAIL_REGEX = "^.*@.*\\..{2,}$";
private static final String LOWER_LETTERS_REGEX = ".*[a-z].*";
private static final String UPPER_LETTERS_REGEX = ".*[A-Z].*";
private static final String NUMBER_REGEX = ".*[0-9].*";
private static final String SPECIAL_CHAR_REGEX = ".*\\W.*";
private static final String NUMBER_ONLY_REGEX = "^[0-9]+$";
// La portée "package" permet à la classe StringUtils d'utiliser les patterns
// suivants :
static final Pattern EMAIL_PATTERN;
static final Pattern LOWER_LETTERS_PATTERN;
static final Pattern UPPER_LETTERS_PATTERN;
static final Pattern NUMBER_PATTERN;
static final Pattern SPECIAL_CHAR_PATTERN;
static final Pattern NUMBER_ONLY_PATTERN;
static {
EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
LOWER_LETTERS_PATTERN = Pattern.compile(LOWER_LETTERS_REGEX);
UPPER_LETTERS_PATTERN = Pattern.compile(UPPER_LETTERS_REGEX);
NUMBER_PATTERN = Pattern.compile(NUMBER_REGEX);
SPECIAL_CHAR_PATTERN = Pattern.compile(SPECIAL_CHAR_REGEX);
NUMBER_ONLY_PATTERN = Pattern.compile(NUMBER_ONLY_REGEX);
}
/**
* Chekcs if {@code pString} corresponds to an email address.
*
* @param pString
* The string which should be an email address.
* @return {@code true} if {@link pString} corresponds to an email address,
* {@code false} otherwise.
*/
public static boolean isEmail(final String pString) {
return EMAIL_PATTERN.matcher(pString).find();
}
/**
* Replace the sequences of {@code pString} matched by the {@code pRegex}
* with the {@code pReplacingString}.
*
* @param pString
* The string to update.
* @param pRegex
* The regex to match the sentences to replace.
* @param pReplacingString
* The string to replace the sentences which match with the
* regex.
* @return The new string.
*/
public static String replaceSequence(final String pString,
final String pRegex, final String pReplacingString) {
return Pattern.compile(pRegex).matcher(pString)
.replaceAll(pReplacingString);
}
/**
* Checks if {@code pString} corresponds to a number.
*
* @param pString
* The string which should be a number.
* @return {@code true} if {@code pString} corresponds to a number,
* {@code false} otherwise.
*/
public static boolean isNumber(final String pString) {
return NUMBER_ONLY_PATTERN.matcher(pString).find();
}
public static String getGroup(final String regex, final int numeroGroupe, final String chaine) {
final Pattern pattern = Pattern.compile(regex);
final Matcher matcher = pattern.matcher(chaine);
matcher.find();
return matcher.group(numeroGroupe);
}
}

View File

@@ -0,0 +1,93 @@
package org.codiki.utils;
import org.mindrot.jbcrypt.BCrypt;
/**
* Generic methods about {@link String} class.
*
* @author takiguchi
*
*/
public final class StringUtils {
/**
* Indicate if {@code pString} is null or just composed of spaces.
*
* @param pString
* The string to test.
* @return {@code true} if {@code pString} is null or just composed of
* spaces, {@code false} otherwise.
*/
public static boolean isNull(final String chaine) {
return chaine == null || chaine.trim().length() == 0;
}
/**
* Hash the password given into parameters.
*
* @param pPassword The password to hash.
* @return The password hashed.
*/
public static String hashPassword(final String pPassword) {
return hashString(pPassword, 10);
}
public static String hashString(final String pString, final int pSalt) {
return BCrypt.hashpw(pString, BCrypt.gensalt(pSalt));
}
/**
* Compare the password and the hashed string given into parameters.
*
* @param pPassword
* The password to compare to the hashed string.
* @param pHashToCompare
* The hashed string to compare to the password.
* @return {@code true} if the password matches to the hashed string.
*/
public static boolean compareHash(final String pPassword, final String pHashToCompare) {
return BCrypt.checkpw(pPassword, pHashToCompare);
}
/**
* Concatenate the parameters to form just one single string.
*
* @param pArgs
* The strings to concatenate.
* @return The parameters concatenated.
*/
public static String concat(final Object... pArgs) {
final StringBuilder result = new StringBuilder();
for (final Object arg : pArgs) {
result.append(arg);
}
return result.toString();
}
public static String printStrings(final String... pStrings) {
final StringBuilder result = new StringBuilder();
for (int i = 0 ; i < pStrings.length ; i++) {
result.append(pStrings[i]);
if(i < pStrings.length - 1) {
result.append(",");
}
}
return result.toString();
}
public static boolean containLowercase(final String pString) {
return RegexUtils.LOWER_LETTERS_PATTERN.matcher(pString).find();
}
public static boolean containUppercase(final String pString) {
return RegexUtils.UPPER_LETTERS_PATTERN.matcher(pString).find();
}
public static boolean containNumber(final String pString) {
return RegexUtils.NUMBER_PATTERN.matcher(pString).find();
}
public static boolean containSpecialChar(final String pString) {
return RegexUtils.SPECIAL_CHAR_PATTERN.matcher(pString).find();
}
}

View File

@@ -0,0 +1,15 @@
#server.error.whitelabel.enabled=false
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/codiki
spring.datasource.username=codiki
spring.datasource.password=P@ssword
# Disable feature detection by this undocumented parameter. Check the org.hibernate.engine.jdbc.internal.JdbcServiceImpl.configure method for more details.
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false
# Because detection is disabled you have to set correct dialect by hand.
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect
logging.level.org.hibernate=DEBUG
cors.enabled=false

View File

@@ -0,0 +1,72 @@
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.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.codiki.security.Route;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
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, ServletResponse 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"));
}
}