basic validation
This commit is contained in:
parent
df7e4e7f0e
commit
2e376291c5
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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 {};
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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">
|
||||
|
Loading…
x
Reference in New Issue
Block a user