From 4133ad9173bb0d26a815216eb9ed0b26142af9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Henke?= Date: Mon, 14 Nov 2022 10:55:20 +0100 Subject: [PATCH] basic registration functionality, still missing keycloak --- build.gradle | 3 + .../module/common/LambdaResultWrapper.java | 22 ++ .../registration/KeycloakRepository.java | 27 ++ .../module/registration/RegistrationBean.java | 245 ++++++++++++++++++ .../registration/RegistrationController.java | 16 +- .../registration/RegistrationGateway.java | 101 ++++++++ .../registration/RegistrationService.java | 16 ++ .../resources/templates/registration.html | 44 ++-- 8 files changed, 457 insertions(+), 17 deletions(-) create mode 100644 src/main/java/de/jottyfan/camporganizer/module/common/LambdaResultWrapper.java create mode 100644 src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java create mode 100644 src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationBean.java diff --git a/build.gradle b/build.gradle index 67474d6..fd0dd89 100644 --- a/build.gradle +++ b/build.gradle @@ -54,6 +54,9 @@ dependencies { implementation 'net.sf.biweekly:biweekly:0.6.6' implementation 'org.keycloak:keycloak-spring-boot-starter' + + // backward compatibility until the complete registration is converted to keycloak + implementation 'org.jasypt:jasypt:1.9.3' implementation 'org.springframework.boot:spring-boot-starter-jooq' implementation 'org.springframework.boot:spring-boot-starter-security' diff --git a/src/main/java/de/jottyfan/camporganizer/module/common/LambdaResultWrapper.java b/src/main/java/de/jottyfan/camporganizer/module/common/LambdaResultWrapper.java new file mode 100644 index 0000000..4f38ea0 --- /dev/null +++ b/src/main/java/de/jottyfan/camporganizer/module/common/LambdaResultWrapper.java @@ -0,0 +1,22 @@ +package de.jottyfan.camporganizer.module.common; + +/** + * + * @author henkej + * + */ +public class LambdaResultWrapper { + private Integer counter; + + public LambdaResultWrapper() { + counter = 0; + } + + public Integer getCounter() { + return counter; + } + + public void add(Integer i) { + counter += i; + } +} diff --git a/src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java b/src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java new file mode 100644 index 0000000..52dcf80 --- /dev/null +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java @@ -0,0 +1,27 @@ +package de.jottyfan.camporganizer.module.registration; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Repository; + +/** + * + * @author henkej + * + */ +@Repository +public class KeycloakRepository { + private final static Logger LOGGER = LogManager.getLogger(KeycloakRepository.class); + + /** + * register the login in keycloak + * + * @param login + * @param password + */ + public void register(String login, String password) { + // TODO Auto-generated method stub + LOGGER.error("not yet implemented registration of user {} in keycloak", login); + } + +} diff --git a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationBean.java b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationBean.java new file mode 100644 index 0000000..584ff86 --- /dev/null +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationBean.java @@ -0,0 +1,245 @@ +package de.jottyfan.camporganizer.module.registration; + +import java.io.Serializable; +import java.time.LocalDate; + +import org.springframework.format.annotation.DateTimeFormat; + +import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole; +import de.jottyfan.camporganizer.db.jooq.enums.EnumSex; + +/** + * + * @author jotty + * + */ +public class RegistrationBean implements Serializable { + private static final long serialVersionUID = 1L; + + private String forename; + private String surname; + private EnumSex sex; + @DateTimeFormat(pattern="yyyy-MM-dd") + private LocalDate birthDate; + private String street; + private String zip; + private String city; + private String email; + private String phone; + private String comment; + private Integer fkCamp; + private EnumCamprole campRole; + private Boolean registerInKeycloak; + private String login; + private String password; + + /** + * @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; + } + + /** + * @return the sex + */ + public EnumSex getSex() { + return sex; + } + + /** + * @param sex the sex to set + */ + public void setSex(EnumSex sex) { + this.sex = sex; + } + + /** + * @return the birthDate + */ + public LocalDate getBirthDate() { + return birthDate; + } + + /** + * @param birthDate the birthDate to set + */ + public void setBirthDate(LocalDate birthDate) { + this.birthDate = birthDate; + } + + /** + * @return the street + */ + public String getStreet() { + return street; + } + + /** + * @param street the street to set + */ + public void setStreet(String street) { + this.street = street; + } + + /** + * @return the zip + */ + public String getZip() { + return zip; + } + + /** + * @param zip the zip to set + */ + public void setZip(String zip) { + this.zip = zip; + } + + /** + * @return the city + */ + public String getCity() { + return city; + } + + /** + * @param city the city to set + */ + public void setCity(String city) { + this.city = city; + } + + /** + * @return the email + */ + public String getEmail() { + return email; + } + + /** + * @param email the email to set + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * @return the phone + */ + public String getPhone() { + return phone; + } + + /** + * @param phone the phone to set + */ + public void setPhone(String phone) { + this.phone = phone; + } + + /** + * @return the comment + */ + public String getComment() { + return comment; + } + + /** + * @param comment the comment to set + */ + public void setComment(String comment) { + this.comment = comment; + } + + /** + * @return the registerInKeycloak + */ + public Boolean getRegisterInKeycloak() { + return registerInKeycloak; + } + + /** + * @param registerInKeycloak the registerInKeycloak to set + */ + public void setRegisterInKeycloak(Boolean registerInKeycloak) { + this.registerInKeycloak = registerInKeycloak; + } + + /** + * @return the login + */ + public String getLogin() { + return login; + } + + /** + * @param login the login to set + */ + public void setLogin(String login) { + this.login = login; + } + + /** + * @return the password + */ + public String getPassword() { + return password; + } + + /** + * @param password the password to set + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * @return the fkCamp + */ + public Integer getFkCamp() { + return fkCamp; + } + + /** + * @param fkCamp the fkCamp to set + */ + public void setFkCamp(Integer fkCamp) { + this.fkCamp = fkCamp; + } + + /** + * @return the campRole + */ + public EnumCamprole getCampRole() { + return campRole; + } + + /** + * @param campRole the campRole to set + */ + public void setCampRole(EnumCamprole campRole) { + this.campRole = campRole; + } +} 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 5073525..4f62a6a 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationController.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationController.java @@ -6,7 +6,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import de.jottyfan.camporganizer.module.common.CommonController; @@ -27,9 +29,19 @@ public class RegistrationController extends CommonController { @GetMapping("/registration/{fkCamp}") public String index(@PathVariable(name = "fkCamp", required = true) Integer fkCamp, Model model) { super.setupSession(model, request); - model.addAttribute("camp", service.getCamp(fkCamp)); - // TODO: prepare a bean for the form variables; use model.getAttribute("camp").getPk() for the fk_camp + CampBean campBean = service.getCamp(fkCamp); + model.addAttribute("camp", campBean); + RegistrationBean bean = new RegistrationBean(); + bean.setFkCamp(fkCamp); + bean.setRegisterInKeycloak(true); // we want people to register generally + model.addAttribute("bean", bean); return "/registration"; } + @PostMapping("/registration/register") + public String register(@ModelAttribute RegistrationBean bean, Model model) { + 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 c5f431e..12b3133 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationGateway.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationGateway.java @@ -1,18 +1,34 @@ package de.jottyfan.camporganizer.module.registration; import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMP; +import static de.jottyfan.camporganizer.db.jooq.Tables.T_PERSON; +import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.UUID; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jasypt.util.password.StrongPasswordEncryptor; import org.jooq.DSLContext; +import org.jooq.InsertResultStep; +import org.jooq.InsertValuesStep12; +import org.jooq.InsertValuesStep13; import org.jooq.SelectConditionStep; +import org.jooq.exception.DataAccessException; +import org.jooq.impl.DSL; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole; +import de.jottyfan.camporganizer.db.jooq.enums.EnumSex; import de.jottyfan.camporganizer.db.jooq.tables.records.TCampRecord; +import de.jottyfan.camporganizer.db.jooq.tables.records.TPersonRecord; +import de.jottyfan.camporganizer.db.jooq.tables.records.TProfileRecord; +import de.jottyfan.camporganizer.module.common.LambdaResultWrapper; /** * @@ -49,4 +65,89 @@ public class RegistrationGateway { } } + /** + * 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 + */ + public Boolean register(RegistrationBean bean) { + 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) { + throw new DataAccessException("login already in use: " + bean.getLogin()); + } + // TODO: check if teacher is at least 2 years older than the camp participants + String oldPassword = new StrongPasswordEncryptor().encryptPassword(bean.getPassword()); + InsertResultStep sql1 = DSL.using(t) + // @formatter:off + .insertInto(T_PROFILE, + T_PROFILE.FORENAME, + T_PROFILE.SURNAME, + T_PROFILE.USERNAME, + T_PROFILE.PASSWORD, + T_PROFILE.DUEDATE, + T_PROFILE.UUID) + .values(bean.getForename(), bean.getSurname(), bean.getLogin(), oldPassword, LocalDateTime.now().plus(356, ChronoUnit.DAYS), UUID.nameUUIDFromBytes(bean.getLogin().getBytes()).toString()) + .returning(T_PROFILE.PK); + // @formatter:on + LOGGER.debug(sql1.toString()); + Integer fkProfile = sql1.fetchOne().getPk(); + // register the person for camp participation + InsertValuesStep13 sql2 = DSL + .using(t) + // @formatter:off + .insertInto(T_PERSON, + T_PERSON.FORENAME, + T_PERSON.SURNAME, + T_PERSON.SEX, + T_PERSON.BIRTHDATE, + T_PERSON.STREET, + T_PERSON.ZIP, + T_PERSON.CITY, + T_PERSON.EMAIL, + T_PERSON.PHONE, + T_PERSON.CAMPROLE, + T_PERSON.FK_CAMP, + T_PERSON.COMMENT, + T_PERSON.FK_PROFILE) + .values(bean.getForename(), bean.getSurname(), bean.getSex(), + bean.getBirthDate(), bean.getStreet(), bean.getZip(), bean.getCity(), bean.getEmail(), + bean.getPhone(), bean.getCampRole(), bean.getFkCamp(), bean.getComment(), fkProfile); + // @formatter:on + LOGGER.debug(sql2.toString()); + lrw.add(sql2.execute()); + // register the login for the portal + } else { + InsertValuesStep12 sql = DSL + .using(t) + // @formatter:off + .insertInto(T_PERSON, + T_PERSON.FORENAME, + T_PERSON.SURNAME, + T_PERSON.SEX, + T_PERSON.BIRTHDATE, + T_PERSON.STREET, + T_PERSON.ZIP, + T_PERSON.CITY, + T_PERSON.EMAIL, + T_PERSON.PHONE, + T_PERSON.CAMPROLE, + T_PERSON.FK_CAMP, + T_PERSON.COMMENT) + .values(bean.getForename(), bean.getSurname(), bean.getSex(), + bean.getBirthDate(), bean.getStreet(), bean.getZip(), bean.getCity(), bean.getEmail(), + bean.getPhone(), bean.getCampRole(), bean.getFkCamp(), bean.getComment()); + // @formatter:on + LOGGER.debug(sql.toString()); + lrw.add(sql.execute()); + } + }); + return lrw.getCounter() > 0; + } } diff --git a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationService.java b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationService.java index 23d477a..d098465 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationService.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationService.java @@ -13,6 +13,9 @@ public class RegistrationService { @Autowired private RegistrationGateway gateway; + + @Autowired + private KeycloakRepository keycloak; /** * get the camp @@ -24,4 +27,17 @@ public class RegistrationService { return gateway.getCamp(fkCamp); } + /** + * register the person for a camp; if registerInKeycloak, do so also + * + * @param bean the bean + * @return true if successful, false otherwise + */ + public Boolean register(RegistrationBean bean) { + Boolean result = gateway.register(bean); + if (result && bean.getRegisterInKeycloak()) { + keycloak.register(bean.getLogin(), bean.getPassword()); + } + return result; + } } diff --git a/src/main/resources/templates/registration.html b/src/main/resources/templates/registration.html index a14d487..b8ca905 100644 --- a/src/main/resources/templates/registration.html +++ b/src/main/resources/templates/registration.html @@ -15,66 +15,80 @@

-
+ +
- +
- +
-
- +
- +
- +
- +
- +
- + +
+
+
+
+ mitkommen als +
+
+
- +
- +
- +
- +
@@ -89,4 +103,4 @@
- \ No newline at end of file +