| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.syncope.core.spring.security; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.stream.Collectors; |
| import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf; |
| import org.apache.syncope.core.persistence.api.dao.PasswordRule; |
| import org.apache.syncope.core.persistence.api.entity.ExternalResource; |
| import org.apache.syncope.core.persistence.api.entity.Implementation; |
| import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy; |
| import org.apache.syncope.core.spring.ImplementationManager; |
| import org.apache.syncope.core.spring.policy.DefaultPasswordRule; |
| import org.passay.CharacterRule; |
| import org.passay.EnglishCharacterData; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.springframework.transaction.annotation.Transactional; |
| |
| /** |
| * Generate random passwords according to given policies. |
| * When no minimum and / or maximum length are specified, default values are set. |
| * |
| * <strong>WARNING</strong>: This class only takes {@link DefaultPasswordRuleConf} into account. |
| */ |
| public class DefaultPasswordGenerator implements PasswordGenerator { |
| |
| protected static final Logger LOG = LoggerFactory.getLogger(PasswordGenerator.class); |
| |
| protected static final int VERY_MIN_LENGTH = 0; |
| |
| protected static final int VERY_MAX_LENGTH = 64; |
| |
| protected static final int MIN_LENGTH_IF_ZERO = 8; |
| |
| protected final Map<String, PasswordRule> perContextPasswordRules = new ConcurrentHashMap<>(); |
| |
| @Transactional(readOnly = true) |
| @Override |
| public String generate(final ExternalResource resource) { |
| List<PasswordPolicy> policies = new ArrayList<>(); |
| |
| if (resource.getPasswordPolicy() != null) { |
| policies.add(resource.getPasswordPolicy()); |
| } |
| |
| return generate(policies); |
| } |
| |
| protected List<PasswordRule> getPasswordRules(final PasswordPolicy policy) { |
| List<PasswordRule> result = new ArrayList<>(); |
| |
| for (Implementation impl : policy.getRules()) { |
| try { |
| ImplementationManager.buildPasswordRule( |
| impl, |
| () -> perContextPasswordRules.get(impl.getKey()), |
| instance -> perContextPasswordRules.put(impl.getKey(), instance)). |
| ifPresent(result::add); |
| } catch (Exception e) { |
| LOG.warn("While building {}", impl, e); |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public String generate(final List<PasswordPolicy> policies) { |
| List<DefaultPasswordRuleConf> ruleConfs = new ArrayList<>(); |
| |
| policies.stream().forEach(policy -> getPasswordRules(policy).stream(). |
| filter(rule -> rule.getConf() instanceof DefaultPasswordRuleConf). |
| forEach(rule -> ruleConfs.add((DefaultPasswordRuleConf) rule.getConf()))); |
| |
| return generate(merge(ruleConfs)); |
| } |
| |
| protected DefaultPasswordRuleConf merge(final List<DefaultPasswordRuleConf> defaultRuleConfs) { |
| DefaultPasswordRuleConf result = new DefaultPasswordRuleConf(); |
| result.setMinLength(VERY_MIN_LENGTH); |
| result.setMaxLength(VERY_MAX_LENGTH); |
| |
| defaultRuleConfs.forEach(ruleConf -> { |
| if (ruleConf.getMinLength() > result.getMinLength()) { |
| result.setMinLength(ruleConf.getMinLength()); |
| } |
| |
| if (ruleConf.getMaxLength() > 0 && ruleConf.getMaxLength() < result.getMaxLength()) { |
| result.setMaxLength(ruleConf.getMaxLength()); |
| } |
| |
| if (ruleConf.getAlphabetical() > result.getAlphabetical()) { |
| result.setAlphabetical(ruleConf.getAlphabetical()); |
| } |
| |
| if (ruleConf.getUppercase() > result.getUppercase()) { |
| result.setUppercase(ruleConf.getUppercase()); |
| } |
| |
| if (ruleConf.getLowercase() > result.getLowercase()) { |
| result.setLowercase(ruleConf.getLowercase()); |
| } |
| |
| if (ruleConf.getDigit() > result.getDigit()) { |
| result.setDigit(ruleConf.getDigit()); |
| } |
| |
| if (ruleConf.getSpecial() > result.getSpecial()) { |
| result.setSpecial(ruleConf.getSpecial()); |
| } |
| |
| if (!ruleConf.getSpecialChars().isEmpty()) { |
| result.getSpecialChars().addAll(ruleConf.getSpecialChars().stream(). |
| filter(c -> !result.getSpecialChars().contains(c)).collect(Collectors.toList())); |
| } |
| |
| if (!ruleConf.getIllegalChars().isEmpty()) { |
| result.getIllegalChars().addAll(ruleConf.getIllegalChars().stream(). |
| filter(c -> !result.getIllegalChars().contains(c)).collect(Collectors.toList())); |
| } |
| |
| if (ruleConf.getRepeatSame() > result.getRepeatSame()) { |
| result.setRepeatSame(ruleConf.getRepeatSame()); |
| } |
| |
| if (!result.isUsernameAllowed()) { |
| result.setUsernameAllowed(ruleConf.isUsernameAllowed()); |
| } |
| |
| if (!ruleConf.getWordsNotPermitted().isEmpty()) { |
| result.getWordsNotPermitted().addAll(ruleConf.getWordsNotPermitted().stream(). |
| filter(w -> !result.getWordsNotPermitted().contains(w)).collect(Collectors.toList())); |
| } |
| }); |
| |
| if (result.getMinLength() == 0) { |
| result.setMinLength( |
| result.getMaxLength() < MIN_LENGTH_IF_ZERO ? result.getMaxLength() : MIN_LENGTH_IF_ZERO); |
| } |
| if (result.getMinLength() > result.getMaxLength()) { |
| result.setMaxLength(result.getMinLength()); |
| } |
| |
| return result; |
| } |
| |
| protected String generate(final DefaultPasswordRuleConf ruleConf) { |
| List<CharacterRule> characterRules = DefaultPasswordRule.conf2Rules(ruleConf).stream(). |
| filter(CharacterRule.class::isInstance).map(CharacterRule.class::cast). |
| collect(Collectors.toList()); |
| if (characterRules.isEmpty()) { |
| int halfMinLength = ruleConf.getMinLength() / 2; |
| characterRules = List.of( |
| new CharacterRule(EnglishCharacterData.Alphabetical, halfMinLength), |
| new CharacterRule(EnglishCharacterData.Digit, halfMinLength)); |
| } |
| return SecureRandomUtils.passwordGenerator().generatePassword(ruleConf.getMinLength(), characterRules); |
| } |
| } |