basic validation
This commit is contained in:
		| @@ -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"> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user