Jottyfan 2023-03-12 23:53:34 +01:00
parent 68124316d0
commit f415d99e9e
9 changed files with 330 additions and 4 deletions

View File

@ -18,7 +18,7 @@ apply plugin: 'war'
apply plugin: 'application'
group = 'de.jottyfan.camporganizer'
version = '0.3.3'
version = '0.3.4'
sourceCompatibility = 17
mainClassName = "de.jottyfan.camporganizer.Main"
@ -56,6 +56,9 @@ dependencies {
implementation 'org.keycloak:keycloak-spring-boot-starter'
// old login algorithm; can be removed when all accounts have moved
implementation 'org.jasypt:jasypt:1.9.3'
// for using the keycloak rest interface
implementation 'org.keycloak:keycloak-admin-client:20.0.1'
implementation 'org.jboss.resteasy:resteasy-client:5.0.0.Final'

View File

@ -0,0 +1,118 @@
package de.jottyfan.camporganizer.module.migration;
import java.io.Serializable;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import org.jasypt.exceptions.EncryptionOperationNotPossibleException;
import org.jasypt.util.password.StrongPasswordEncryptor;
import de.jottyfan.camporganizer.module.registration.validate.UnusedUsername;
/**
*
* @author jotty
*
*/
@UnusedUsername(field = "email", message = "Diese E-Mail ist als Login leider bereits vergeben. Bitte wähle eine andere.")
public class MigrationBean implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank(message = "Bitte gib deinen Vornamen an.")
private String forename;
@NotBlank(message = "Bitte gib deinen Nachnamen an.")
private String surname;
@NotBlank(message = "Bitte gib deinen alten Nutzernamen an.")
private String username;
@NotBlank(message = "Bitte gib dein altes Passwort an.")
private String password;
@NotBlank(message = "Bitte gib eine E-Mail-Adresse an. Ohne kannst du deinen Zugang nicht umziehen.")
@Email(message = "Bitte gib eine gültige E-Mail-Adresse an.")
private String email;
/**
* check if the current password fits to the encrypted one
*
* @param encryptedPassword the encrypted password from the database
* @return true or false
*/
public boolean checkPassword(String encryptedPassword) {
try {
return new StrongPasswordEncryptor().checkPassword(password, encryptedPassword);
} catch (EncryptionOperationNotPossibleException e) {
return false; // password is not decryptable
}
}
/**
* @return the username
*/
public String getUsername() {
return username;
}
/**
* @param username the username to set
*/
public void setUsername(String username) {
this.username = username;
}
/**
* @return the password
*/
public String getPassword() {
return password;
}
/**
* @param password the password to set
*/
public void setPassword(String password) {
this.password = password;
}
/**
* @return the email
*/
public String getEmail() {
return email;
}
/**
* @param email the email to set
*/
public void setEmail(String email) {
this.email = email;
}
/**
* @return the forename
*/
public String getForename() {
return forename;
}
/**
* @param forename the forename to set
*/
public void setForename(String forename) {
this.forename = forename;
}
/**
* @return the surname
*/
public String getSurname() {
return surname;
}
/**
* @param surname the surname to set
*/
public void setSurname(String surname) {
this.surname = surname;
}
}

View File

@ -0,0 +1,48 @@
package de.jottyfan.camporganizer.module.migration;
import javax.validation.Valid;
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.validation.FieldError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
/**
*
* @author jotty
*
*/
@Controller
public class MigrationController {
@Autowired
private MigrationService service;
/**
* to the login page
*
* @return
*/
@GetMapping("/migration/login")
public String getLogin(Model model) {
model.addAttribute("bean", new MigrationBean());
return "/migration/login";
}
@PostMapping("/migration/loginold")
public String loginOld(@Valid @ModelAttribute("bean") MigrationBean bean, final BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "/migration/login";
} else if (service.checkLogin(bean)) {
service.migrate(bean);
return "redirect:/dashboard";
} else {
bindingResult.addError(new FieldError("password", "password", "Dein Passwort scheint falsch zu sein, oder den Benutzer gibt es nicht."));
return "/migration/login";
}
}
}

View File

@ -0,0 +1,43 @@
package de.jottyfan.camporganizer.module.migration;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.jooq.Record1;
import org.jooq.SelectConditionStep;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
*
* @author jotty
*
*/
@Repository
@Transactional(transactionManager = "transactionManager")
public class MigrationRepository {
private static final Logger LOGGER = LogManager.getLogger(MigrationRepository.class);
@Autowired
private DSLContext jooq;
/**
* get the encrypted password of username
*
* @param username the username
* @return the encrypted password or null
*/
public String getEncryptedPassword(String username) {
SelectConditionStep<Record1<String>> sql = jooq
// @formatter:off
.select(T_PROFILE.PASSWORD)
.from(T_PROFILE)
.where(T_PROFILE.USERNAME.eq(username));
// @formatter:on
LOGGER.debug(sql.toString());
return sql.fetchOne(T_PROFILE.PASSWORD);
}
}

View File

@ -0,0 +1,44 @@
package de.jottyfan.camporganizer.module.migration;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.camporganizer.module.registration.KeycloakRepository;
/**
*
* @author jotty
*
*/
@Service
public class MigrationService {
@Autowired
private MigrationRepository repository;
@Autowired
private KeycloakRepository keycloak;
/**
* check if the login is valid
*
* @param bean the bean
* @return true or false
*/
public boolean checkLogin(@Valid MigrationBean bean) {
String encryptedPassword = repository.getEncryptedPassword(bean.getUsername());
return bean.checkPassword(encryptedPassword);
}
/**
* do the migration
*
* @param bean the bean
* @return true or false
*/
public boolean migrate(@Valid MigrationBean bean) {
return keycloak.register(bean.getForename(), bean.getSurname(), bean.getUsername(), bean.getPassword(), bean.getEmail());
}
}

View File

@ -396,3 +396,9 @@ div {
border-radius: 8px;
border: 1px solid gray;
}
.block660 {
max-width: 660px;
margin-left: auto;
margin-right: auto;
}

View File

@ -16,10 +16,14 @@
</th:block>
<th:block layout:fragment="content">
<div class="mainpage">
<div class="alert alert-warning alert-dismissible fade show block660" role="alert">
<span>alte Zugangsdaten ins neue System </span><a th:href="@{/migration/login}">umziehen</a>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Schließen"></button>
</div>
<script type="text/javascript">
var mytoggle = new MyToggle();
</script>
<div class="card bottomdist16" style="max-width: 660px; margin-left: auto; margin-right: auto; background: transparent" th:each="c : ${camps}">
<div class="card bottomdist16 block660" style="background: transparent" th:each="c : ${camps}">
<div class="card-header mytoggle_btn" style="background: transparent" th:onclick="mytoggle.toggle('campdiv_[[${c.pk}]]')">
<div style="margin-left: auto; margin-right: auto;">
<span th:text="${c.name}" class="headlinefont"></span><span class="headlinefont">&nbsp;</span><span th:text="${#numbers.formatInteger(c.year, 0)}" class="headlinefont"

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<title>Camp Organizer 2</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<th:block layout:fragment="header">
Umziehen der Zugangsdaten aus dem alten Anmeldeportal
</th:block>
<th:block layout:fragment="content">
<div class="mainpage">
<div class="alert alert-success alert-dismissible fade show block660" role="alert">
Mit dem Umzug in das neue Anmeldeportal ist es leider notwendig, dass du dir ein neues Login anlegst.
Damit das für dich leichter geht, haben wir ein Migrationswerkzeug entwickelt, mit dem du deinen alten Zugang übertragen kannst.<br />
<br />
Wichtig dabei ist, dass du beim Anlegen des neuen Zugangs eine gültige E-Mail-Adresse verwendest. Die wird im neuen System benötigt,
falls du dein Passwort vergessen hast. Damit eine sinnvolle Zuordnung möglich ist, gib bitte ebenfalls Vor- und Nachname an.<br />
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Schließen"></button>
</div>
<div class="block660">
<div class="card centered-card" style="max-width: 48rem">
<div class="card-body">
<form th:action="@{/migration/loginold}" th:object="${bean}" method="post">
<div class="container">
<div class="row">
<div class="col-sm-6 rowdist">
<span class="error" th:each="error : ${#fields.errors('username')}">[[${error}]]<br /></span>
<input type="text" placeholder="username" th:field="*{username}" th:class="${'form-control ' + (#fields.hasErrors('username') ? 'inputerror' : '')}" />
</div>
<div class="col-sm-6 rowdist">
<span class="error" th:each="error : ${#fields.errors('password')}">[[${error}]]<br /></span>
<input type="password" placeholder="Passwort" th:field="*{password}" th:class="${'form-control ' + (#fields.hasErrors('password') ? 'inputerror' : '')}" />
</div>
<div class="col-sm-6 rowdist">
<span class="error" th:each="error : ${#fields.errors('forename')}">[[${error}]]<br /></span>
<input type="text" placeholder="Vorname" th:field="*{forename}" th:class="${'form-control ' + (#fields.hasErrors('forename') ? 'inputerror' : '')}" />
</div>
<div class="col-sm-6 rowdist">
<span class="error" th:each="error : ${#fields.errors('surname')}">[[${error}]]<br /></span>
<input type="text" placeholder="Nachname" th:field="*{surname}" th:class="${'form-control ' + (#fields.hasErrors('surname') ? 'inputerror' : '')}" />
</div>
<div class="col-sm-12 rowdist">
<span class="error" th:each="error : ${#fields.errors('email')}">[[${error}]]<br /></span>
<input type="text" placeholder="E-Mail" th:field="*{email}" th:class="${'form-control ' + (#fields.hasErrors('email') ? 'inputerror' : '')}" />
</div>
</div>
<div class="row">
<div class="col-sm-12 rowdist centered">
<input type="submit" class="btn btn-linda buttonfont" value="jetzt umziehen" />
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</th:block>
</body>
</html>

View File

@ -22,11 +22,11 @@
<div class="container">
<div class="row">
<div class="col-sm-6 rowdist">
<span class="error" th:each="error : ${#fields.errors('surname')}">[[${error}]]<br /></span>
<span class="error" th:each="error : ${#fields.errors('forename')}">[[${error}]]<br /></span>
<input type="text" placeholder="Vorname" th:field="*{forename}" th:class="${'form-control ' + (#fields.hasErrors('forename') ? 'inputerror' : '')}" />
</div>
<div class="col-sm-6 rowdist">
<span class="error" th:each="error : ${#fields.errors('forename')}">[[${error}]]<br /></span>
<span class="error" th:each="error : ${#fields.errors('surname')}">[[${error}]]<br /></span>
<input type="text" placeholder="Nachname" th:field="*{surname}" th:class="${'form-control ' + (#fields.hasErrors('surname') ? 'inputerror' : '')}" />
</div>
</div>