Revision a8e937e5
Added by Andreas Müller almost 2 years ago
cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/permission/User.java | ||
---|---|---|
48 | 48 |
|
49 | 49 |
import eu.etaxonomy.cdm.model.agent.Person; |
50 | 50 |
import eu.etaxonomy.cdm.model.common.CdmBase; |
51 |
import eu.etaxonomy.cdm.validation.Level2; |
|
52 |
import eu.etaxonomy.cdm.validation.annotation.ValidPassword; |
|
51 | 53 |
|
52 | 54 |
@XmlAccessorType(XmlAccessType.FIELD) |
53 | 55 |
@XmlType(name = "User", propOrder = { |
... | ... | |
114 | 116 |
*/ |
115 | 117 |
@XmlElement(name = "Password") |
116 | 118 |
@NotAudited |
119 |
@ValidPassword(groups=Level2.class) |
|
117 | 120 |
protected String password; |
118 | 121 |
|
119 | 122 |
|
cdmlib-model/src/main/java/eu/etaxonomy/cdm/validation/annotation/ValidPassword.java | ||
---|---|---|
20 | 20 |
import javax.validation.Constraint; |
21 | 21 |
import javax.validation.Payload; |
22 | 22 |
|
23 |
import eu.etaxonomy.cdm.validation.constraint.PasswordConstraintValidator;
|
|
23 |
import eu.etaxonomy.cdm.validation.constraint.ValidPasswordValidator;
|
|
24 | 24 |
|
25 | 25 |
/** |
26 | 26 |
* @author a.kohlbecker |
27 | 27 |
* @since Nov 12, 2021 |
28 | 28 |
*/ |
29 | 29 |
@Documented |
30 |
@Constraint(validatedBy = PasswordConstraintValidator.class)
|
|
30 |
@Constraint(validatedBy = ValidPasswordValidator.class)
|
|
31 | 31 |
@Target({ TYPE, FIELD, ANNOTATION_TYPE }) |
32 | 32 |
@Retention(RUNTIME) |
33 | 33 |
public @interface ValidPassword { |
cdmlib-model/src/main/java/eu/etaxonomy/cdm/validation/constraint/PasswordConstraintValidator.java | ||
---|---|---|
1 |
/** |
|
2 |
* Copyright (C) 2021 EDIT |
|
3 |
* European Distributed Institute of Taxonomy |
|
4 |
* http://www.e-taxonomy.eu |
|
5 |
* |
|
6 |
* The contents of this file are subject to the Mozilla Public License Version 1.1 |
|
7 |
* See LICENSE.TXT at the top of this package for the full license terms. |
|
8 |
*/ |
|
9 |
package eu.etaxonomy.cdm.validation.constraint; |
|
10 |
|
|
11 |
import java.util.ArrayList; |
|
12 |
import java.util.Arrays; |
|
13 |
import java.util.List; |
|
14 |
import java.util.stream.Collectors; |
|
15 |
|
|
16 |
import javax.validation.ConstraintValidator; |
|
17 |
import javax.validation.ConstraintValidatorContext; |
|
18 |
|
|
19 |
import org.passay.CharacterRule; |
|
20 |
import org.passay.EnglishCharacterData; |
|
21 |
import org.passay.LengthRule; |
|
22 |
import org.passay.PasswordData; |
|
23 |
import org.passay.PasswordData.Origin; |
|
24 |
import org.passay.PasswordValidator; |
|
25 |
import org.passay.RuleResult; |
|
26 |
import org.passay.WhitespaceRule; |
|
27 |
|
|
28 |
import eu.etaxonomy.cdm.validation.annotation.ValidPassword; |
|
29 |
|
|
30 |
|
|
31 |
/** |
|
32 |
* @author a.kohlbecker |
|
33 |
* @since Nov 12, 2021 |
|
34 |
*/ |
|
35 |
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> { |
|
36 |
|
|
37 |
@Override |
|
38 |
public boolean isValid(String value, ConstraintValidatorContext context) { |
|
39 |
|
|
40 |
final PasswordValidator validator = defaultPasswordValidator(); |
|
41 |
final RuleResult result = validator.validate(new PasswordData(value)); |
|
42 |
if (result.isValid()) { |
|
43 |
return true; |
|
44 |
} |
|
45 |
context.disableDefaultConstraintViolation(); |
|
46 |
context.buildConstraintViolationWithTemplate( |
|
47 |
validator.getMessages(result).stream().collect(Collectors.joining(" "))).addConstraintViolation(); |
|
48 |
return false; |
|
49 |
} |
|
50 |
|
|
51 |
private static PasswordValidator defaultPasswordValidator() { |
|
52 |
return new PasswordValidator(Arrays.asList( |
|
53 |
// see https://www.passay.org/reference/ |
|
54 |
|
|
55 |
// length between 8 and 16 characters |
|
56 |
new LengthRule(8, Integer.MAX_VALUE), |
|
57 |
|
|
58 |
// at least one upper-case character |
|
59 |
new CharacterRule(EnglishCharacterData.UpperCase, 1), |
|
60 |
|
|
61 |
// at least one lower-case character |
|
62 |
new CharacterRule(EnglishCharacterData.LowerCase, 1), |
|
63 |
|
|
64 |
// at least one digit character |
|
65 |
new CharacterRule(EnglishCharacterData.Digit, 1), |
|
66 |
|
|
67 |
// // at least one symbol (special character) |
|
68 |
// new CharacterRule(EnglishCharacterData.Special, 1), |
|
69 |
|
|
70 |
// no whitespace |
|
71 |
new WhitespaceRule())); |
|
72 |
} |
|
73 |
|
|
74 |
public static class PasswordRulesValidator { |
|
75 |
|
|
76 |
private PasswordValidator validator = PasswordConstraintValidator.defaultPasswordValidator(); |
|
77 |
|
|
78 |
/** |
|
79 |
* Validate a password which was generated by a typical human user |
|
80 |
* |
|
81 |
* @param password |
|
82 |
* The password to validate |
|
83 |
* @return In case of rule violations the returned lost contains the |
|
84 |
* violation messages, other wise the lost is empty. |
|
85 |
*/ |
|
86 |
public List<String> validateUserPassword(String password) { |
|
87 |
return readViolationMessageList(validator.validate(new PasswordData(password))); |
|
88 |
} |
|
89 |
|
|
90 |
/** |
|
91 |
* Validate a password which was generated by a random source |
|
92 |
* |
|
93 |
* @param password |
|
94 |
* The password to validate |
|
95 |
* @return In case of rule violations the returned lost contains the |
|
96 |
* violation messages, other wise the lost is empty. |
|
97 |
*/ |
|
98 |
public List<String> validateGeneratedPassword(String password) { |
|
99 |
return readViolationMessageList(validator.validate(new PasswordData(password, Origin.Generated))); |
|
100 |
} |
|
101 |
|
|
102 |
private List<String> readViolationMessageList(RuleResult validate) { |
|
103 |
if (validate.isValid()) { |
|
104 |
return new ArrayList<>(0); |
|
105 |
} |
|
106 |
return validator.getMessages(validate); |
|
107 |
} |
|
108 |
|
|
109 |
protected PasswordValidator getValidator() { |
|
110 |
return validator; |
|
111 |
} |
|
112 |
} |
|
113 |
|
|
114 |
|
|
115 |
|
|
116 |
} |
cdmlib-model/src/main/java/eu/etaxonomy/cdm/validation/constraint/ValidPasswordValidator.java | ||
---|---|---|
1 |
/** |
|
2 |
* Copyright (C) 2021 EDIT |
|
3 |
* European Distributed Institute of Taxonomy |
|
4 |
* http://www.e-taxonomy.eu |
|
5 |
* |
|
6 |
* The contents of this file are subject to the Mozilla Public License Version 1.1 |
|
7 |
* See LICENSE.TXT at the top of this package for the full license terms. |
|
8 |
*/ |
|
9 |
package eu.etaxonomy.cdm.validation.constraint; |
|
10 |
|
|
11 |
import java.util.ArrayList; |
|
12 |
import java.util.Arrays; |
|
13 |
import java.util.List; |
|
14 |
import java.util.stream.Collectors; |
|
15 |
|
|
16 |
import javax.validation.ConstraintValidator; |
|
17 |
import javax.validation.ConstraintValidatorContext; |
|
18 |
|
|
19 |
import org.passay.CharacterRule; |
|
20 |
import org.passay.EnglishCharacterData; |
|
21 |
import org.passay.LengthRule; |
|
22 |
import org.passay.PasswordData; |
|
23 |
import org.passay.PasswordData.Origin; |
|
24 |
import org.passay.PasswordValidator; |
|
25 |
import org.passay.RuleResult; |
|
26 |
import org.passay.WhitespaceRule; |
|
27 |
|
|
28 |
import eu.etaxonomy.cdm.validation.annotation.ValidPassword; |
|
29 |
|
|
30 |
|
|
31 |
/** |
|
32 |
* @author a.kohlbecker |
|
33 |
* @since Nov 12, 2021 |
|
34 |
*/ |
|
35 |
public class ValidPasswordValidator implements ConstraintValidator<ValidPassword, String> { |
|
36 |
|
|
37 |
public static PasswordValidator defaultValidator; |
|
38 |
|
|
39 |
@Override |
|
40 |
public boolean isValid(String value, ConstraintValidatorContext context) { |
|
41 |
|
|
42 |
final PasswordValidator validator = getDefaultPasswordValidator(); |
|
43 |
String message; |
|
44 |
if (value != null) { |
|
45 |
final RuleResult result = validator.validate(new PasswordData(value)); |
|
46 |
if (result.isValid()) { |
|
47 |
return true; |
|
48 |
} |
|
49 |
message = validator.getMessages(result).stream().collect(Collectors.joining(" ")); |
|
50 |
}else { |
|
51 |
message = "Null is not allowed for password"; |
|
52 |
} |
|
53 |
context.disableDefaultConstraintViolation(); |
|
54 |
|
|
55 |
context.buildConstraintViolationWithTemplate(message).addConstraintViolation(); |
|
56 |
return false; |
|
57 |
} |
|
58 |
|
|
59 |
private static PasswordValidator getDefaultPasswordValidator() { |
|
60 |
if (defaultValidator == null) { |
|
61 |
defaultValidator = new PasswordValidator(Arrays.asList( |
|
62 |
// see https://www.passay.org/reference/ |
|
63 |
|
|
64 |
// length between 8 and 16 characters |
|
65 |
new LengthRule(8, 256), |
|
66 |
|
|
67 |
// at least one upper-case character |
|
68 |
new CharacterRule(EnglishCharacterData.UpperCase, 1), |
|
69 |
|
|
70 |
// at least one lower-case character |
|
71 |
new CharacterRule(EnglishCharacterData.LowerCase, 1), |
|
72 |
|
|
73 |
// at least one digit character |
|
74 |
new CharacterRule(EnglishCharacterData.Digit, 1), |
|
75 |
|
|
76 |
// // at least one symbol (special character) |
|
77 |
// new CharacterRule(EnglishCharacterData.Special, 1), |
|
78 |
|
|
79 |
// no whitespace |
|
80 |
new WhitespaceRule())); |
|
81 |
} |
|
82 |
return defaultValidator; |
|
83 |
} |
|
84 |
|
|
85 |
public static class PasswordRulesValidator { |
|
86 |
|
|
87 |
private PasswordValidator validator = defaultValidator; |
|
88 |
|
|
89 |
/** |
|
90 |
* Validate a password which was generated by a typical human user |
|
91 |
* |
|
92 |
* @param password |
|
93 |
* The password to validate |
|
94 |
* @return In case of rule violations the returned lost contains the |
|
95 |
* violation messages, other wise the lost is empty. |
|
96 |
*/ |
|
97 |
public List<String> validateUserPassword(String password) { |
|
98 |
return readViolationMessageList(validator.validate(new PasswordData(password))); |
|
99 |
} |
|
100 |
|
|
101 |
/** |
|
102 |
* Validate a password which was generated by a random source |
|
103 |
* |
|
104 |
* @param password |
|
105 |
* The password to validate |
|
106 |
* @return In case of rule violations the returned lost contains the |
|
107 |
* violation messages, other wise the lost is empty. |
|
108 |
*/ |
|
109 |
public List<String> validateGeneratedPassword(String password) { |
|
110 |
return readViolationMessageList(validator.validate(new PasswordData(password, Origin.Generated))); |
|
111 |
} |
|
112 |
|
|
113 |
private List<String> readViolationMessageList(RuleResult validate) { |
|
114 |
if (validate.isValid()) { |
|
115 |
return new ArrayList<>(0); |
|
116 |
} |
|
117 |
return validator.getMessages(validate); |
|
118 |
} |
|
119 |
|
|
120 |
protected PasswordValidator getValidator() { |
|
121 |
return validator; |
|
122 |
} |
|
123 |
} |
|
124 |
|
|
125 |
|
|
126 |
|
|
127 |
} |
cdmlib-model/src/test/java/eu/etaxonomy/cdm/validation/ValidPasswordTest.java | ||
---|---|---|
1 |
/** |
|
2 |
* Copyright (C) 2022 EDIT |
|
3 |
* European Distributed Institute of Taxonomy |
|
4 |
* http://www.e-taxonomy.eu |
|
5 |
* |
|
6 |
* The contents of this file are subject to the Mozilla Public License Version 1.1 |
|
7 |
* See LICENSE.TXT at the top of this package for the full license terms. |
|
8 |
*/ |
|
9 |
package eu.etaxonomy.cdm.validation; |
|
10 |
|
|
11 |
import static org.junit.Assert.assertTrue; |
|
12 |
|
|
13 |
import java.util.Set; |
|
14 |
|
|
15 |
import javax.validation.ConstraintViolation; |
|
16 |
|
|
17 |
import org.apache.commons.lang3.StringUtils; |
|
18 |
import org.apache.log4j.Logger; |
|
19 |
import org.junit.Before; |
|
20 |
import org.junit.Test; |
|
21 |
|
|
22 |
import eu.etaxonomy.cdm.model.permission.User; |
|
23 |
import eu.etaxonomy.cdm.validation.constraint.ValidPasswordValidator; |
|
24 |
|
|
25 |
/** |
|
26 |
* @author a.mueller |
|
27 |
* @date 21.01.2022 |
|
28 |
*/ |
|
29 |
public class ValidPasswordTest extends ValidationTestBase { |
|
30 |
|
|
31 |
@SuppressWarnings("unused") |
|
32 |
private static final Logger logger = Logger.getLogger(ValidPasswordTest.class); |
|
33 |
|
|
34 |
private User user; |
|
35 |
|
|
36 |
private static final String valid = "Aa345678"; |
|
37 |
|
|
38 |
@Before |
|
39 |
public void setUp() { |
|
40 |
|
|
41 |
} |
|
42 |
|
|
43 |
/****************** TESTS *****************************/ |
|
44 |
|
|
45 |
@Test |
|
46 |
public final void testValidPassword() { |
|
47 |
|
|
48 |
user = User.NewInstance("testuser", valid); |
|
49 |
|
|
50 |
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user, Level2.class); |
|
51 |
assertTrue("There should not be a constraint violation as the 'valid' password fullfils all requirements",constraintViolations.isEmpty()); |
|
52 |
|
|
53 |
user.setPassword(StringUtils.leftPad(valid, 256, 'a')); |
|
54 |
assertTrue("There should not be a constraint violation as up to 256 characters are allowed",constraintViolations.isEmpty()); |
|
55 |
|
|
56 |
} |
|
57 |
|
|
58 |
@Test |
|
59 |
public final void testNotValidPassword() { |
|
60 |
user = User.NewInstance("testuser", null); |
|
61 |
|
|
62 |
validateHasConstraint(user, ValidPasswordValidator.class, Level2.class); |
|
63 |
|
|
64 |
user.setPassword(""); |
|
65 |
validateHasConstraint(user, ValidPasswordValidator.class, Level2.class); |
|
66 |
|
|
67 |
user.setPassword("A"); |
|
68 |
validateHasConstraint(user, ValidPasswordValidator.class, Level2.class); |
|
69 |
user.setPassword(valid.substring(0, 7)); |
|
70 |
validateHasConstraint(user, ValidPasswordValidator.class, Level2.class); |
|
71 |
|
|
72 |
user.setPassword(StringUtils.leftPad(valid, 257, 'a')); |
|
73 |
validateHasConstraint(user, ValidPasswordValidator.class, Level2.class); |
|
74 |
|
|
75 |
|
|
76 |
} |
|
77 |
|
|
78 |
} |
Also available in: Unified diff
ref #9862 rename password validator class, add tests, add level2 validation to User, add maxLength and handle null