Skip to content

Commit

Permalink
Register user / allow to configured allowed email domains (#8186)
Browse files Browse the repository at this point in the history
* Register user / allow to configured allowed email domains

* Register user / allow to configured allowed email domains - update migration scripts

* Revert accidental change in Messages.properties
  • Loading branch information
josegar74 committed Jun 19, 2024
1 parent 6b95d5c commit 76f68d9
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2001-2023 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)
*
Expand Down Expand Up @@ -83,6 +83,7 @@ public class Settings {
public static final String SYSTEM_CSW_CAPABILITY_RECORD_UUID = "system/csw/capabilityRecordUuid";
public static final String SYSTEM_CSW_METADATA_PUBLIC = "system/csw/metadataPublic";
public static final String SYSTEM_USERSELFREGISTRATION_ENABLE = "system/userSelfRegistration/enable";
public static final String SYSTEM_USERSELFREGISTRATION_EMAIL_DOMAINS = "system/userSelfRegistration/domainsAllowed";
public static final String SYSTEM_USERSELFREGISTRATION_RECAPTCHA_ENABLE = "system/userSelfRegistration/recaptcha/enable";
public static final String SYSTEM_USERSELFREGISTRATION_RECAPTCHA_PUBLICKEY = "system/userSelfRegistration/recaptcha/publickey";
public static final String SYSTEM_USERSELFREGISTRATION_RECAPTCHA_SECRETKEY = "system/userSelfRegistration/recaptcha/secretkey";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,16 @@ user_watchlist_message=The following records have been updated:\n\
</div>\n\
</div>
self_registration_disabled=User self-registration is disabled
self_registration_no_valid_mail=The email address is not allowed
recaptcha_not_valid=Recaptcha is not valid
metadata.title.createdFromTemplate=Copy of template %s created at %s
metadata.title.createdFromRecord=Copy of record %s created at %s
username.field.required=Username is required
password.field.length=Password size should be between {min} and {max} characters
password.field.invalid=Password must contain at least 1 uppercase, 1 lowercase, 1 number and 1 symbol. Symbols include: `~!@#$%^&*()-_=+[]{}\\|;:'",.<>/?');
field.required.name=Name is required
field.required.email=Email address is required
field.notvalid.email=Email address is not valid
api.exception.forbidden=Access denied
api.exception.forbidden.description=Access is denied. To access, try again with a user containing more privileges.
api.exception.resourceNotFound=Resource not found
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,14 @@ user_watchlist_message=Les fiches suivantes ont \u00E9t\u00E9 mises \u00e0 jour
</div>\n\
</div>
self_registration_disabled=La cr\u00E9ation de compte par les utilisateurs est d\u00E9sactiv\u00E9e
self_registration_no_valid_mail=L''adresse email n''est pas autoris\u00E9e
recaptcha_not_valid=Recaptcha invalide
metadata.title.createdFromTemplate=Copie du mod\u00e8le %s cr\u00E9\u00E9e le %s
metadata.title.createdFromRecord=Copie de la fiche %s cr\u00E9\u00E9e le %s
username.field.required=Le nom d''utilisateur est requis
field.required.name=Nom est obligatoire
field.required.email=L''adresse mail est obligatoire
field.notvalid.email=L''adresse mail n''est pas valide
password.field.length=Le mot de passe doit contenir entre {min} et {max} caract\u00E8res
password.field.invalid=Le mot de passe doit contenir a minima 1 lettre majuscule, 1 minuscule, 1 chiffre et 1 symbole (ie. `~!@#$%^&*()-_=+[]{}\\|;:'",.<>/?'));
api.exception.forbidden=L''acc\u00E8s est refus\u00E9
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ See [Configuring Shibboleth](../managing-users-and-groups/authentication-mode.md

Enable the self registration form. See [User Self-Registration](../managing-users-and-groups/user-self-registration.md).

You can configure optionally re-Captcha, to protect you and your users from spam and abuse. And a list of email domains (separated by commas)
that can request an account. If not configured any email address is allowed.

## system/userFeedback

!!! warning "Deprecated"
Expand Down
50 changes: 22 additions & 28 deletions services/src/main/java/org/fao/geonet/api/users/RegisterApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.fao.geonet.api.tools.i18n.LanguageUtils;
import org.fao.geonet.api.users.model.UserRegisterDto;
import org.fao.geonet.api.users.recaptcha.RecaptchaChecker;
import org.fao.geonet.api.users.validation.UserRegisterDtoValidator;
import org.fao.geonet.domain.*;
import org.fao.geonet.kernel.security.SecurityProviderConfiguration;
import org.fao.geonet.kernel.setting.SettingManager;
Expand All @@ -46,7 +47,6 @@
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;

Expand All @@ -68,6 +68,9 @@ public class RegisterApi {
@Autowired(required=false)
SecurityProviderConfiguration securityProviderConfiguration;

@Autowired
UserRepository userRepository;

@Autowired
GroupRepository groupRepository;

Expand Down Expand Up @@ -123,39 +126,30 @@ public ResponseEntity<String> registerUser(
}
}

// Validate the user registration
if (bindingResult.hasErrors()) {
List<ObjectError> errorList = bindingResult.getAllErrors();

StringBuilder sb = new StringBuilder();
Iterator<ObjectError> it = errorList.iterator();
while (it.hasNext()) {
sb.append(messages.getString(it.next().getDefaultMessage()));
if (it.hasNext()) {
sb.append(", ");
}
}

return new ResponseEntity<>(sb.toString(), HttpStatus.PRECONDITION_FAILED);
// Validate userDto data
UserRegisterDtoValidator userRegisterDtoValidator = new UserRegisterDtoValidator();
userRegisterDtoValidator.validate(userRegisterDto, bindingResult);
String errorMessage = ApiUtils.processRequestValidation(bindingResult, messages);
if (org.apache.commons.lang.StringUtils.isNotEmpty(errorMessage)) {
return new ResponseEntity<>(errorMessage, HttpStatus.PRECONDITION_FAILED);
}

final UserRepository userRepository = context.getBean(UserRepository.class);
if (userRepository.findOneByEmail(userRegisterDto.getEmail()) != null) {
return new ResponseEntity<>(String.format(
messages.getString("user_with_that_email_found"),
userRegisterDto.getEmail()
), HttpStatus.PRECONDITION_FAILED);
}

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"),
userRegisterDto.getEmail()
), HttpStatus.PRECONDITION_FAILED);
String emailDomainsAllowed = settingManager.getValue(Settings.SYSTEM_USERSELFREGISTRATION_EMAIL_DOMAINS);
if (StringUtils.hasLength(emailDomainsAllowed)) {
List<String> emailDomainsAllowedList = Arrays.asList(emailDomainsAllowed.split(","));

String userEmailDomain = userRegisterDto.getEmail().split("@")[1];

if (!emailDomainsAllowedList.contains(userEmailDomain)) {
return new ResponseEntity<>(String.format(
messages.getString("self_registration_no_valid_mail")
), HttpStatus.PRECONDITION_FAILED);
}
}

User user = new User();

user.setName(userRegisterDto.getName());
user.setSurname(userRegisterDto.getSurname());
user.setOrganisation(userRegisterDto.getOrganisation());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
* Rome - Italy. email: [email protected]
*/

package org.fao.geonet.api.users.validation;

import org.fao.geonet.ApplicationContextHolder;
import org.fao.geonet.api.users.model.UserRegisterDto;
import org.fao.geonet.constants.Params;
import org.fao.geonet.repository.UserRepository;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

/**
* Validator for UserRegisterDto.
*
*/
public class UserRegisterDtoValidator implements Validator {
private static final String OWASP_EMAIL_REGEX = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
private static final java.util.regex.Pattern OWASP_EMAIL_PATTERN = java.util.regex.Pattern.compile(OWASP_EMAIL_REGEX);

@Override
public boolean supports(Class<?> clazz) {
return UserRegisterDto.class.isAssignableFrom(clazz);
}

@Override
public void validate(Object target, Errors errors) {
UserRegisterDto userRegisterDto = (UserRegisterDto) target;

ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "field.required", Params.NAME
+ " is required");

ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "field.required", Params.EMAIL
+ " is required");

if (StringUtils.hasLength(userRegisterDto.getEmail()) && !OWASP_EMAIL_PATTERN.matcher(userRegisterDto.getEmail()).matches()) {
errors.rejectValue("email", "field.notvalid", "Email address is not valid");
}

UserRepository userRepository = ApplicationContextHolder.get().getBean(UserRepository.class);
if (userRepository.findOneByEmail(userRegisterDto.getEmail()) != null) {
errors.rejectValue("", "user_with_that_email_found", "A user with this email or username already exists.");
}

if (userRepository.findByUsernameIgnoreCase(userRegisterDto.getEmail()).size() != 0) {
errors.rejectValue("", "user_with_that_username_found", "A user with this email or username already exists.");
}
}
}
2 changes: 2 additions & 0 deletions web-ui/src/main/resources/catalog/locales/en-admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,8 @@
"system/userSelfRegistration": "User self-registration",
"system/userSelfRegistration/enable": "Enable self-registration",
"system/userSelfRegistration/enable-help": "When enabled, make sure a mail server is also configured.",
"system/userSelfRegistration/domainsAllowed": "Email domains allowed",
"system/userSelfRegistration/domainsAllowed-help": "Comma separated list of email domains that can request an account. If not configured, any email address is allowed.",
"system/userSelfRegistration/recaptcha/enable": "Enable re-captcha",
"system/userSelfRegistration/recaptcha/enable-help": "Enabling re-captcha will protect you and your users from spam and abuse. This is highly recommended when you enable feedback or self-registration. Create your re-captcha key on <a href='https://www.google.com/recaptcha/'>https://www.google.com/recaptcha/</a>",
"system/userSelfRegistration/recaptcha/publickey": "Re-captcha public key",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,16 @@ user_watchlist_message=The following records have been updated:\n\
</div>\n\
</div>
self_registration_disabled=User self-registration is disabled
self_registration_no_valid_mail=The email address is not allowed
recaptcha_not_valid=Recaptcha is not valid
metadata.title.createdFromTemplate=Copy of template %s created at %s
metadata.title.createdFromRecord=Copy of record %s created at %s
username.field.required=Username is required
password.field.length=Password size should be between {min} and {max} characters
password.field.invalid=Password must contain at least 1 uppercase, 1 lowercase, 1 number and 1 symbol. Symbols include: `~!@#$%^&*()-_=+[]{}\\|;:'",.<>/?');
field.required.name=Name is required
field.required.email=Email address is required
field.notvalid.email=Email address is not valid
api.exception.forbidden=Access denied
api.exception.forbidden.description=Access is denied. To access, try again with a user containing more privileges.
api.exception.resourceNotFound=Resource not found
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,16 @@ user_watchlist_message=Les fiches suivantes ont \u00E9t\u00E9 mises \u00e0 jour
</div>\n\
</div>
self_registration_disabled=La cr\u00E9ation de compte par les utilisateurs est d\u00E9sactiv\u00E9e
self_registration_no_valid_mail=L''adresse email n''est pas autoris\u00E9e
recaptcha_not_valid=Recaptcha invalide
metadata.title.createdFromTemplate=Copie du mod\u00e8le %s cr\u00E9\u00E9e le %s
metadata.title.createdFromRecord=Copie de la fiche %s cr\u00E9\u00E9e le %s
username.field.required=Le nom d''utilisateur est requis
password.field.length=Le mot de passe doit contenir entre {min} et {max} caract\u00E8res
password.field.invalid=Le mot de passe doit contenir a minima 1 lettre majuscule, 1 minuscule, 1 chiffre et 1 symbole (ie. `~!@#$%^&*()-_=+[]{}\\|;:'",.<>/?'));
field.required.name=Nom est obligatoire
field.required.email=L''adresse mail est obligatoire
field.notvalid.email=L''adresse mail n''est pas valide
api.exception.forbidden=L''acc\u00E8s est refus\u00E9
api.exception.forbidden.description=L''acc\u00E8s est refus\u00E9. Pour y acc\u00E9der, r\u00E9essayez avec un utilisateur disposant de plus de privil\u00E8ges.
api.exception.resourceNotFound=Ressource introuvable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -726,10 +726,11 @@ INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('metada

INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/ui/defaultView', 'default', 0, 10100, 'n');


INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/userSelfRegistration/recaptcha/enable', 'false', 2, 1910, 'n');
INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/userSelfRegistration/recaptcha/publickey', '', 0, 1910, 'n');
INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/userSelfRegistration/recaptcha/secretkey', '', 0, 1910, 'y');

INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/userSelfRegistration/domainsAllowed', '', 0, 1911, 'y');

INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/publication/doi/doienabled', 'false', 2, 100191, 'n');
INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/publication/doi/doiurl', '', 0, 100192, 'n');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
UPDATE Settings SET value='4.4.6' WHERE name='system/platform/version';
UPDATE Settings SET value='SNAPSHOT' WHERE name='system/platform/subVersion';

INSERT INTO Settings (name, value, datatype, position, internal) VALUES ('system/userSelfRegistration/domainsAllowed', '', 0, 1911, 'y');

0 comments on commit 76f68d9

Please sign in to comment.