basic validation

This commit is contained in:
Jottyfan 2022-12-08 23:03:50 +01:00
parent df7e4e7f0e
commit 2e376291c5
12 changed files with 174 additions and 35 deletions

View File

@ -26,6 +26,5 @@
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View File

@ -1,14 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="CampOrganizer2">
<property name="context-root" value="CampOrganizer2"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/resources"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
</wb-module>
<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="CampOrganizer2">
<property name="context-root" value="CampOrganizer2"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/resources"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
</wb-module>
</project-modules>

View File

@ -67,6 +67,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
implementation 'de.jottyfan:COJooq:2021.02'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.0.0'

View File

@ -34,10 +34,10 @@ public class KeycloakRepository {
@Value("${keycloak.realm:ow}")
private String keycloakRealm;
@Value("${keycloak.admin.name:admin")
@Value("${ow.keycloak.admin.name:admin")
private String keycloakAdminName;
@Value("${keycloak.admin.password:password")
@Value("${ow.keycloak.admin.password:password")
private String keycloakAdminPassword;
/**

View File

@ -3,32 +3,50 @@ package de.jottyfan.camporganizer.module.registration;
import java.io.Serializable;
import java.time.LocalDate;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.springframework.format.annotation.DateTimeFormat;
import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
import de.jottyfan.camporganizer.db.jooq.enums.EnumSex;
import de.jottyfan.camporganizer.module.registration.validate.UnusedUsername;
/**
*
*
* @author jotty
*
*/
@UnusedUsername(field = "login", message = "Dieses Login ist leider bereits vergeben. Bitte wähle ein anderes.")
public class RegistrationBean 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;
@NotNull(message = "Bitte gib dein Geschlecht an. Wir benötigen das, um zu wissen, ob du in einem Jungs- oder Mädchenzimmer übernachten kannst.")
private EnumSex sex;
@NotNull(message = "Bitte gib dein Geburtsdatum an. Damit errechnen wir, ob die Freizeit für dich geeignet ist.")
@DateTimeFormat(pattern="yyyy-MM-dd")
private LocalDate birthDate;
@NotBlank(message = "Bitte gib die Strasse deines Wohnsitzes an.")
private String street;
@NotBlank(message = "Bitte gib die Postleitzahl deines Wohnsitzes an.")
private String zip;
@NotBlank(message = "Bitte gib den Ort deines Wohnsitzes an.")
private String city;
@Email(message = "Bitte gib eine gültige E-Mail-Adresse an (oder gar keine).")
private String email;
private String phone;
private String comment;
@NotNull(message = "Bitte gib an, zu welcher Freizeit du dich anmelden möchtest.")
private Integer fkCamp;
@NotNull(message = "Bitte gib an, in welcher Rolle du dich anmelden möchtest.")
private EnumCamprole campRole;
@NotNull(message = "Bitte gib an, ob du dir für spätere Anmeldungen einen Zugang einrichten willst.")
private Boolean registerInKeycloak;
private String login;
private String password;

View File

@ -1,10 +1,12 @@
package de.jottyfan.camporganizer.module.registration;
import javax.servlet.http.HttpServletRequest;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
@ -39,7 +41,14 @@ public class RegistrationController extends CommonController {
}
@PostMapping("/registration/register")
public String register(@ModelAttribute RegistrationBean bean, Model model) {
public String register(@Valid @ModelAttribute RegistrationBean bean, Model model, final BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
super.setupSession(model, request);
CampBean campBean = service.getCamp(bean.getFkCamp());
model.addAttribute("camp", campBean);
model.addAttribute("bean", bean);
return "/registration";
}
Boolean result = service.register(bean);
// TODO: give the user a message about success or error and, if registered in keycloak, a note about how to login
return index(bean.getFkCamp(), model);

View File

@ -65,10 +65,26 @@ public class RegistrationGateway {
}
}
/**
* test if the login is available (not yet in use)
*
* @param login the login
* @return true or false
*/
public Boolean isLoginAvailable(String login) {
SelectConditionStep<TProfileRecord> sql = jooq
// @formatter:off
.selectFrom(T_PROFILE)
.where(T_PROFILE.USERNAME.eq(login));
// @formatter:on
LOGGER.debug(sql);
return sql.fetch().size() < 1;
}
/**
* save the content in t_person; also, create a profile for the user if
* registerInKeycloak is true
*
*
* @param bean the bean
* @return true or false
*/
@ -76,10 +92,7 @@ public class RegistrationGateway {
LambdaResultWrapper lrw = new LambdaResultWrapper();
jooq.transaction(t -> {
if (bean.getRegisterInKeycloak()) {
SelectConditionStep<TProfileRecord> sql0 = DSL.using(t).selectFrom(T_PROFILE)
.where(T_PROFILE.USERNAME.eq(bean.getLogin()));
LOGGER.debug(sql0);
if (sql0.fetch().size() > 0) {
if (!isLoginAvailable(bean.getLogin())) {
throw new DataAccessException("login already in use: " + bean.getLogin());
}
// TODO: check if teacher is at least 2 years older than the camp participants

View File

@ -0,0 +1,29 @@
package de.jottyfan.camporganizer.module.registration.validate;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
/**
*
* @author jotty
*
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UnusedUsernameValidator.class)
@Documented
public @interface UnusedUsername {
String message() default "username is already in use";
String field();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,40 @@
package de.jottyfan.camporganizer.module.registration.validate;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import de.jottyfan.camporganizer.module.registration.RegistrationGateway;
/**
*
* @author jotty
*
*/
public class UnusedUsernameValidator implements ConstraintValidator<UnusedUsername, Object> {
private String field;
private String message;
@Autowired
private RegistrationGateway gateway;
public void initialize(UnusedUsername uu) {
this.field = uu.field();
this.message = uu.message();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object login = new BeanWrapperImpl(value).getPropertyValue(field);
Boolean result = gateway.isLoginAvailable((String) login);
if (!result) {
context.buildConstraintViolationWithTemplate(message).addPropertyNode(field).addConstraintViolation()
.disableDefaultConstraintViolation();
}
return result;
}
}

View File

@ -1,5 +1,22 @@
# database credentials from defined config file
spring.config.import=/etc/CampOrganizer2/prod.properties
# define overwriteable arguments
spring.datasource.driver-class-name=${spring.datasource.driver-class-name:org.postgresql.Driver}
spring.datasource.url=${spring.datasource.url}
spring.datasource.username=${spring.datasource.username}
spring.datasource.password=${spring.datasource.password}
server.servlet.context-path=${server.servlet.context-path:/CampOrganizer2}
keycloak.auth-server-url=${keycloak.auth-server-url}
keycloak.realm=${keycloak.realm:ow}
keycloak.resource=${keycloak.resource:biblecamp}
keycloak.public-client=${keycloak.public-client}
keycloak.use-resource-role-mappings=${keycloak.use-resource-role-mappings}
ow.keycloak.admin.name=${ow.keycloak.admin.name}
ow.keycloak.admin.password=${ow.keycloak.admin.password}
# for development only
server.port = 8081

View File

@ -269,6 +269,13 @@ div {
color: red;
}
.inputerror {
border: 1px solid red !important;
background-image: linear-gradient(to left bottom, #fff, #fcc) !important;
padding: 4px !important;
border-radius: 6px !important;
}
.locked {
background-color: rgba(255, 255, 255, 0.2) !important;
cursor: not-allowed;

View File

@ -21,41 +21,50 @@
<div class="container">
<div class="row">
<div class="col-sm-6 rowdist">
<input type="text" class="form-control" placeholder="Vorname" th:field="*{forename}" />
<span class="error" th:each="error : ${#fields.errors('surname')}">[[${error}]]<br /></span>
<input type="text" placeholder="Vorname" th:field="*{forename}" th:class="${#fields.hasErrors('forename') ? 'inputerror' : 'form-control'}" />
</div>
<div class="col-sm-6 rowdist">
<input type="text" class="form-control" placeholder="Nachname" th:field="*{surname}" />
<span class="error" th:each="error : ${#fields.errors('forename')}">[[${error}]]<br /></span>
<input type="text" placeholder="Nachname" th:field="*{surname}" th:class="${#fields.hasErrors('surname') ? 'inputerror' : 'form-control'}" />
</div>
</div>
<div class="row">
<div class="col-sm-6 rowdist">
<select class="form-select" th:field="*{sex}">
<span class="error" th:each="error : ${#fields.errors('sex')}">[[${error}]]<br /></span>
<select th:field="*{sex}" th:class="${#fields.hasErrors('sex') ? 'inputerror' : 'form-select'}">
<option value="">Geschlecht</option>
<option value="female">weiblich</option>
<option value="male">männlich</option>
</select>
</div>
<div class="col-sm-6 rowdist">
<input type="date" class="form-control" placeholder="Geburtsdatum" th:field="*{birthDate}" />
<span class="error" th:each="error : ${#fields.errors('birthDate')}">[[${error}]]<br /></span>
<input type="date" placeholder="Geburtsdatum" th:field="*{birthDate}" th:class="${#fields.hasErrors('birthDate') ? 'inputerror' : 'form-control'}" />
</div>
</div>
<div class="row">
<div class="col-sm-6 rowdist">
<input type="text" class="form-control" placeholder="Straße und Hausnummer" th:field="*{street}" />
<span class="error" th:each="error : ${#fields.errors('street')}">[[${error}]]<br /></span>
<input type="text" placeholder="Straße und Hausnummer" th:field="*{street}" th:class="${#fields.hasErrors('street') ? 'inputerror' : 'form-control'}" />
</div>
<div class="col-sm-2 rowdist">
<input type="text" class="form-control" placeholder="PLZ" th:field="*{zip}" />
<span class="error" th:each="error : ${#fields.errors('zip')}">[[${error}]]<br /></span>
<input type="text" placeholder="PLZ" th:field="*{zip}" th:class="${#fields.hasErrors('zip') ? 'inputerror' : 'form-control'}" />
</div>
<div class="col-sm-4 rowdist">
<input type="text" class="form-control" placeholder="Ort" th:field="*{city}" />
<span class="error" th:each="error : ${#fields.errors('city')}">[[${error}]]<br /></span>
<input type="text" placeholder="Ort" th:field="*{city}" th:class="${#fields.hasErrors('city') ? 'inputerror' : 'form-control'}" />
</div>
</div>
<div class="row">
<div class="col-sm-6 rowdist">
<input type="text" class="form-control" placeholder="E-Mail" th:field="*{email}" />
<span class="error" th:each="error : ${#fields.errors('email')}">[[${error}]]<br /></span>
<input type="text" placeholder="E-Mail" th:field="*{email}" th:class="${#fields.hasErrors('email') ? 'inputerror' : 'form-control'}" />
</div>
<div class="col-sm-6 rowdist">
<input type="text" class="form-control" placeholder="Telefonnummer" th:field="*{phone}" />
<span class="error" th:each="error : ${#fields.errors('phone')}">[[${error}]]<br /></span>
<input type="text" placeholder="Telefonnummer" th:field="*{phone}" th:class="${#fields.hasErrors('phone') ? 'inputerror' : 'form-control'}" />
</div>
</div>
<div class="row">
@ -63,7 +72,8 @@
<span>mitkommen als</span>
</div>
<div class="col-sm-6 rowdist">
<select class="form-select" th:field="*{campRole}">
<span class="error" th:each="error : ${#fields.errors('campRole')}">[[${error}]]<br /></span>
<select th:field="*{campRole}" th:class="${#fields.hasErrors('campRole') ? 'inputerror' : 'form-select'}">
<option value="student">Teilnehmer</option>
<option value="teacher">Mitarbeiter</option>
<option value="feeder">Küchenteam</option>
@ -86,10 +96,12 @@
</div>
<div id="createlogin" class="row">
<div class="col-sm-6 rowdist">
<input type="text" class="form-control" placeholder="Login" th:field="*{login}" />
<span class="error" th:each="error : ${#fields.errors('login')}">[[${error}]]<br /></span>
<input type="text" placeholder="Login" th:field="*{login}" th:class="${#fields.hasErrors('login') ? 'inputerror' : 'form-control'}" />
</div>
<div class="col-sm-6 rowdist">
<input type="password" class="form-control" placeholder="Passwort" th:field="*{password}" />
<span class="error" th:each="error : ${#fields.errors('password')}">[[${error}]]<br /></span>
<input type="password" placeholder="Passwort" th:field="*{password}" th:class="${#fields.hasErrors('password') ? 'inputerror' : 'form-control'}" />
</div>
</div>
<div class="row">