View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.spring.security;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Optional;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.stream.Collectors;
27  import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
28  import org.apache.syncope.core.persistence.api.entity.ExternalResource;
29  import org.apache.syncope.core.persistence.api.entity.Implementation;
30  import org.apache.syncope.core.persistence.api.entity.Realm;
31  import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
32  import org.apache.syncope.core.provisioning.api.rules.PasswordRule;
33  import org.apache.syncope.core.spring.implementation.ImplementationManager;
34  import org.apache.syncope.core.spring.policy.DefaultPasswordRule;
35  import org.passay.CharacterRule;
36  import org.passay.EnglishCharacterData;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  import org.springframework.transaction.annotation.Transactional;
40  
41  /**
42   * Generate random passwords according to given policies.
43   * When no minimum and / or maximum length are specified, default values are set.
44   *
45   * <strong>WARNING</strong>: This class only takes {@link DefaultPasswordRuleConf} into account.
46   */
47  public class DefaultPasswordGenerator implements PasswordGenerator {
48  
49      protected static final Logger LOG = LoggerFactory.getLogger(PasswordGenerator.class);
50  
51      protected static final int VERY_MIN_LENGTH = 0;
52  
53      protected static final int VERY_MAX_LENGTH = 64;
54  
55      protected static final int MIN_LENGTH_IF_ZERO = 8;
56  
57      protected final Map<String, PasswordRule> perContextPasswordRules = new ConcurrentHashMap<>();
58  
59      @Transactional(readOnly = true)
60      @Override
61      public String generate(final ExternalResource resource, final List<Realm> realms) {
62          List<PasswordPolicy> policies = new ArrayList<>();
63  
64          // add resource policy
65          Optional.ofNullable(resource.getPasswordPolicy()).ifPresent(policies::add);
66  
67          // add realm policies
68          realms.forEach(r -> Optional.ofNullable(r.getPasswordPolicy()).
69                  filter(p -> !policies.contains(p)).
70                  ifPresent(policies::add));
71  
72          return generate(policies);
73      }
74  
75      protected List<PasswordRule> getPasswordRules(final PasswordPolicy policy) {
76          List<PasswordRule> result = new ArrayList<>();
77  
78          for (Implementation impl : policy.getRules()) {
79              try {
80                  ImplementationManager.buildPasswordRule(
81                          impl,
82                          () -> perContextPasswordRules.get(impl.getKey()),
83                          instance -> perContextPasswordRules.put(impl.getKey(), instance)).
84                          ifPresent(result::add);
85              } catch (Exception e) {
86                  LOG.warn("While building {}", impl, e);
87              }
88          }
89  
90          return result;
91      }
92  
93      @Override
94      public String generate(final List<PasswordPolicy> policies) {
95          List<DefaultPasswordRuleConf> ruleConfs = new ArrayList<>();
96  
97          policies.stream().forEach(policy -> getPasswordRules(policy).stream().
98                  filter(rule -> rule.getConf() instanceof DefaultPasswordRuleConf).
99                  forEach(rule -> ruleConfs.add((DefaultPasswordRuleConf) rule.getConf())));
100 
101         return generate(merge(ruleConfs));
102     }
103 
104     protected DefaultPasswordRuleConf merge(final List<DefaultPasswordRuleConf> defaultRuleConfs) {
105         DefaultPasswordRuleConf result = new DefaultPasswordRuleConf();
106         result.setMinLength(VERY_MIN_LENGTH);
107         result.setMaxLength(VERY_MAX_LENGTH);
108 
109         defaultRuleConfs.forEach(ruleConf -> {
110             if (ruleConf.getMinLength() > result.getMinLength()) {
111                 result.setMinLength(ruleConf.getMinLength());
112             }
113 
114             if (ruleConf.getMaxLength() > 0 && ruleConf.getMaxLength() < result.getMaxLength()) {
115                 result.setMaxLength(ruleConf.getMaxLength());
116             }
117 
118             if (ruleConf.getAlphabetical() > result.getAlphabetical()) {
119                 result.setAlphabetical(ruleConf.getAlphabetical());
120             }
121 
122             if (ruleConf.getUppercase() > result.getUppercase()) {
123                 result.setUppercase(ruleConf.getUppercase());
124             }
125 
126             if (ruleConf.getLowercase() > result.getLowercase()) {
127                 result.setLowercase(ruleConf.getLowercase());
128             }
129 
130             if (ruleConf.getDigit() > result.getDigit()) {
131                 result.setDigit(ruleConf.getDigit());
132             }
133 
134             if (ruleConf.getSpecial() > result.getSpecial()) {
135                 result.setSpecial(ruleConf.getSpecial());
136             }
137 
138             if (!ruleConf.getSpecialChars().isEmpty()) {
139                 result.getSpecialChars().addAll(ruleConf.getSpecialChars().stream().
140                         filter(c -> !result.getSpecialChars().contains(c)).collect(Collectors.toList()));
141             }
142 
143             if (!ruleConf.getIllegalChars().isEmpty()) {
144                 result.getIllegalChars().addAll(ruleConf.getIllegalChars().stream().
145                         filter(c -> !result.getIllegalChars().contains(c)).collect(Collectors.toList()));
146             }
147 
148             if (ruleConf.getRepeatSame() > result.getRepeatSame()) {
149                 result.setRepeatSame(ruleConf.getRepeatSame());
150             }
151 
152             if (!result.isUsernameAllowed()) {
153                 result.setUsernameAllowed(ruleConf.isUsernameAllowed());
154             }
155 
156             if (!ruleConf.getWordsNotPermitted().isEmpty()) {
157                 result.getWordsNotPermitted().addAll(ruleConf.getWordsNotPermitted().stream().
158                         filter(w -> !result.getWordsNotPermitted().contains(w)).collect(Collectors.toList()));
159             }
160         });
161 
162         if (result.getMinLength() == 0) {
163             result.setMinLength(
164                     result.getMaxLength() < MIN_LENGTH_IF_ZERO ? result.getMaxLength() : MIN_LENGTH_IF_ZERO);
165         }
166         if (result.getMinLength() > result.getMaxLength()) {
167             result.setMaxLength(result.getMinLength());
168         }
169 
170         return result;
171     }
172 
173     protected String generate(final DefaultPasswordRuleConf ruleConf) {
174         List<CharacterRule> characterRules = DefaultPasswordRule.conf2Rules(ruleConf).stream().
175                 filter(CharacterRule.class::isInstance).map(CharacterRule.class::cast).
176                 collect(Collectors.toList());
177         if (characterRules.isEmpty()) {
178             int halfMinLength = ruleConf.getMinLength() / 2;
179             characterRules = List.of(
180                     new CharacterRule(EnglishCharacterData.Alphabetical, halfMinLength),
181                     new CharacterRule(EnglishCharacterData.Digit, halfMinLength));
182         }
183         int min = Math.max(ruleConf.getMinLength(),
184                 characterRules.stream().mapToInt(CharacterRule::getNumberOfCharacters).sum());
185         return SecureRandomUtils.passwordGenerator().generatePassword(min, characterRules);
186     }
187 }