From 29e8ff77987f3e1d3df863ef3b2f7b38fb7bde6e Mon Sep 17 00:00:00 2001 From: Jottyfan Date: Fri, 9 Dec 2022 22:23:54 +0100 Subject: [PATCH] keycloak registration done --- .../registration/KeycloakRepository.java | 64 +++++++++++-------- .../registration/RegistrationController.java | 26 ++++++-- .../registration/RegistrationGateway.java | 62 ++++++++++++++++++ .../registration/RegistrationService.java | 33 +++++++++- src/main/resources/application.properties | 26 ++++---- src/main/resources/templates/dashboard.html | 11 ++-- src/main/resources/templates/index.html | 4 +- .../templates/registration/cancellation.html | 44 +++++++++++++ .../{ => registration}/registration.html | 0 .../templates/registration/success.html | 31 +++++++++ 10 files changed, 248 insertions(+), 53 deletions(-) create mode 100644 src/main/resources/templates/registration/cancellation.html rename src/main/resources/templates/{ => registration}/registration.html (100%) create mode 100644 src/main/resources/templates/registration/success.html 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 4ff54ac..e955df2 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/KeycloakRepository.java @@ -25,19 +25,19 @@ import org.springframework.stereotype.Repository; public class KeycloakRepository { private final static Logger LOGGER = LogManager.getLogger(KeycloakRepository.class); - @Value("${keycloak.resource:biblecamp}") + @Value("${keycloak.resource}") private String keycloakClientId; @Value("${keycloak.auth-server-url}") private String keycloakUrl; - @Value("${keycloak.realm:ow}") + @Value("${keycloak.realm}") private String keycloakRealm; - @Value("${ow.keycloak.admin.name:admin") + @Value("${ow.keycloak.admin.name}") private String keycloakAdminName; - @Value("${ow.keycloak.admin.password:password") + @Value("${ow.keycloak.admin.password}") private String keycloakAdminPassword; /** @@ -54,28 +54,32 @@ public class KeycloakRepository { /** * register the login in keycloak * + * @param forename the forename + * @param surname the surname * @param login the username * @param password the password * @param email the email * @return true or false */ - public boolean register(String login, String password, String email) { - UserRepresentation user = getUserRepresentation(login, password, email); - UsersResource resource = getUsersResource(keycloakUrl, keycloakRealm, keycloakAdminName, keycloakAdminPassword, keycloakClientId); - boolean result = register(resource, user); - sendVerificationLink(login, resource); - return result; + public boolean register(String forename, String surname, String login, String password, String email) { + UserRepresentation user = getUserRepresentation(forename, surname, login, password, email); + UsersResource resource = getUsersResource(keycloakUrl, keycloakRealm, keycloakAdminName, keycloakAdminPassword, + keycloakClientId); + return register(resource, user); } /** * generate a user representation * - * @param login the login + * @param forename the forename + * @param surname the surname + * @param login the login * @param password the password - * @param email the email + * @param email the email * @return the user representation */ - protected UserRepresentation getUserRepresentation(String login, String password, String email) { + protected UserRepresentation getUserRepresentation(String forename, String surname, String login, String password, + String email) { CredentialRepresentation passwordCredentials = new CredentialRepresentation(); passwordCredentials.setTemporary(false); passwordCredentials.setType(CredentialRepresentation.PASSWORD); @@ -83,6 +87,8 @@ public class KeycloakRepository { UserRepresentation user = new UserRepresentation(); user.setUsername(login); + user.setFirstName(forename); + user.setLastName(surname); user.setEmail(email); user.setCredentials(Collections.singletonList(passwordCredentials)); user.setEnabled(true); @@ -94,37 +100,42 @@ public class KeycloakRepository { * register a user in keycloak * * @param resource the resource - * @param user the user + * @param user the user * @return true or false */ protected boolean register(UsersResource resource, UserRepresentation user) { Response response = resource.create(user); - LOGGER.info("created new keycloak user {}", user.getUsername()); - return Status.OK.equals(response.getStatusInfo()); + Boolean success = Status.CREATED.equals(response.getStatusInfo()); + if (success) { + LOGGER.info("created new keycloak user {}", user.getUsername()); + } else { + LOGGER.error("error on creating keycloak user {}: {}", user.getUsername(), response.getStatus()); + } + return success; } /** * generate a keycloak object with the special arguments * - * @param url the url - * @param realm the realm - * @param admin the admin of the realm + * @param url the url + * @param realm the realm + * @param admin the admin of the realm * @param password the password * @param clientId the client ID * @return the keycloak object */ public KeycloakBuilder getKeycloak(String url, String realm, String admin, String password, String clientId) { - return KeycloakBuilder.builder().serverUrl(url).realm(realm).grantType(OAuth2Constants.PASSWORD) - .username(admin).password(password).clientId(clientId) + return KeycloakBuilder.builder().serverUrl(url).realm(realm).grantType(OAuth2Constants.PASSWORD).username(admin) + .password(password).clientId(clientId) .resteasyClient(new ResteasyClientBuilderImpl().connectionPoolSize(10).build()); } /** * generate a users resource object * - * @param url the url - * @param realm the realm - * @param admin the admin of the realm + * @param url the url + * @param realm the realm + * @param admin the admin of the realm * @param password the password * @param clientId the client ID * @return the users resource @@ -138,8 +149,9 @@ public class KeycloakRepository { * * @param userId the ID of the user */ - public void sendVerificationLink(String userId, UsersResource usersResource) { - usersResource.get(userId).sendVerifyEmail(); + public void sendVerificationLink(String userId) { + getUsersResource(keycloakUrl, keycloakRealm, keycloakAdminName, keycloakAdminPassword, keycloakClientId).get(userId) + .sendVerifyEmail(); } } 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 ba5f4f0..373c932 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationController.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationController.java @@ -37,20 +37,32 @@ public class RegistrationController extends CommonController { bean.setFkCamp(fkCamp); bean.setRegisterInKeycloak(true); // we want people to register generally model.addAttribute("bean", bean); - return "/registration"; + return "/registration/registration"; } @PostMapping("/registration/register") - public String register(@Valid @ModelAttribute RegistrationBean bean, final BindingResult bindingResult, Model model) { + public String register(@Valid @ModelAttribute("bean") RegistrationBean bean, final BindingResult bindingResult, Model model) { + super.setupSession(model, request); if (bindingResult.hasErrors()) { - super.setupSession(model, request); CampBean campBean = service.getCamp(bean.getFkCamp()); model.addAttribute("camp", campBean); - model.addAttribute("bean", bean); - return "/registration"; + return "/registration/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); + return result ? "/registration/success" : "/error"; + } + + @GetMapping("/registration/cancel/{id}") + public String cancellation(@PathVariable Integer id, final Model model) { + super.setupSession(model, request); + model.addAttribute("bean", service.getBooking(id)); + return "/registration/cancellation"; + } + + @GetMapping("/registration/remove/{id}") + public String remove(@PathVariable Integer id, final Model model) { + super.setupSession(model, request); + service.removeBooking(id); + return "redirect:/dashboard"; } } 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 9de8527..0056156 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationGateway.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationGateway.java @@ -2,6 +2,7 @@ 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_PERSONDOCUMENT; import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE; import java.time.LocalDate; @@ -13,9 +14,11 @@ 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.DeleteConditionStep; import org.jooq.InsertResultStep; import org.jooq.InsertValuesStep12; import org.jooq.InsertValuesStep13; +import org.jooq.Record7; import org.jooq.SelectConditionStep; import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; @@ -27,7 +30,9 @@ 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.TPersondocumentRecord; import de.jottyfan.camporganizer.db.jooq.tables.records.TProfileRecord; +import de.jottyfan.camporganizer.module.common.BookingBean; import de.jottyfan.camporganizer.module.common.LambdaResultWrapper; /** @@ -163,4 +168,61 @@ public class RegistrationGateway { }); return lrw.getCounter() > 0; } + + /** + * get the booking from the database + * + * @param id the id of the booking + * @return the booking bean or null + */ + public BookingBean getBooking(Integer id) { + SelectConditionStep> sql = jooq + // @formatter:off + .select(T_PERSON.PK, + T_PERSON.FORENAME, + T_PERSON.SURNAME, + T_PERSON.CAMPROLE, + T_CAMP.NAME, + T_CAMP.ARRIVE, + T_CAMP.DEPART) + .from(T_PERSON) + .leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP)) + .where(T_PERSON.PK.eq(id)); + // @formatter:on + LOGGER.debug(sql.toString()); + Record7 r = sql.fetchOne(); + if (r != null) { + BookingBean bean = new BookingBean(); + bean.setPk(r.get(T_PERSON.PK)); + bean.setForename(r.get(T_PERSON.FORENAME)); + bean.setSurname(r.get(T_PERSON.SURNAME)); + bean.setCampName(r.get(T_CAMP.NAME)); + bean.setArrive(r.get(T_CAMP.ARRIVE)); + bean.setDepart(r.get(T_CAMP.DEPART)); + bean.setCamprole(r.get(T_PERSON.CAMPROLE)); + return bean; + } + return null; + } + + /** + * remove the booking and all of its dependencies + * + * @param id the pk of t_person + * @return number of affected database rows, should be 1 + */ + public Integer removeBooking(Integer id) { + LambdaResultWrapper lrw = new LambdaResultWrapper(); + jooq.transaction(t -> { + DeleteConditionStep sql1 = DSL.using(t).deleteFrom(T_PERSONDOCUMENT) + .where(T_PERSONDOCUMENT.FK_PERSON.eq(id)); + LOGGER.debug(sql1.toString()); + sql1.execute(); + + DeleteConditionStep sql2 = DSL.using(t).deleteFrom(T_PERSON).where(T_PERSON.PK.eq(id)); + LOGGER.debug(sql2.toString()); + lrw.add(sql2.execute()); + }); + return lrw.getCounter(); + } } 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 f155f24..d41babd 100644 --- a/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationService.java +++ b/src/main/java/de/jottyfan/camporganizer/module/registration/RegistrationService.java @@ -1,8 +1,12 @@ package de.jottyfan.camporganizer.module.registration; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import de.jottyfan.camporganizer.module.common.BookingBean; + /** * * @author jotty @@ -10,6 +14,7 @@ import org.springframework.stereotype.Service; */ @Service public class RegistrationService { + private final static Logger LOGGER = LogManager.getLogger(RegistrationService.class); @Autowired private RegistrationGateway gateway; @@ -36,8 +41,34 @@ public class RegistrationService { public Boolean register(RegistrationBean bean) { Boolean result = gateway.register(bean); if (result && bean.getRegisterInKeycloak()) { - keycloak.register(bean.getLogin(), bean.getPassword(), bean.getEmail()); + keycloak.register(bean.getForename(), bean.getSurname(), bean.getLogin(), bean.getPassword(), bean.getEmail()); + if (bean.getEmail() != null && !bean.getEmail().isBlank()) { + try { + keycloak.sendVerificationLink(bean.getLogin()); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + } } return result; } + + /** + * get the booking bean + * + * @param id the id of the registration (t_person.pk) + * @return the booking bean or null + */ + public BookingBean getBooking(Integer id) { + return gateway.getBooking(id); + } + + /** + * remove the booking and all of its dependencies + * + * @param id the id of the booking (t_person.pk) + */ + public Boolean removeBooking(Integer id) { + return gateway.removeBooking(id) > 0; + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 30f6c58..81d2509 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,22 +1,22 @@ # database credentials from defined config file -spring.config.import=/etc/CampOrganizer2/prod.properties +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} +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} +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} +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} +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/templates/dashboard.html b/src/main/resources/templates/dashboard.html index 7a087b7..b08553a 100644 --- a/src/main/resources/templates/dashboard.html +++ b/src/main/resources/templates/dashboard.html @@ -28,7 +28,7 @@
-

+

-
+
Freizeitdaten
@@ -112,7 +112,7 @@
Foto-Einverständnis:
- +
Kommentar:
@@ -120,9 +120,12 @@
-
+
+
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 278fcd8..5f20392 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -13,7 +13,7 @@ @@ -56,7 +56,7 @@
diff --git a/src/main/resources/templates/registration/cancellation.html b/src/main/resources/templates/registration/cancellation.html new file mode 100644 index 0000000..458c601 --- /dev/null +++ b/src/main/resources/templates/registration/cancellation.html @@ -0,0 +1,44 @@ + + + +Camp Organizer 2 + + + + + + + + + + +
+
+ Willst du deine Anmeldung wirklich stornieren und damit den Freizeitplatz für einen anderen Teilnehmer freigeben? Diese Stornierung kann nicht + rückgängig gemacht werden. Willst du deine Anmeldung wirklich stornieren? +
+
+
+ +
+
+ TeilnehmerMitarbeiter LeiterKüchenhilfe +
+ +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/registration.html b/src/main/resources/templates/registration/registration.html similarity index 100% rename from src/main/resources/templates/registration.html rename to src/main/resources/templates/registration/registration.html diff --git a/src/main/resources/templates/registration/success.html b/src/main/resources/templates/registration/success.html new file mode 100644 index 0000000..9dc0497 --- /dev/null +++ b/src/main/resources/templates/registration/success.html @@ -0,0 +1,31 @@ + + + +Camp Organizer 2 + + + + + + + + + + +
+
+ Deine Anmeldung wurde entgegengenommen. Falls du dir auch ein Login eingerichtet hast, kannst du dich jetzt oben rechts einloggen und deine Anmeldung bearbeiten. +
+ +
+
+ + \ No newline at end of file