diff --git a/core/src/test/resources/org/fao/geonet/api/Messages.properties b/core/src/test/resources/org/fao/geonet/api/Messages.properties index 9bd5be836db3..e34944bd1879 100644 --- a/core/src/test/resources/org/fao/geonet/api/Messages.properties +++ b/core/src/test/resources/org/fao/geonet/api/Messages.properties @@ -61,6 +61,10 @@ register_email_admin_message=Dear Admin,\n\ Newly registered user %s has requested %s access for %s.\n\ Yours sincerely,\n\ The %s team. +register_email_group_admin_message=Dear Admin,\n\ + Newly registered user %s has requested %s access in group %s for %s.\n\ + Yours sincerely,\n\ + The %s team. register_email_subject=%s / Your account as %s register_email_message=Dear User,\n\ Your registration at %s was successful.\n\ @@ -77,6 +81,21 @@ register_email_message=Dear User,\n\ \n\ Yours sincerely,\n\ The %s team. +register_email_group_message=Dear User,\n\ + Your registration at %s was successful.\n\ + Your account is: \n\ + * username: %s\n\ + * password: %s\n\ + * profile: %s\n\ + \n\ + You've told us that you want to be %s in group %s, you will be contacted soon.\n\ + To log in and access your account, please click on the link below.\n\ + %s\n\ + \n\ + Thanks for your registration.\n\ + \n\ + Yours sincerely,\n\ + The %s team. new_user_rating=%s / New user rating on %s new_user_rating_text=See record %s user_feedback_title=%s / User feedback on %s / %s diff --git a/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties b/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties index 6abe49058d62..db7afa56704f 100644 --- a/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties +++ b/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties @@ -52,6 +52,10 @@ register_email_admin_message=Cher administrateur,\n\ L'utilisateur %s vient de demander une cr\u00E9ation de compte pour %s.\n\ Salutation,\n\ L'\u00E9quipe %s. +register_email_group_admin_message=Cher administrateur,\n\ + L'utilisateur %s vient de demander une cr\u00E9ation de compte pour %s en groupe %s.\n\ + Salutation,\n\ + L'\u00E9quipe %s. register_email_subject=%s / Votre compte %s register_email_message=Cher utilisateur,\n\ Votre compte a \u00E9t\u00E9 cr\u00E9\u00E9 avec succ\u00E9s pour %s.\n\ @@ -65,6 +69,18 @@ register_email_message=Cher utilisateur,\n\ \n\ Salutations,\n\ L'\u00E9quipe %s. +register_email_group_message=Cher utilisateur,\n\ + Votre compte a \u00E9t\u00E9 cr\u00E9\u00E9 avec succ\u00E9s pour %s.\n\ + * Nom d'utilisateur : %s\n\ + * Mot de passe : %s\n\ + * Profil : %s\n\ + \n\ + Vous avez demand\u00E9 un profil %s en groupe %s, vous serez contact\u00E9 par notre \u00E9quipe prochainement.\n\ + Vous pouvez d\u00E9s \u00E0 pr\u00E9sent vous connecter.\n\ + %s\n\ + \n\ + Salutations,\n\ + L'\u00E9quipe %s. new_user_rating=%s / Nouvelle \u00E9valuation faite pour %s new_user_rating_text=Consulter la fiche %s user_feedback_title=%s / Nouveau commentaire sur %s / %s diff --git a/docs/manual/docs/administrator-guide/managing-users-and-groups/img/selfregistration-form.png b/docs/manual/docs/administrator-guide/managing-users-and-groups/img/selfregistration-form.png index 09321a4e4b52..d718b94a0190 100644 Binary files a/docs/manual/docs/administrator-guide/managing-users-and-groups/img/selfregistration-form.png and b/docs/manual/docs/administrator-guide/managing-users-and-groups/img/selfregistration-form.png differ diff --git a/docs/manual/docs/administrator-guide/managing-users-and-groups/user-self-registration.md b/docs/manual/docs/administrator-guide/managing-users-and-groups/user-self-registration.md index 2594a81f727c..fe3cb2d01426 100644 --- a/docs/manual/docs/administrator-guide/managing-users-and-groups/user-self-registration.md +++ b/docs/manual/docs/administrator-guide/managing-users-and-groups/user-self-registration.md @@ -11,9 +11,12 @@ Click the `Create an account` button and fill out the registration form: The fields in this form are self-explanatory except for the following: - **Email**: The user's email address. This is mandatory and will be used as the username. -- **Profile**: By default, self-registered users are given the `Registered User` profile (see previous section). If any other profile is selected: +- **Requested profile**: By default, self-registered users are given the `Registered User` profile (see previous section). If any other profile is selected: - the user will still be given the `Registered User` profile - an email will be sent to the Email address nominated in the Feedback section of the 'System Administration' menu, informing them of the request for a more privileged profile +- **Requested group**: By default, self-registered users are not assigned to any group. If a group is selected: + - the user will still not be assigned to any group + - an email will be sent to the Email address nominated in the Feedback section of the 'System Administration' menu, informing them of the requested group. ## What happens when a user self-registers? diff --git a/services/src/main/java/org/fao/geonet/api/users/RegisterApi.java b/services/src/main/java/org/fao/geonet/api/users/RegisterApi.java index e5eeb703f1db..110a6fd82c9e 100644 --- a/services/src/main/java/org/fao/geonet/api/users/RegisterApi.java +++ b/services/src/main/java/org/fao/geonet/api/users/RegisterApi.java @@ -1,5 +1,5 @@ //============================================================================= -//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the +//=== Copyright (C) 2001-2024 Food and Agriculture Organization of the //=== United Nations (FAO-UN), United Nations World Food Programme (WFP) //=== and United Nations Environment Programme (UNEP) //=== @@ -26,7 +26,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jeeves.server.context.ServiceContext; -import org.fao.geonet.api.API; import org.fao.geonet.api.ApiUtils; import org.fao.geonet.api.tools.i18n.LanguageUtils; import org.fao.geonet.api.users.model.UserRegisterDto; @@ -45,17 +44,14 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import javax.servlet.http.HttpServletRequest; -import java.sql.SQLException; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.ResourceBundle; +import java.util.*; @EnableWebMvc @Service @@ -72,12 +68,20 @@ public class RegisterApi { @Autowired(required=false) SecurityProviderConfiguration securityProviderConfiguration; + @Autowired + GroupRepository groupRepository; + + @Autowired + UserGroupRepository userGroupRepository; + + @Autowired + SettingManager settingManager; + @io.swagger.v3.oas.annotations.Operation(summary = "Create user account", description = "User is created with a registered user profile. username field is ignored and the email is used as " + "username. Password is sent by email. Catalog administrator is also notified.") - @RequestMapping( + @PutMapping( value = "/actions/register", - method = RequestMethod.PUT, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseStatus(value = HttpStatus.CREATED) @ResponseBody @@ -101,19 +105,18 @@ public ResponseEntity registerUser( ServiceContext context = ApiUtils.createServiceContext(request); - SettingManager sm = context.getBean(SettingManager.class); - boolean selfRegistrationEnabled = sm.getValueAsBool(Settings.SYSTEM_USERSELFREGISTRATION_ENABLE); + boolean selfRegistrationEnabled = settingManager.getValueAsBool(Settings.SYSTEM_USERSELFREGISTRATION_ENABLE); if (!selfRegistrationEnabled) { return new ResponseEntity<>(String.format( messages.getString("self_registration_disabled") ), HttpStatus.PRECONDITION_FAILED); } - boolean recaptchaEnabled = sm.getValueAsBool(Settings.SYSTEM_USERSELFREGISTRATION_RECAPTCHA_ENABLE); + boolean recaptchaEnabled = settingManager.getValueAsBool(Settings.SYSTEM_USERSELFREGISTRATION_RECAPTCHA_ENABLE); if (recaptchaEnabled) { boolean validRecaptcha = RecaptchaChecker.verify(userRegisterDto.getCaptcha(), - sm.getValue(Settings.SYSTEM_USERSELFREGISTRATION_RECAPTCHA_SECRETKEY)); + settingManager.getValue(Settings.SYSTEM_USERSELFREGISTRATION_RECAPTCHA_SECRETKEY)); if (!validRecaptcha) { return new ResponseEntity<>( messages.getString("recaptcha_not_valid"), HttpStatus.PRECONDITION_FAILED); @@ -144,7 +147,7 @@ public ResponseEntity registerUser( ), HttpStatus.PRECONDITION_FAILED); } - if (userRepository.findByUsernameIgnoreCase(userRegisterDto.getEmail()).size() != 0) { + if (!userRepository.findByUsernameIgnoreCase(userRegisterDto.getEmail()).isEmpty()) { // username is ignored and the email is used as username in selfregister return new ResponseEntity<>(String.format( messages.getString("user_with_that_username_found"), @@ -153,8 +156,6 @@ public ResponseEntity registerUser( } User user = new User(); - - // user.setUsername(userRegisterDto.getUsername()); user.setName(userRegisterDto.getName()); user.setSurname(userRegisterDto.getSurname()); user.setOrganisation(userRegisterDto.getOrganisation()); @@ -162,7 +163,6 @@ public ResponseEntity registerUser( user.getAddresses().add(userRegisterDto.getAddress()); user.getEmailAddresses().add(userRegisterDto.getEmail()); - String password = User.getRandomPassword(); user.getSecurity().setPassword( PasswordUtil.encode(context, password) @@ -172,48 +172,80 @@ public ResponseEntity registerUser( user.setProfile(Profile.RegisteredUser); user = userRepository.save(user); - Group targetGroup = getGroup(context); + Group targetGroup = getGroup(); + if (targetGroup != null) { UserGroup userGroup = new UserGroup().setUser(user).setGroup(targetGroup).setProfile(Profile.RegisteredUser); - context.getBean(UserGroupRepository.class).save(userGroup); + userGroupRepository.save(userGroup); } - String catalogAdminEmail = sm.getValue(Settings.SYSTEM_FEEDBACK_EMAIL); + + String catalogAdminEmail = settingManager.getValue(Settings.SYSTEM_FEEDBACK_EMAIL); String subject = String.format( messages.getString("register_email_admin_subject"), - sm.getSiteName(), + settingManager.getSiteName(), user.getEmail(), requestedProfile ); - String message = String.format( - messages.getString("register_email_admin_message"), - user.getEmail(), - requestedProfile, - sm.getNodeURL(), - sm.getSiteName() - ); - if (!MailUtil.sendMail(catalogAdminEmail, subject, message, null, sm)) { + Group requestedGroup = getRequestedGroup(userRegisterDto.getGroup()); + String message; + if (requestedGroup != null) { + message = String.format( + messages.getString("register_email_group_admin_message"), + user.getEmail(), + requestedProfile, + requestedGroup.getLabelTranslations().get(context.getLanguage()), + settingManager.getNodeURL(), + settingManager.getSiteName() + ); + } else { + message = String.format( + messages.getString("register_email_admin_message"), + user.getEmail(), + requestedProfile, + settingManager.getNodeURL(), + settingManager.getSiteName() + ); + + } + + if (Boolean.FALSE.equals(MailUtil.sendMail(catalogAdminEmail, subject, message, null, settingManager))) { return new ResponseEntity<>(String.format( messages.getString("mail_error")), HttpStatus.PRECONDITION_FAILED); } subject = String.format( messages.getString("register_email_subject"), - sm.getSiteName(), + settingManager.getSiteName(), user.getProfile() ); - message = String.format( - messages.getString("register_email_message"), - sm.getSiteName(), - user.getUsername(), - password, - Profile.RegisteredUser, - requestedProfile, - sm.getNodeURL(), - sm.getSiteName() - ); - if (!MailUtil.sendMail(user.getEmail(), subject, message, null, sm)) { + if (requestedGroup != null) { + message = String.format( + messages.getString("register_email_group_message"), + settingManager.getSiteName(), + user.getUsername(), + password, + Profile.RegisteredUser, + requestedProfile, + requestedGroup.getLabelTranslations().get(context.getLanguage()), + settingManager.getNodeURL(), + settingManager.getSiteName() + ); + } else { + message = String.format( + messages.getString("register_email_message"), + settingManager.getSiteName(), + user.getUsername(), + password, + Profile.RegisteredUser, + requestedProfile, + settingManager.getNodeURL(), + settingManager.getSiteName() + ); + } + + if (Boolean.FALSE.equals(MailUtil.sendMail(user.getEmail(), subject, message, null, settingManager))) { return new ResponseEntity<>(String.format( messages.getString("mail_error")), HttpStatus.PRECONDITION_FAILED); } @@ -224,8 +256,39 @@ public ResponseEntity registerUser( ), HttpStatus.CREATED); } - Group getGroup(ServiceContext context) throws SQLException { - final GroupRepository bean = context.getBean(GroupRepository.class); - return bean.findById(ReservedGroup.guest.getId()).get(); + /** + * Returns the group (GUEST) to assign to the registered user. + * + * @return + */ + private Group getGroup() { + Optional targetGroupOpt = groupRepository.findById(ReservedGroup.guest.getId()); + + if (targetGroupOpt.isPresent()) { + return targetGroupOpt.get(); + } + + return null; + } + + /** + * Returns the group requested by the registered user. + * + * @param requestedGroup Requested group identifier for the user. + * @return + */ + Group getRequestedGroup(String requestedGroup) { + Group targetGroup = null; + + if (StringUtils.hasLength(requestedGroup)) { + Optional targetGroupOpt = groupRepository.findById(Integer.parseInt(requestedGroup)); + + // Don't allow reserved groups + if (targetGroupOpt.isPresent() && !targetGroupOpt.get().isReserved()) { + targetGroup = targetGroupOpt.get(); + } + } + + return targetGroup; } } diff --git a/services/src/main/java/org/fao/geonet/api/users/model/UserRegisterDto.java b/services/src/main/java/org/fao/geonet/api/users/model/UserRegisterDto.java index c77a7b66b6c9..5131c99a2418 100644 --- a/services/src/main/java/org/fao/geonet/api/users/model/UserRegisterDto.java +++ b/services/src/main/java/org/fao/geonet/api/users/model/UserRegisterDto.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * Copyright (C) 2001-2024 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -27,7 +27,6 @@ /** * DTO class for user register information. * - * @author Jose GarcĂ­a */ public class UserRegisterDto { private String profile; @@ -39,6 +38,8 @@ public class UserRegisterDto { private Address address; private String captcha; + private String group; + public String getProfile() { return profile; } @@ -103,6 +104,14 @@ public void setCaptcha(String captcha) { this.captcha = captcha; } + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -110,6 +119,7 @@ public boolean equals(Object o) { UserRegisterDto that = (UserRegisterDto) o; + if (group != null ? !group.equals(that.group) : that.group != null) return false; if (profile != null ? !profile.equals(that.profile) : that.profile != null) return false; if (username != null ? !username.equals(that.username) : that.username != null) return false; if (email != null ? !email.equals(that.email) : that.email != null) return false; @@ -123,6 +133,7 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = profile != null ? profile.hashCode() : 0; + result = 31 * result + (group != null ? group.hashCode() : 0); result = 31 * result + (username != null ? username.hashCode() : 0); result = 31 * result + (email != null ? email.hashCode() : 0); result = 31 * result + (name != null ? name.hashCode() : 0); diff --git a/web-ui/src/main/resources/catalog/js/LoginController.js b/web-ui/src/main/resources/catalog/js/LoginController.js index ec4df79ad466..a3dadf809ab6 100644 --- a/web-ui/src/main/resources/catalog/js/LoginController.js +++ b/web-ui/src/main/resources/catalog/js/LoginController.js @@ -101,6 +101,8 @@ $scope.userToRemind = gnUtilityService.getUrlParameter("username"); $scope.changeKey = gnUtilityService.getUrlParameter("changeKey"); } + + $scope.retrieveGroups(); } // TODO: https://github.com/angular/angular.js/issues/1460 @@ -143,6 +145,15 @@ } }; + $scope.retrieveGroups = function () { + $http.get("../api/groups").then( + function (response) { + $scope.groups = response.data; + }, + function (response) {} + ); + }; + $scope.register = function () { if ($scope.recaptchaEnabled) { if (vcRecaptchaService.getResponse() === "") { diff --git a/web-ui/src/main/resources/catalog/locales/en-core.json b/web-ui/src/main/resources/catalog/locales/en-core.json index 834426eec247..a78bf324667d 100644 --- a/web-ui/src/main/resources/catalog/locales/en-core.json +++ b/web-ui/src/main/resources/catalog/locales/en-core.json @@ -234,6 +234,7 @@ "register": "Register", "rememberMe": "Remember me", "requestedProfile": "Requested profile", + "requestedGroup": "Requested group", "resetPassword": "Reset password", "resetPasswordTitle": "Reset {{user}} password.", "resetPasswordError": "Error occurred while resetting password", diff --git a/web-ui/src/main/resources/catalog/templates/new-account.html b/web-ui/src/main/resources/catalog/templates/new-account.html index 2b6e10404ff3..cc3badcc1404 100644 --- a/web-ui/src/main/resources/catalog/templates/new-account.html +++ b/web-ui/src/main/resources/catalog/templates/new-account.html @@ -6,7 +6,12 @@

createAnAccount

newAccountInfo

-
+
@@ -20,6 +25,7 @@

createAnAccount

data-ng-model="userInfo.name" id="inputName" name="name" + required="" />
@@ -31,9 +37,13 @@

createAnAccount

data-ng-model="userInfo.surname" id="inputUsername" name="surname" + required="" />
-
+
createAnAccount data-ng-model="userInfo.username" autofocus="" class="form-control" + required="" />
@@ -61,12 +72,28 @@

createAnAccount

data-ng-model="userInfo.profile" aria-label="{{'requestedProfile' | translate}}" class="form-control" + required="" >
+ +
+ + +
@@ -139,6 +166,7 @@

createAnAccount