blob: daa33514ef76dd112743f6b8240c7ab6a8c8382c [file] [log] [blame]
/*
* 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);
}
}