1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.spring.policy;
20
21 import java.io.InputStream;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Objects;
27 import java.util.Properties;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 import org.apache.commons.lang3.ArrayUtils;
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
33 import org.apache.syncope.common.lib.policy.PasswordRuleConf;
34 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
35 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
36 import org.apache.syncope.core.persistence.api.entity.user.User;
37 import org.apache.syncope.core.provisioning.api.rules.PasswordRule;
38 import org.apache.syncope.core.provisioning.api.rules.PasswordRuleConfClass;
39 import org.apache.syncope.core.spring.security.Encryptor;
40 import org.passay.CharacterData;
41 import org.passay.CharacterRule;
42 import org.passay.EnglishCharacterData;
43 import org.passay.IllegalCharacterRule;
44 import org.passay.LengthRule;
45 import org.passay.PasswordData;
46 import org.passay.PasswordValidator;
47 import org.passay.PropertiesMessageResolver;
48 import org.passay.RepeatCharactersRule;
49 import org.passay.Rule;
50 import org.passay.RuleResult;
51 import org.passay.UsernameRule;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.springframework.transaction.annotation.Transactional;
55 import org.springframework.util.CollectionUtils;
56
57 @PasswordRuleConfClass(DefaultPasswordRuleConf.class)
58 public class DefaultPasswordRule implements PasswordRule {
59
60 protected static final Logger LOG = LoggerFactory.getLogger(DefaultPasswordRule.class);
61
62 protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
63
64 public static List<Rule> conf2Rules(final DefaultPasswordRuleConf conf) {
65 List<Rule> rules = new ArrayList<>();
66
67 LengthRule lengthRule = new LengthRule();
68 if (conf.getMinLength() > 0) {
69 lengthRule.setMinimumLength(conf.getMinLength());
70 }
71 if (conf.getMaxLength() > 0) {
72 lengthRule.setMaximumLength(conf.getMaxLength());
73 }
74 rules.add(lengthRule);
75
76 if (conf.getAlphabetical() > 0) {
77 rules.add(new CharacterRule(EnglishCharacterData.Alphabetical, conf.getAlphabetical()));
78 }
79
80 if (conf.getUppercase() > 0) {
81 rules.add(new CharacterRule(EnglishCharacterData.UpperCase, conf.getUppercase()));
82 }
83
84 if (conf.getLowercase() > 0) {
85 rules.add(new CharacterRule(EnglishCharacterData.LowerCase, conf.getLowercase()));
86 }
87
88 if (conf.getDigit() > 0) {
89 rules.add(new CharacterRule(EnglishCharacterData.Digit, conf.getDigit()));
90 }
91
92 if (conf.getSpecial() > 0) {
93 rules.add(new CharacterRule(new CharacterData() {
94
95 @Override
96 public String getErrorCode() {
97 return "INSUFFICIENT_SPECIAL";
98 }
99
100 @Override
101 public String getCharacters() {
102 return new String(ArrayUtils.toPrimitive(conf.getSpecialChars().toArray(Character[]::new)));
103 }
104 }, conf.getSpecial()));
105 }
106
107 if (!conf.getIllegalChars().isEmpty()) {
108 rules.add(new IllegalCharacterRule(
109 ArrayUtils.toPrimitive(conf.getIllegalChars().toArray(Character[]::new))));
110 }
111
112 if (conf.getRepeatSame() > 0) {
113 rules.add(new RepeatCharactersRule(conf.getRepeatSame()));
114 }
115
116 if (!conf.isUsernameAllowed()) {
117 rules.add(new UsernameRule(true, true));
118 }
119
120 return rules;
121 }
122
123 protected DefaultPasswordRuleConf conf;
124
125 protected PasswordValidator passwordValidator;
126
127 @Override
128 public PasswordRuleConf getConf() {
129 return conf;
130 }
131
132 @Override
133 public void setConf(final PasswordRuleConf conf) {
134 if (conf instanceof DefaultPasswordRuleConf) {
135 this.conf = (DefaultPasswordRuleConf) conf;
136
137 Properties passay = new Properties();
138 try (InputStream in = getClass().getResourceAsStream("/passay.properties")) {
139 passay.load(in);
140 passwordValidator = new PasswordValidator(new PropertiesMessageResolver(passay), conf2Rules(this.conf));
141 } catch (Exception e) {
142 throw new IllegalStateException("Could not initialize Passay", e);
143 }
144 } else {
145 throw new IllegalArgumentException(
146 DefaultPasswordRuleConf.class.getName() + " expected, got " + conf.getClass().getName());
147 }
148 }
149
150 protected void enforce(final String clear, final String username, final Set<String> wordsNotPermitted) {
151 RuleResult result = passwordValidator.validate(
152 username == null ? new PasswordData(clear) : new PasswordData(username, clear));
153 if (!result.isValid()) {
154 throw new PasswordPolicyException(passwordValidator.getMessages(result).
155 stream().collect(Collectors.joining(",")));
156 }
157
158
159 wordsNotPermitted.stream().
160 filter(word -> StringUtils.containsIgnoreCase(clear, word)).findFirst().
161 ifPresent(word -> {
162 throw new PasswordPolicyException("Used word(s) not permitted");
163 });
164 }
165
166 @Override
167 public void enforce(final String username, final String clearPassword) {
168 if (clearPassword != null) {
169 Set<String> wordsNotPermitted = new HashSet<>(conf.getWordsNotPermitted());
170 enforce(clearPassword, username, wordsNotPermitted);
171 }
172 }
173
174 @Transactional(readOnly = true)
175 @Override
176 public void enforce(final User user, final String clearPassword) {
177 if (clearPassword != null) {
178 Set<String> wordsNotPermitted = new HashSet<>(conf.getWordsNotPermitted());
179 wordsNotPermitted.addAll(
180 conf.getSchemasNotPermitted().stream().
181 map(schema -> user.getPlainAttr(schema).
182 map(PlainAttr::getValuesAsStrings).orElse(null)).
183 filter(Objects::nonNull).
184 filter(values -> !CollectionUtils.isEmpty(values)).
185 flatMap(Collection::stream).
186 collect(Collectors.toSet()));
187
188 enforce(clearPassword, user.getUsername(), wordsNotPermitted);
189 }
190 }
191
192 @Transactional(readOnly = true)
193 @Override
194 public void enforce(final LinkedAccount account) {
195 conf.getWordsNotPermitted().addAll(
196 conf.getSchemasNotPermitted().stream().
197 map(schema -> account.getPlainAttr(schema).
198 map(PlainAttr::getValuesAsStrings).orElse(null)).
199 filter(Objects::nonNull).
200 filter(values -> !CollectionUtils.isEmpty(values)).
201 flatMap(Collection::stream).
202 collect(Collectors.toList()));
203
204 if (account.getPassword() != null) {
205 String clear = null;
206 if (account.canDecodeSecrets()) {
207 try {
208 clear = ENCRYPTOR.decode(account.getPassword(), account.getCipherAlgorithm());
209 } catch (Exception e) {
210 LOG.error("Could not decode password for {}", account, e);
211 }
212 }
213
214 if (clear != null) {
215 Set<String> wordsNotPermitted = new HashSet<>(conf.getWordsNotPermitted());
216 wordsNotPermitted.addAll(
217 conf.getSchemasNotPermitted().stream().
218 map(schema -> account.getPlainAttr(schema).
219 map(PlainAttr::getValuesAsStrings).orElse(null)).
220 filter(Objects::nonNull).
221 filter(values -> !CollectionUtils.isEmpty(values)).
222 flatMap(Collection::stream).
223 collect(Collectors.toSet()));
224
225 enforce(clear, account.getUsername(), wordsNotPermitted);
226 }
227 }
228 }
229 }