From 2e376291c5cc6c6732f905c9f668aaa976767fe6 Mon Sep 17 00:00:00 2001 From: Jottyfan Date: Thu, 8 Dec 2022 23:03:50 +0100 Subject: [PATCH] basic validation --- .classpath | 1 - .settings/org.eclipse.wst.common.component | 20 ++++------ build.gradle | 1 + .../registration/KeycloakRepository.java | 4 +- .../module/registration/RegistrationBean.java | 20 +++++++++- .../registration/RegistrationController.java | 11 ++++- .../registration/RegistrationGateway.java | 23 ++++++++--- .../registration/validate/UnusedUsername.java | 29 ++++++++++++++ .../validate/UnusedUsernameValidator.java | 40 +++++++++++++++++++ src/main/resources/application.properties | 17 ++++++++ src/main/resources/static/css/style.css | 7 ++++ .../resources/templates/registration.html | 36 +++++++++++------ 12 files changed, 174 insertions(+), 35 deletions(-) create mode 100644 src/main/java/de/jottyfan/camporganizer/module/registration/validate/UnusedUsername.java create mode 100644 src/main/java/de/jottyfan/camporganizer/module/registration/validate/UnusedUsernameValidator.java diff --git a/.classpath b/.classpath index c5c4c5b..7606e94 100644 --- a/.classpath +++ b/.classpath @@ -26,6 +26,5 @@ - diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component index 31a62d0..59fc2ab 100644 --- a/.settings/org.eclipse.wst.common.component +++ b/.settings/org.eclipse.wst.common.component @@ -1,14 +1,8 @@ - - - - - - - - - - - - - + + + + + + + diff --git a/build.gradle b/build.gradle index 3d32a0f..223dbc2 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java b/src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java index fe277fd..4ff54ac 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java @@ -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; /** diff --git a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationBean.java b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationBean.java index 584ff86..f5c3628 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationBean.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationBean.java @@ -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; diff --git a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationController.java b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationController.java index 4f62a6a..3b1da11 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationController.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationController.java @@ -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); diff --git a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationGateway.java b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationGateway.java index 12b3133..9de8527 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationGateway.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationGateway.java @@ -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 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 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 diff --git a/src/main/java/de/jottyfan/camporganizer/module/registration/validate/UnusedUsername.java b/src/main/java/de/jottyfan/camporganizer/module/registration/validate/UnusedUsername.java new file mode 100644 index 0000000..74dbcf0 --- /dev/null +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/validate/UnusedUsername.java @@ -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[] payload() default {}; +} diff --git a/src/main/java/de/jottyfan/camporganizer/module/registration/validate/UnusedUsernameValidator.java b/src/main/java/de/jottyfan/camporganizer/module/registration/validate/UnusedUsernameValidator.java new file mode 100644 index 0000000..ea6046c --- /dev/null +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/validate/UnusedUsernameValidator.java @@ -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 { + + 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; + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index adb59d8..30f6c58 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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 diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index c309cff..bd679ef 100644 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css @@ -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; diff --git a/src/main/resources/templates/registration.html b/src/main/resources/templates/registration.html index b379bbc..f61ac22 100644 --- a/src/main/resources/templates/registration.html +++ b/src/main/resources/templates/registration.html @@ -21,41 +21,50 @@
- + [[${error}]]
+
- + [[${error}]]
+
-
- + [[${error}]]
+
- + [[${error}]]
+
- + [[${error}]]
+
- + [[${error}]]
+
- + [[${error}]]
+
- + [[${error}]]
+
@@ -63,7 +72,8 @@ mitkommen als
- @@ -86,10 +96,12 @@
- + [[${error}]]
+
- + [[${error}]]
+