Commit 98cf26de authored by Marek Kadlečík's avatar Marek Kadlečík
Browse files

Merge branch 'security' into 'main'

Security

See merge request !28
parents e682f12b a9478aa1
Pipeline #141827 passed with stage
in 1 minute and 5 seconds
package cz.muni.fi.pa165.dto;
public class PersonAuthenticateDTO {
private String userId;
private String password;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
......@@ -17,4 +17,8 @@ public class PersonDTO extends BaseDTO {
private LocalDate dateOfBirth;
private CountryEnum country;
private String passwordHash;
private String username;
}
package cz.muni.fi.pa165.facade;
import cz.muni.fi.pa165.dto.PersonAuthenticateDTO;
import cz.muni.fi.pa165.dto.PersonDTO;
import java.util.List;
public interface PersonFacade {
List<PersonDTO> findAllPeople();
PersonDTO getPersonById(String uuid);
PersonDTO getPersonByUsername(String username);
void delete(String uuid);
boolean authenticate(PersonAuthenticateDTO person);
boolean isAdmin(PersonDTO person);
}
......@@ -107,6 +107,10 @@
<artifactId>assertj-core</artifactId>
<version>3.21.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
......@@ -13,6 +13,8 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
......
......@@ -7,4 +7,5 @@ import cz.muni.fi.pa165.entity.PersonEntity;
* @author Dominik Kozubík
*/
public interface PersonDao extends BaseDao<PersonEntity> {
PersonEntity findByUsername(String username);
}
package cz.muni.fi.pa165.dao.person;
import cz.muni.fi.pa165.dao.BaseDaoImpl;
import cz.muni.fi.pa165.dao.exceptions.DaoDataAccessException;
import cz.muni.fi.pa165.entity.PersonEntity;
import org.springframework.stereotype.Repository;
......@@ -19,4 +20,14 @@ public class PersonDaoImpl extends BaseDaoImpl<PersonEntity> implements PersonDa
public PersonDaoImpl() {
super(PersonEntity.class);
}
@Override
public PersonEntity findByUsername(String username) {
try {
return em.createQuery("select e from " + entityClass.getName() + " e where e.username like :username", entityClass)
.setParameter("username", username).getSingleResult();
} catch (Exception e) {
return null;
}
}
}
......@@ -22,6 +22,12 @@ public class PersonEntity extends BaseEntity {
private CountryEnum country;
private String username;
private String passwordHash;
private boolean admin;
public PersonEntity() {
}
......
......@@ -46,5 +46,11 @@
<artifactId>gaming-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
package cz.muni.fi.pa165.sampledata;
import com.google.common.hash.Hashing;
import cz.muni.fi.pa165.entity.PlayerEntity;
import cz.muni.fi.pa165.entity.TeamEntity;
import cz.muni.fi.pa165.enums.CountryEnum;
......@@ -8,10 +9,13 @@ import cz.muni.fi.pa165.services.team.TeamService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @author Adrián Piaček
......@@ -31,23 +35,26 @@ public class SampleDataLoadingFacadeImpl implements SampleDataLoadingFacade {
@Override
@SuppressWarnings("unused")
public void loadData() throws IOException {
TeamEntity g2_esports = team("g2_esports", CountryEnum.GERMANY);
TeamEntity skt_t1 = team("skt_t1", CountryEnum.SOUTH_KOREA);
PlayerEntity crimsix = player("9384b466-badf-40ca-bbeb-4d8f6eb98042", "Ian Porter",
CountryEnum.AFGHANISTAN,g2_esports,5,6,7);
PlayerEntity crimsix = player("9384b466-badf-40ca-bbeb-4d8f6eb98042", "Ian Porter", "poter",
"heslo", true, CountryEnum.AFGHANISTAN, g2_esports, 5, 6, 7);
PlayerEntity JJoNak = player("6e374a4e-5213-4581-847c-45f9e73ffe90", "Bang Seong-hyun ",
CountryEnum.ALBANIA,skt_t1,5,6,7);
PlayerEntity JJoNak = player("6e374a4e-5213-4581-847c-45f9e73ffe90", "Bang Seong-hyun ", "bang",
"heslo", true, CountryEnum.ALBANIA, skt_t1, 15, 25, 100);
log.info("Loaded Sample Players.");
}
private PlayerEntity player(String uuid, String name, CountryEnum country, TeamEntity team, int killStat, int deathStat, int assistStat) {
private PlayerEntity player(String uuid, String name, String username, String password, boolean admin,
CountryEnum country, TeamEntity team, int killStat, int deathStat, int assistStat) {
PlayerEntity player = new PlayerEntity();
player.setId(uuid);
player.setName(name);
player.setUsername(username);
String sha256hex = Hashing.sha256().hashString(password, StandardCharsets.UTF_8).toString();
player.setPasswordHash(sha256hex);
player.setAdmin(admin);
player.setCountry(country);
player.setTeam(team);
player.setKillStat(killStat);
......
......@@ -46,6 +46,12 @@
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>
......
package cz.muni.fi.pa165.facade;
import cz.muni.fi.pa165.dto.PersonAuthenticateDTO;
import cz.muni.fi.pa165.dto.PersonDTO;
import cz.muni.fi.pa165.entity.PersonEntity;
import cz.muni.fi.pa165.services.BeanMappingService;
import cz.muni.fi.pa165.services.person.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class PersonFacadeImpl implements PersonFacade {
@Autowired
private PersonService personService;
@Autowired
private BeanMappingService beanMappingService;
@Override
public List<PersonDTO> findAllPeople() {
return beanMappingService.mapTo(personService.findAll(), PersonDTO.class);
}
@Override
public PersonDTO getPersonById(String uuid) {
PersonEntity person = personService.findById(uuid);
return (person == null) ? null : beanMappingService.mapTo(person, PersonDTO.class);
}
@Override
public PersonDTO getPersonByUsername(String username) {
PersonEntity person = personService.findByUsername(username);
return (person == null) ? null : beanMappingService.mapTo(person, PersonDTO.class);
}
@Override
public void delete(String uuid) {
var player = new PersonEntity();
player.setId(uuid);
personService.remove(player);
}
@Override
public boolean authenticate(PersonAuthenticateDTO person) {
return false;
}
@Override
public boolean isAdmin(PersonDTO person) {
return personService.isAdmin(beanMappingService.mapTo(person, PersonEntity.class));
}
}
......@@ -15,6 +15,18 @@ public interface PersonService {
PersonEntity findById(String id);
PersonEntity findByUsername(String username);
/**
* Try to authenticate a user. Return true only if the hashed password matches the records.
*/
boolean authenticate(PersonEntity person, String password);
/**
* Check if the given user is admin.
*/
boolean isAdmin(PersonEntity person);
String update(PersonEntity match);
void remove(PersonEntity match);
......
package cz.muni.fi.pa165.services.person;
import com.google.common.hash.Hashing;
import cz.muni.fi.pa165.dao.person.PersonDao;
import cz.muni.fi.pa165.entity.PersonEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
......@@ -40,6 +44,25 @@ public class PersonServiceImpl implements PersonService {
return personDao.findById(id);
}
@Override
public PersonEntity findByUsername(String username) {
if (username == null) {
throw new IllegalArgumentException("Given username is null.");
}
return personDao.findByUsername(username);
}
@Override
public boolean authenticate(PersonEntity person, String password) {
String sha256hex = Hashing.sha256().hashString(password, StandardCharsets.UTF_8).toString();
return person.getPasswordHash().equals(sha256hex);
}
@Override
public boolean isAdmin(PersonEntity person) {
return findById(person.getId()).isAdmin();
}
@Override
public String update(PersonEntity person) {
if (person == null) {
......
......@@ -27,7 +27,7 @@ public class MyConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/").setViewName("home");
}
@Override
......
package cz.muni.fi.pa165.mvc.controllers;
import cz.muni.fi.pa165.dto.PersonAuthenticateDTO;
import cz.muni.fi.pa165.dto.PersonDTO;
import cz.muni.fi.pa165.entity.PersonEntity;
import cz.muni.fi.pa165.services.BeanMappingService;
import cz.muni.fi.pa165.services.person.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
/**
* @author Jan Hassman
*/
@Controller
@RequestMapping("/security")
public class LoginController {
@Autowired
private PersonService personService;
@Autowired
private BeanMappingService mapper;
@GetMapping(value = "/login")
public String getLogin(Model model, HttpSession session) {
if (session.getAttribute("authenticatedUser") != null) {
return "redirect:/";
}
model.addAttribute("user", new PersonAuthenticateDTO());
return "/security/login";
}
@PostMapping(value = "/login")
public String postLogin(Model model, HttpSession session, @Valid @ModelAttribute("user") PersonDTO personDTO,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("alert_danger", "Login failed!");
return "security/loginFailed";
}
PersonEntity person = personService.findByUsername(personDTO.getUsername());
if (person == null) {
redirectAttributes.addFlashAttribute("alert_danger", "Login failed!");
return "security/loginFailed";
}
if (!personService.authenticate(person, personDTO.getPasswordHash())) {
redirectAttributes.addFlashAttribute("alert_danger", "Login failed!");
return "security/loginFailed";
}
session.setAttribute("authenticatedUser", mapper.mapTo(person, PersonDTO.class));
redirectAttributes.addFlashAttribute("alert_success", "Login successful!");
return "redirect:/";
}
@GetMapping(value = "/logout")
public String logout(Model model, HttpSession session, RedirectAttributes redirectAttributes) {
if (session.getAttribute("authenticatedUser") == null) {
return "redirect:/";
}
session.removeAttribute("authenticatedUser");
redirectAttributes.addFlashAttribute("alert_success", "Logout successful!");
return "/security/login";
}
@GetMapping(value = "/wrongAccess")
public String wrongAccess(Model model, HttpSession httpSession) {
PersonDTO personDTO = (PersonDTO) httpSession.getAttribute("authenticatedUser");
model.addAttribute("authenticatedUser", personDTO);
return "/security/wrongAccess";
}
}
......@@ -10,4 +10,6 @@ navigation.coaches = Coaches
navigation.matches = Matches
navigation.ranklist.players = Ranklist Players
navigation.ranklist.teams= Ranklist Teams
navigation.login= Login
navigation.logout= Logout
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" session="false" %>
<%@ taglib tagdir="/WEB-INF/tags" prefix="my" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:message var="title" key="navigation.login"/>
<my:pagetemplate title="${title}">
<jsp:attribute name="body">
<form method="POST" action="${pageContext.request.contextPath}/security/login">
<table>
<tr>
<td><label for="username-label">Username:</label></td>
<td><input id="username-label" type="text" name="username"></td>
</tr>
<tr>
<td><label for="password-label">Password:</label></td>
<td><input id="password-label" type="password" name="passwordHash" ></td>
</tr>
</table>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</jsp:attribute>
</my:pagetemplate>
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" session="false" %>
<%@ taglib tagdir="/WEB-INF/tags" prefix="my" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<my:pagetemplate>
<jsp:attribute name="body">
<html>
<head>
<title>Login failed</title>
</head>
<body>
<c:if test="${not empty alert_danger}">
<div class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<c:out value="${alert_danger}"/>Login failed!</div>
</c:if>
</body>
</html>
</jsp:attribute>
</my:pagetemplate>
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" session="false" %>
<%@ taglib tagdir="/WEB-INF/tags" prefix="my" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<my:pagetemplate>
<jsp:attribute name="body">
<html>
<head>
<title>Not found</title>
</head>
<body>
<p>${resource} not found</p>
</body>
</html>
</jsp:attribute>
</my:pagetemplate>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment