You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2016/03/09 12:52:39 UTC
[03/17] syncope git commit: Further refactoring as per SYNCOPE-620
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
new file mode 100644
index 0000000..ee55ea4
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
@@ -0,0 +1,334 @@
+/*
+ * 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 org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
+import org.apache.syncope.common.lib.policy.PasswordRuleConf;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.utils.policy.InvalidPasswordRuleConf;
+import org.apache.syncope.core.provisioning.api.utils.policy.PolicyPattern;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 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 {
+
+ private static final char[] SPECIAL_CHARS = { '!', '£', '%', '&', '(', ')', '?', '#', '$' };
+
+ private static final int VERY_MIN_LENGTH = 0;
+
+ private static final int VERY_MAX_LENGTH = 64;
+
+ private static final int MIN_LENGTH_IF_ZERO = 6;
+
+ @Autowired
+ private UserDAO userDAO;
+
+ @Autowired
+ private RealmDAO realmDAO;
+
+ @Override
+ public String generate(final User user) throws InvalidPasswordRuleConf {
+ List<PasswordRuleConf> ruleConfs = new ArrayList<>();
+
+ for (Realm ancestor : realmDAO.findAncestors(user.getRealm())) {
+ if (ancestor.getPasswordPolicy() != null) {
+ ruleConfs.addAll(ancestor.getPasswordPolicy().getRuleConfs());
+ }
+ }
+
+ for (ExternalResource resource : userDAO.findAllResources(user)) {
+ if (resource.getPasswordPolicy() != null) {
+ ruleConfs.addAll(resource.getPasswordPolicy().getRuleConfs());
+ }
+ }
+
+ return generate(ruleConfs);
+ }
+
+ @Override
+ public String generate(final List<PasswordRuleConf> ruleConfs) throws InvalidPasswordRuleConf {
+ List<DefaultPasswordRuleConf> defaultRuleConfs = new ArrayList<>();
+ for (PasswordRuleConf ruleConf : ruleConfs) {
+ if (ruleConf instanceof DefaultPasswordRuleConf) {
+ defaultRuleConfs.add((DefaultPasswordRuleConf) ruleConf);
+ }
+ }
+
+ DefaultPasswordRuleConf ruleConf = merge(defaultRuleConfs);
+ check(ruleConf);
+ return generate(ruleConf);
+ }
+
+ private DefaultPasswordRuleConf merge(final List<DefaultPasswordRuleConf> defaultRuleConfs) {
+ DefaultPasswordRuleConf result = new DefaultPasswordRuleConf();
+ result.setMinLength(VERY_MIN_LENGTH);
+ result.setMaxLength(VERY_MAX_LENGTH);
+
+ for (DefaultPasswordRuleConf ruleConf : defaultRuleConfs) {
+ if (ruleConf.getMinLength() > result.getMinLength()) {
+ result.setMinLength(ruleConf.getMinLength());
+ }
+
+ if ((ruleConf.getMaxLength() != 0) && ((ruleConf.getMaxLength() < result.getMaxLength()))) {
+ result.setMaxLength(ruleConf.getMaxLength());
+ }
+ result.getPrefixesNotPermitted().addAll(ruleConf.getPrefixesNotPermitted());
+ result.getSuffixesNotPermitted().addAll(ruleConf.getSuffixesNotPermitted());
+
+ if (!result.isNonAlphanumericRequired()) {
+ result.setNonAlphanumericRequired(ruleConf.isNonAlphanumericRequired());
+ }
+
+ if (!result.isAlphanumericRequired()) {
+ result.setAlphanumericRequired(ruleConf.isAlphanumericRequired());
+ }
+ if (!result.isDigitRequired()) {
+ result.setDigitRequired(ruleConf.isDigitRequired());
+ }
+
+ if (!result.isLowercaseRequired()) {
+ result.setLowercaseRequired(ruleConf.isLowercaseRequired());
+ }
+ if (!result.isUppercaseRequired()) {
+ result.setUppercaseRequired(ruleConf.isUppercaseRequired());
+ }
+ if (!result.isMustStartWithDigit()) {
+ result.setMustStartWithDigit(ruleConf.isMustStartWithDigit());
+ }
+ if (!result.isMustntStartWithDigit()) {
+ result.setMustntStartWithDigit(ruleConf.isMustntStartWithDigit());
+ }
+ if (!result.isMustEndWithDigit()) {
+ result.setMustEndWithDigit(ruleConf.isMustEndWithDigit());
+ }
+ if (result.isMustntEndWithDigit()) {
+ result.setMustntEndWithDigit(ruleConf.isMustntEndWithDigit());
+ }
+ if (!result.isMustStartWithAlpha()) {
+ result.setMustStartWithAlpha(ruleConf.isMustStartWithAlpha());
+ }
+ if (!result.isMustntStartWithAlpha()) {
+ result.setMustntStartWithAlpha(ruleConf.isMustntStartWithAlpha());
+ }
+ if (!result.isMustStartWithNonAlpha()) {
+ result.setMustStartWithNonAlpha(ruleConf.isMustStartWithNonAlpha());
+ }
+ if (!result.isMustntStartWithNonAlpha()) {
+ result.setMustntStartWithNonAlpha(ruleConf.isMustntStartWithNonAlpha());
+ }
+ if (!result.isMustEndWithNonAlpha()) {
+ result.setMustEndWithNonAlpha(ruleConf.isMustEndWithNonAlpha());
+ }
+ if (!result.isMustntEndWithNonAlpha()) {
+ result.setMustntEndWithNonAlpha(ruleConf.isMustntEndWithNonAlpha());
+ }
+ if (!result.isMustEndWithAlpha()) {
+ result.setMustEndWithAlpha(ruleConf.isMustEndWithAlpha());
+ }
+ if (!result.isMustntEndWithAlpha()) {
+ result.setMustntEndWithAlpha(ruleConf.isMustntEndWithAlpha());
+ }
+ if (!result.isUsernameAllowed()) {
+ result.setUsernameAllowed(ruleConf.isUsernameAllowed());
+ }
+ }
+
+ if (result.getMinLength() == 0) {
+ result.setMinLength(
+ result.getMaxLength() < MIN_LENGTH_IF_ZERO ? result.getMaxLength() : MIN_LENGTH_IF_ZERO);
+ }
+
+ return result;
+ }
+
+ private void check(final DefaultPasswordRuleConf defaultPasswordRuleConf)
+ throws InvalidPasswordRuleConf {
+
+ if (defaultPasswordRuleConf.isMustEndWithAlpha() && defaultPasswordRuleConf.isMustntEndWithAlpha()) {
+ throw new InvalidPasswordRuleConf(
+ "mustEndWithAlpha and mustntEndWithAlpha are both true");
+ }
+ if (defaultPasswordRuleConf.isMustEndWithAlpha() && defaultPasswordRuleConf.isMustEndWithDigit()) {
+ throw new InvalidPasswordRuleConf(
+ "mustEndWithAlpha and mustEndWithDigit are both true");
+ }
+ if (defaultPasswordRuleConf.isMustEndWithDigit() && defaultPasswordRuleConf.isMustntEndWithDigit()) {
+ throw new InvalidPasswordRuleConf(
+ "mustEndWithDigit and mustntEndWithDigit are both true");
+ }
+ if (defaultPasswordRuleConf.isMustEndWithNonAlpha() && defaultPasswordRuleConf.isMustntEndWithNonAlpha()) {
+ throw new InvalidPasswordRuleConf(
+ "mustEndWithNonAlpha and mustntEndWithNonAlpha are both true");
+ }
+ if (defaultPasswordRuleConf.isMustStartWithAlpha() && defaultPasswordRuleConf.isMustntStartWithAlpha()) {
+ throw new InvalidPasswordRuleConf(
+ "mustStartWithAlpha and mustntStartWithAlpha are both true");
+ }
+ if (defaultPasswordRuleConf.isMustStartWithAlpha() && defaultPasswordRuleConf.isMustStartWithDigit()) {
+ throw new InvalidPasswordRuleConf(
+ "mustStartWithAlpha and mustStartWithDigit are both true");
+ }
+ if (defaultPasswordRuleConf.isMustStartWithDigit() && defaultPasswordRuleConf.isMustntStartWithDigit()) {
+ throw new InvalidPasswordRuleConf(
+ "mustStartWithDigit and mustntStartWithDigit are both true");
+ }
+ if (defaultPasswordRuleConf.isMustStartWithNonAlpha() && defaultPasswordRuleConf.isMustntStartWithNonAlpha()) {
+ throw new InvalidPasswordRuleConf(
+ "mustStartWithNonAlpha and mustntStartWithNonAlpha are both true");
+ }
+ if (defaultPasswordRuleConf.getMinLength() > defaultPasswordRuleConf.getMaxLength()) {
+ throw new InvalidPasswordRuleConf(
+ "Minimun length (" + defaultPasswordRuleConf.getMinLength() + ")"
+ + "is greater than maximum length (" + defaultPasswordRuleConf.getMaxLength() + ")");
+ }
+ }
+
+ private String generate(final DefaultPasswordRuleConf ruleConf) {
+ String[] generatedPassword = new String[ruleConf.getMinLength()];
+
+ for (int i = 0; i < generatedPassword.length; i++) {
+ generatedPassword[i] = StringUtils.EMPTY;
+ }
+
+ checkStartChar(generatedPassword, ruleConf);
+
+ checkEndChar(generatedPassword, ruleConf);
+
+ checkRequired(generatedPassword, ruleConf);
+
+ for (int firstEmptyChar = firstEmptyChar(generatedPassword);
+ firstEmptyChar < generatedPassword.length - 1; firstEmptyChar++) {
+
+ generatedPassword[firstEmptyChar] = SecureRandomUtils.generateRandomLetter();
+ }
+
+ checkPrefixAndSuffix(generatedPassword, ruleConf);
+
+ return StringUtils.join(generatedPassword);
+ }
+
+ private void checkStartChar(final String[] generatedPassword, final DefaultPasswordRuleConf ruleConf) {
+ if (ruleConf.isMustStartWithAlpha()) {
+ generatedPassword[0] = SecureRandomUtils.generateRandomLetter();
+ }
+ if (ruleConf.isMustStartWithNonAlpha() || ruleConf.isMustStartWithDigit()) {
+ generatedPassword[0] = SecureRandomUtils.generateRandomNumber();
+ }
+ if (ruleConf.isMustntStartWithAlpha()) {
+ generatedPassword[0] = SecureRandomUtils.generateRandomNumber();
+ }
+ if (ruleConf.isMustntStartWithDigit()) {
+ generatedPassword[0] = SecureRandomUtils.generateRandomLetter();
+ }
+ if (ruleConf.isMustntStartWithNonAlpha()) {
+ generatedPassword[0] = SecureRandomUtils.generateRandomLetter();
+ }
+
+ if (StringUtils.EMPTY.equals(generatedPassword[0])) {
+ generatedPassword[0] = SecureRandomUtils.generateRandomLetter();
+ }
+ }
+
+ private void checkEndChar(final String[] generatedPassword, final DefaultPasswordRuleConf ruleConf) {
+ if (ruleConf.isMustEndWithAlpha()) {
+ generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomLetter();
+ }
+ if (ruleConf.isMustEndWithNonAlpha() || ruleConf.isMustEndWithDigit()) {
+ generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomNumber();
+ }
+
+ if (ruleConf.isMustntEndWithAlpha()) {
+ generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomNumber();
+ }
+ if (ruleConf.isMustntEndWithDigit()) {
+ generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomLetter();
+ }
+ if (ruleConf.isMustntEndWithNonAlpha()) {
+ generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomLetter();
+ }
+
+ if (StringUtils.EMPTY.equals(generatedPassword[ruleConf.getMinLength() - 1])) {
+ generatedPassword[ruleConf.getMinLength() - 1] = SecureRandomUtils.generateRandomLetter();
+ }
+ }
+
+ private int firstEmptyChar(final String[] generatedPStrings) {
+ int index = 0;
+ while (!generatedPStrings[index].isEmpty()) {
+ index++;
+ }
+ return index;
+ }
+
+ private void checkRequired(final String[] generatedPassword, final DefaultPasswordRuleConf ruleConf) {
+ if (ruleConf.isDigitRequired()
+ && !PolicyPattern.DIGIT.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+ generatedPassword[firstEmptyChar(generatedPassword)] = SecureRandomUtils.generateRandomNumber();
+ }
+
+ if (ruleConf.isUppercaseRequired()
+ && !PolicyPattern.ALPHA_UPPERCASE.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+ generatedPassword[firstEmptyChar(generatedPassword)] =
+ SecureRandomUtils.generateRandomLetter().toUpperCase();
+ }
+
+ if (ruleConf.isLowercaseRequired()
+ && !PolicyPattern.ALPHA_LOWERCASE.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+ generatedPassword[firstEmptyChar(generatedPassword)] =
+ SecureRandomUtils.generateRandomLetter().toLowerCase();
+ }
+
+ if (ruleConf.isNonAlphanumericRequired()
+ && !PolicyPattern.NON_ALPHANUMERIC.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+ generatedPassword[firstEmptyChar(generatedPassword)] =
+ SecureRandomUtils.generateRandomSpecialCharacter(SPECIAL_CHARS);
+ }
+ }
+
+ private void checkPrefixAndSuffix(final String[] generatedPassword, final DefaultPasswordRuleConf ruleConf) {
+ for (String prefix : ruleConf.getPrefixesNotPermitted()) {
+ if (StringUtils.join(generatedPassword).startsWith(prefix)) {
+ checkStartChar(generatedPassword, ruleConf);
+ }
+ }
+
+ for (String suffix : ruleConf.getSuffixesNotPermitted()) {
+ if (StringUtils.join(generatedPassword).endsWith(suffix)) {
+ checkEndChar(generatedPassword, ruleConf);
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/DelegatedAdministrationException.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DelegatedAdministrationException.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DelegatedAdministrationException.java
new file mode 100644
index 0000000..d99c96c
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DelegatedAdministrationException.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.apache.syncope.common.lib.types.AnyTypeKind;
+
+public class DelegatedAdministrationException extends RuntimeException {
+
+ private static final long serialVersionUID = 7540587364235915081L;
+
+ public DelegatedAdministrationException(final AnyTypeKind type, final Long key) {
+ super("Missing entitlement or realm administration for "
+ + (key == null
+ ? "new " + type
+ : type + " " + key));
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/Encryptor.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/Encryptor.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/Encryptor.java
new file mode 100644
index 0000000..a1e8a9e
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/Encryptor.java
@@ -0,0 +1,256 @@
+/*
+ * 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.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.jasypt.commons.CommonUtils;
+import org.jasypt.digest.StandardStringDigester;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.crypto.bcrypt.BCrypt;
+import org.springframework.security.crypto.codec.Base64;
+
+public final class Encryptor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Encryptor.class);
+
+ private static final Map<String, Encryptor> INSTANCES = new ConcurrentHashMap<>();
+
+ private static final String DEFAULT_SECRET_KEY = "1abcdefghilmnopqrstuvz2!";
+
+ /**
+ * Default value for salted {@link StandardStringDigester#setIterations(int)}.
+ */
+ private static final int DEFAULT_SALT_ITERATIONS = 1;
+
+ /**
+ * Default value for {@link StandardStringDigester#setSaltSizeBytes(int)}.
+ */
+ private static final int DEFAULT_SALT_SIZE_BYTES = 8;
+
+ /**
+ * Default value for {@link StandardStringDigester#setInvertPositionOfPlainSaltInEncryptionResults(boolean)}.
+ */
+ private static final boolean DEFAULT_IPOPSIER = true;
+
+ /**
+ * Default value for salted {@link StandardStringDigester#setInvertPositionOfSaltInMessageBeforeDigesting(boolean)}.
+ */
+ private static final boolean DEFAULT_IPOSIMBD = true;
+
+ /**
+ * Default value for salted {@link StandardStringDigester#setUseLenientSaltSizeCheck(boolean)}.
+ */
+ private static final boolean DEFAULT_ULSSC = true;
+
+ private static String SECRET_KEY;
+
+ private static Integer SALT_ITERATIONS;
+
+ private static Integer SALT_SIZE_BYTES;
+
+ private static Boolean IPOPSIER;
+
+ private static Boolean IPOSIMBD;
+
+ private static Boolean ULSSC;
+
+ static {
+ InputStream propStream = null;
+ try {
+ propStream = Encryptor.class.getResourceAsStream("/security.properties");
+ Properties props = new Properties();
+ props.load(propStream);
+
+ SECRET_KEY = props.getProperty("secretKey");
+ SALT_ITERATIONS = Integer.valueOf(props.getProperty("digester.saltIterations"));
+ SALT_SIZE_BYTES = Integer.valueOf(props.getProperty("digester.saltSizeBytes"));
+ IPOPSIER = Boolean.valueOf(props.getProperty("digester.invertPositionOfPlainSaltInEncryptionResults"));
+ IPOSIMBD = Boolean.valueOf(props.getProperty("digester.invertPositionOfSaltInMessageBeforeDigesting"));
+ ULSSC = Boolean.valueOf(props.getProperty("digester.useLenientSaltSizeCheck"));
+ } catch (Exception e) {
+ LOG.error("Could not read security parameters", e);
+ } finally {
+ IOUtils.closeQuietly(propStream);
+ }
+
+ if (SECRET_KEY == null) {
+ SECRET_KEY = DEFAULT_SECRET_KEY;
+ LOG.debug("secretKey not found, reverting to default");
+ }
+ if (SALT_ITERATIONS == null) {
+ SALT_ITERATIONS = DEFAULT_SALT_ITERATIONS;
+ LOG.debug("digester.saltIterations not found, reverting to default");
+ }
+ if (SALT_SIZE_BYTES == null) {
+ SALT_SIZE_BYTES = DEFAULT_SALT_SIZE_BYTES;
+ LOG.debug("digester.saltSizeBytes not found, reverting to default");
+ }
+ if (IPOPSIER == null) {
+ IPOPSIER = DEFAULT_IPOPSIER;
+ LOG.debug("digester.invertPositionOfPlainSaltInEncryptionResults not found, reverting to default");
+ }
+ if (IPOSIMBD == null) {
+ IPOSIMBD = DEFAULT_IPOSIMBD;
+ LOG.debug("digester.invertPositionOfSaltInMessageBeforeDigesting not found, reverting to default");
+ }
+ if (ULSSC == null) {
+ ULSSC = DEFAULT_ULSSC;
+ LOG.debug("digester.useLenientSaltSizeCheck not found, reverting to default");
+ }
+ }
+
+ public static Encryptor getInstance() {
+ return getInstance(SECRET_KEY);
+ }
+
+ public static Encryptor getInstance(final String secretKey) {
+ String actualKey = StringUtils.isBlank(secretKey) ? DEFAULT_SECRET_KEY : secretKey;
+
+ Encryptor instance = INSTANCES.get(actualKey);
+ if (instance == null) {
+ instance = new Encryptor(actualKey);
+ INSTANCES.put(actualKey, instance);
+ }
+
+ return instance;
+ }
+
+ private SecretKeySpec keySpec;
+
+ private Encryptor(final String secretKey) {
+ String actualKey = secretKey;
+ if (actualKey.length() < 16) {
+ StringBuilder actualKeyPadding = new StringBuilder(actualKey);
+ for (int i = 0; i < 16 - actualKey.length(); i++) {
+ actualKeyPadding.append('0');
+ }
+ actualKey = actualKeyPadding.toString();
+ LOG.debug("actualKey too short, adding some random characters");
+ }
+
+ try {
+ keySpec = new SecretKeySpec(ArrayUtils.subarray(
+ actualKey.getBytes(SyncopeConstants.DEFAULT_CHARSET), 0, 16),
+ CipherAlgorithm.AES.getAlgorithm());
+ } catch (Exception e) {
+ LOG.error("Error during key specification", e);
+ }
+ }
+
+ public String encode(final String value, final CipherAlgorithm cipherAlgorithm)
+ throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
+ IllegalBlockSizeException, BadPaddingException {
+
+ String encodedValue = null;
+
+ if (value != null) {
+ if (cipherAlgorithm == null || cipherAlgorithm == CipherAlgorithm.AES) {
+ final byte[] cleartext = value.getBytes(SyncopeConstants.DEFAULT_CHARSET);
+
+ final Cipher cipher = Cipher.getInstance(CipherAlgorithm.AES.getAlgorithm());
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec);
+
+ encodedValue = new String(Base64.encode(cipher.doFinal(cleartext)));
+ } else if (cipherAlgorithm == CipherAlgorithm.BCRYPT) {
+ encodedValue = BCrypt.hashpw(value, BCrypt.gensalt());
+ } else {
+ encodedValue = getDigester(cipherAlgorithm).digest(value);
+ }
+ }
+
+ return encodedValue;
+ }
+
+ public boolean verify(final String value, final CipherAlgorithm cipherAlgorithm, final String encodedValue) {
+ boolean res = false;
+
+ try {
+ if (value != null) {
+ if (cipherAlgorithm == null || cipherAlgorithm == CipherAlgorithm.AES) {
+ res = encode(value, cipherAlgorithm).equals(encodedValue);
+ } else if (cipherAlgorithm == CipherAlgorithm.BCRYPT) {
+ res = BCrypt.checkpw(value, encodedValue);
+ } else {
+ res = getDigester(cipherAlgorithm).matches(value, encodedValue);
+ }
+ }
+ } catch (Exception e) {
+ LOG.error("Could not verify encoded value", e);
+ }
+
+ return res;
+ }
+
+ public String decode(final String encodedValue, final CipherAlgorithm cipherAlgorithm)
+ throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
+ IllegalBlockSizeException, BadPaddingException {
+
+ String value = null;
+
+ if (encodedValue != null && cipherAlgorithm == CipherAlgorithm.AES) {
+ final byte[] encoded = encodedValue.getBytes(SyncopeConstants.DEFAULT_CHARSET);
+
+ final Cipher cipher = Cipher.getInstance(CipherAlgorithm.AES.getAlgorithm());
+ cipher.init(Cipher.DECRYPT_MODE, keySpec);
+
+ value = new String(cipher.doFinal(Base64.decode(encoded)), SyncopeConstants.DEFAULT_CHARSET);
+ }
+
+ return value;
+ }
+
+ private StandardStringDigester getDigester(final CipherAlgorithm cipherAlgorithm) {
+ StandardStringDigester digester = new StandardStringDigester();
+
+ if (cipherAlgorithm.getAlgorithm().startsWith("S-")) {
+ // Salted ...
+ digester.setAlgorithm(cipherAlgorithm.getAlgorithm().replaceFirst("S\\-", ""));
+ digester.setIterations(SALT_ITERATIONS);
+ digester.setSaltSizeBytes(SALT_SIZE_BYTES);
+ digester.setInvertPositionOfPlainSaltInEncryptionResults(IPOPSIER);
+ digester.setInvertPositionOfSaltInMessageBeforeDigesting(IPOSIMBD);
+ digester.setUseLenientSaltSizeCheck(ULSSC);
+ } else {
+ // Not salted ...
+ digester.setAlgorithm(cipherAlgorithm.getAlgorithm());
+ digester.setIterations(1);
+ digester.setSaltSizeBytes(0);
+ }
+
+ digester.setStringOutputType(CommonUtils.STRING_OUTPUT_TYPE_HEXADECIMAL);
+ return digester;
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/MustChangePasswordFilter.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/MustChangePasswordFilter.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/MustChangePasswordFilter.java
new file mode 100644
index 0000000..f9939dd
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/MustChangePasswordFilter.java
@@ -0,0 +1,80 @@
+/*
+ * 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.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
+
+public class MustChangePasswordFilter implements Filter {
+
+ private static final String[] ALLOWED = new String[] {
+ "/users/self", "/users/self/changePassword"
+ };
+
+ @Override
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ // not used
+ }
+
+ @Override
+ public void destroy() {
+ // not used
+ }
+
+ @Override
+ public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
+ throws IOException, ServletException {
+
+ if (request instanceof SecurityContextHolderAwareRequestWrapper) {
+ boolean isMustChangePassword = IterableUtils.matchesAny(
+ SecurityContextHolder.getContext().getAuthentication().getAuthorities(),
+ new Predicate<GrantedAuthority>() {
+
+ @Override
+ public boolean evaluate(final GrantedAuthority authority) {
+ return StandardEntitlement.MUST_CHANGE_PASSWORD.equals(authority.getAuthority());
+ }
+ });
+
+ SecurityContextHolderAwareRequestWrapper wrapper =
+ SecurityContextHolderAwareRequestWrapper.class.cast(request);
+ if (isMustChangePassword && "GET".equalsIgnoreCase(wrapper.getMethod())
+ && !ArrayUtils.contains(ALLOWED, wrapper.getPathInfo())) {
+
+ throw new AccessDeniedException("Please change your password first");
+ }
+ }
+
+ chain.doFilter(request, response);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/PasswordGenerator.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/PasswordGenerator.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/PasswordGenerator.java
new file mode 100644
index 0000000..ad6b56b
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/PasswordGenerator.java
@@ -0,0 +1,32 @@
+/*
+ * 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.List;
+import org.apache.syncope.common.lib.policy.PasswordRuleConf;
+import org.apache.syncope.core.provisioning.api.utils.policy.InvalidPasswordRuleConf;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+
+public interface PasswordGenerator {
+
+ String generate(User user) throws InvalidPasswordRuleConf;
+
+ String generate(List<PasswordRuleConf> ruleConfs) throws InvalidPasswordRuleConf;
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
new file mode 100644
index 0000000..04aad69
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecureRandomUtils.java
@@ -0,0 +1,48 @@
+/*
+ * 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.security.SecureRandom;
+
+import org.apache.commons.lang3.RandomStringUtils;
+
+public final class SecureRandomUtils {
+
+ private static final SecureRandom RANDOM = new SecureRandom();
+
+ public static String generateRandomPassword(final int tokenLength) {
+ return RandomStringUtils.random(tokenLength, 0, 0, true, false, null, RANDOM);
+ }
+
+ public static String generateRandomLetter() {
+ return RandomStringUtils.random(1, 0, 0, true, false, null, RANDOM);
+ }
+
+ public static String generateRandomNumber() {
+ return RandomStringUtils.random(1, 0, 0, false, true, null, RANDOM);
+ }
+
+ public static String generateRandomSpecialCharacter(final char[] characters) {
+ return RandomStringUtils.random(1, 0, 0, false, false, characters, RANDOM);
+ }
+
+ private SecureRandomUtils() {
+ // private constructor for static utility class
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAccessDeniedHandler.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAccessDeniedHandler.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAccessDeniedHandler.java
new file mode 100644
index 0000000..888cae9
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAccessDeniedHandler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandlerImpl;
+
+/**
+ * Render Spring's {@link AccessDeniedException} as other Syncope errors.
+ */
+public class SyncopeAccessDeniedHandler extends AccessDeniedHandlerImpl {
+
+ @Override
+ public void handle(final HttpServletRequest request, final HttpServletResponse response,
+ final AccessDeniedException accessDeniedException) throws IOException, ServletException {
+
+ response.addHeader(RESTHeaders.ERROR_INFO, accessDeniedException.getMessage());
+
+ super.handle(request, response, accessDeniedException);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
new file mode 100644
index 0000000..c427c48
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
@@ -0,0 +1,86 @@
+/*
+ * 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.io.Serializable;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+public class SyncopeAuthenticationDetails implements Serializable {
+
+ private static final long serialVersionUID = -5899959397393502897L;
+
+ private final String remoteAddress;
+
+ private final String sessionId;
+
+ private String domain;
+
+ public SyncopeAuthenticationDetails(final HttpServletRequest request) {
+ this.remoteAddress = request.getRemoteAddr();
+
+ HttpSession session = request.getSession(false);
+ this.sessionId = session == null ? null : session.getId();
+
+ this.domain = request.getHeader(RESTHeaders.DOMAIN);
+ }
+
+ public SyncopeAuthenticationDetails(final String domain) {
+ this.remoteAddress = null;
+ this.sessionId = null;
+ this.domain = domain;
+ }
+
+ public String getRemoteAddress() {
+ return remoteAddress;
+ }
+
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public void setDomain(final String domain) {
+ this.domain = domain;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ @Override
+ public String toString() {
+ return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java
new file mode 100644
index 0000000..0b2b27c
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+import org.springframework.security.authentication.AuthenticationDetailsSource;
+
+public class SyncopeAuthenticationDetailsSource
+ implements AuthenticationDetailsSource<HttpServletRequest, SyncopeAuthenticationDetails> {
+
+ @Override
+ public SyncopeAuthenticationDetails buildDetails(final HttpServletRequest context) {
+ return new SyncopeAuthenticationDetails(context);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationEntryPoint.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationEntryPoint.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationEntryPoint.java
new file mode 100644
index 0000000..7f9ad7b
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationEntryPoint.java
@@ -0,0 +1,43 @@
+/*
+ * 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.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
+
+/**
+ * Render Spring's {@link AuthenticationException} as other Syncope errors.
+ */
+public class SyncopeAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
+
+ @Override
+ public void commence(final HttpServletRequest request, final HttpServletResponse response,
+ final AuthenticationException authException) throws IOException, ServletException {
+
+ response.addHeader(RESTHeaders.ERROR_INFO, authException.getMessage());
+
+ super.commence(request, response, authException);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java
new file mode 100644
index 0000000..078ebc4
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationProvider.java
@@ -0,0 +1,210 @@
+/*
+ * 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 javax.annotation.Resource;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.spring.security.AuthContextUtils.Executable;
+import org.apache.syncope.core.persistence.api.entity.Domain;
+import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+@Configurable
+public class SyncopeAuthenticationProvider implements AuthenticationProvider {
+
+ protected static final Logger LOG = LoggerFactory.getLogger(SyncopeAuthenticationProvider.class);
+
+ @Autowired
+ protected AuthDataAccessor dataAccessor;
+
+ @Autowired
+ protected UserProvisioningManager provisioningManager;
+
+ @Resource(name = "adminUser")
+ protected String adminUser;
+
+ @Resource(name = "anonymousUser")
+ protected String anonymousUser;
+
+ protected String adminPassword;
+
+ protected String adminPasswordAlgorithm;
+
+ protected String anonymousKey;
+
+ protected UserDetailsService userDetailsService;
+
+ protected final Encryptor encryptor = Encryptor.getInstance();
+
+ /**
+ * @param adminPassword the adminPassword to set
+ */
+ public void setAdminPassword(final String adminPassword) {
+ this.adminPassword = adminPassword;
+ }
+
+ /**
+ * @param adminPasswordAlgorithm the adminPasswordAlgorithm to set
+ */
+ public void setAdminPasswordAlgorithm(final String adminPasswordAlgorithm) {
+ this.adminPasswordAlgorithm = adminPasswordAlgorithm;
+ }
+
+ /**
+ * @param anonymousKey the anonymousKey to set
+ */
+ public void setAnonymousKey(final String anonymousKey) {
+ this.anonymousKey = anonymousKey;
+ }
+
+ public void setUserDetailsService(final UserDetailsService syncopeUserDetailsService) {
+ this.userDetailsService = syncopeUserDetailsService;
+ }
+
+ @Override
+ public Authentication authenticate(final Authentication authentication) {
+ String domainKey = SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain();
+ if (StringUtils.isBlank(domainKey)) {
+ domainKey = SyncopeConstants.MASTER_DOMAIN;
+ }
+ SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).setDomain(domainKey);
+
+ Boolean authenticated;
+ if (anonymousUser.equals(authentication.getName())) {
+ authenticated = authentication.getCredentials().toString().equals(anonymousKey);
+ } else if (adminUser.equals(authentication.getName())) {
+ if (SyncopeConstants.MASTER_DOMAIN.equals(domainKey)) {
+ authenticated = encryptor.verify(
+ authentication.getCredentials().toString(),
+ CipherAlgorithm.valueOf(adminPasswordAlgorithm),
+ adminPassword);
+ } else {
+ final String domainToFind = domainKey;
+ authenticated = AuthContextUtils.execWithAuthContext(
+ SyncopeConstants.MASTER_DOMAIN, new Executable<Boolean>() {
+
+ @Override
+ public Boolean exec() {
+ Domain domain = dataAccessor.findDomain(domainToFind);
+
+ return encryptor.verify(
+ authentication.getCredentials().toString(),
+ domain.getAdminCipherAlgorithm(),
+ domain.getAdminPwd());
+ }
+ });
+ }
+ } else {
+ final Pair<Long, Boolean> authResult =
+ AuthContextUtils.execWithAuthContext(domainKey, new Executable<Pair<Long, Boolean>>() {
+
+ @Override
+ public Pair<Long, Boolean> exec() {
+ return dataAccessor.authenticate(authentication);
+ }
+ });
+ authenticated = authResult.getValue();
+ if (authenticated != null && !authenticated) {
+ AuthContextUtils.execWithAuthContext(domainKey, new Executable<Void>() {
+
+ @Override
+ public Void exec() {
+ provisioningManager.internalSuspend(authResult.getKey());
+ return null;
+ }
+ });
+ }
+ }
+
+ final boolean isAuthenticated = authenticated != null && authenticated;
+ UsernamePasswordAuthenticationToken token;
+ if (isAuthenticated) {
+ token = AuthContextUtils.execWithAuthContext(
+ domainKey, new Executable<UsernamePasswordAuthenticationToken>() {
+
+ @Override
+ public UsernamePasswordAuthenticationToken exec() {
+ UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+ authentication.getPrincipal(),
+ null,
+ userDetailsService.loadUserByUsername(authentication.getPrincipal().toString()).
+ getAuthorities());
+ token.setDetails(authentication.getDetails());
+
+ dataAccessor.audit(
+ AuditElements.EventCategoryType.REST,
+ AuditElements.AUTHENTICATION_CATEGORY,
+ null,
+ AuditElements.LOGIN_EVENT,
+ Result.SUCCESS,
+ null,
+ isAuthenticated,
+ authentication,
+ "Successfully authenticated, with entitlements: " + token.getAuthorities());
+ return token;
+ }
+ });
+
+ LOG.debug("User {} successfully authenticated, with entitlements {}",
+ authentication.getPrincipal(), token.getAuthorities());
+ } else {
+ AuthContextUtils.execWithAuthContext(domainKey, new Executable<Void>() {
+
+ @Override
+ public Void exec() {
+ dataAccessor.audit(
+ AuditElements.EventCategoryType.REST,
+ AuditElements.AUTHENTICATION_CATEGORY,
+ null,
+ AuditElements.LOGIN_EVENT,
+ Result.FAILURE,
+ null,
+ isAuthenticated,
+ authentication,
+ "User " + authentication.getPrincipal() + " not authenticated");
+ return null;
+ }
+ });
+
+ LOG.debug("User {} not authenticated", authentication.getPrincipal());
+
+ throw new BadCredentialsException("User " + authentication.getPrincipal() + " not authenticated");
+ }
+
+ return token;
+ }
+
+ @Override
+ public boolean supports(final Class<? extends Object> type) {
+ return type.equals(UsernamePasswordAuthenticationToken.class);
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java
new file mode 100644
index 0000000..e23902b
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeGrantedAuthority.java
@@ -0,0 +1,90 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.collections4.Closure;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.SetUtils;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
+import org.springframework.security.core.GrantedAuthority;
+
+public class SyncopeGrantedAuthority implements GrantedAuthority {
+
+ private static final long serialVersionUID = -5647624636011919735L;
+
+ private final String entitlement;
+
+ private final Set<String> realms = SetUtils.orderedSet(new HashSet<String>());
+
+ public SyncopeGrantedAuthority(final String entitlement) {
+ this.entitlement = entitlement;
+ }
+
+ public SyncopeGrantedAuthority(final String entitlement, final String realm) {
+ this.entitlement = entitlement;
+ this.realms.add(realm);
+ }
+
+ public boolean addRealm(final String newRealm) {
+ return RealmUtils.normalizingAddTo(realms, newRealm);
+ }
+
+ public void addRealms(final Collection<String> newRealms) {
+ IterableUtils.forEach(newRealms, new Closure<String>() {
+
+ @Override
+ public void execute(final String newRealm) {
+ addRealm(newRealm);
+ }
+ });
+ }
+
+ public Set<String> getRealms() {
+ return Collections.unmodifiableSet(realms);
+ }
+
+ @Override
+ public String getAuthority() {
+ return entitlement;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ @Override
+ public String toString() {
+ return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeUserDetailsService.java
----------------------------------------------------------------------
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeUserDetailsService.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeUserDetailsService.java
new file mode 100644
index 0000000..544fc99
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeUserDetailsService.java
@@ -0,0 +1,37 @@
+/*
+ * 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 org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+@Configurable
+public class SyncopeUserDetailsService implements UserDetailsService {
+
+ @Autowired
+ protected AuthDataAccessor dataAccessor;
+
+ @Override
+ public UserDetails loadUserByUsername(final String username) {
+ return new User(username, "<PASSWORD_PLACEHOLDER>", dataAccessor.load(username));
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/resources/security.properties
----------------------------------------------------------------------
diff --git a/core/spring/src/main/resources/security.properties b/core/spring/src/main/resources/security.properties
new file mode 100644
index 0000000..6bb5210
--- /dev/null
+++ b/core/spring/src/main/resources/security.properties
@@ -0,0 +1,32 @@
+# 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.
+adminUser=admin
+adminPassword=5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
+adminPasswordAlgorithm=SHA1
+
+anonymousUser=${anonymousUser}
+anonymousKey=${anonymousKey}
+
+secretKey=${secretKey}
+# default for LDAP / RFC2307 SSHA
+digester.saltIterations=1
+digester.saltSizeBytes=8
+digester.invertPositionOfPlainSaltInEncryptionResults=true
+digester.invertPositionOfSaltInMessageBeforeDigesting=true
+digester.useLenientSaltSizeCheck=true
+
+passwordGenerator=org.apache.syncope.core.spring.security.DefaultPasswordGenerator
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/main/resources/securityContext.xml
----------------------------------------------------------------------
diff --git a/core/spring/src/main/resources/securityContext.xml b/core/spring/src/main/resources/securityContext.xml
new file mode 100644
index 0000000..0e2072f
--- /dev/null
+++ b/core/spring/src/main/resources/securityContext.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:security="http://www.springframework.org/schema/security"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/security
+ http://www.springframework.org/schema/security/spring-security.xsd">
+
+ <bean id="adminUser" class="java.lang.String">
+ <constructor-arg value="${adminUser}"/>
+ </bean>
+ <bean id="anonymousUser" class="java.lang.String">
+ <constructor-arg value="${anonymousUser}"/>
+ </bean>
+
+ <bean class="${passwordGenerator}"/>
+ <bean class="org.apache.syncope.core.spring.DefaultRolesPrefixPostProcessor"/>
+
+ <security:global-method-security pre-post-annotations="enabled"/>
+
+ <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
+ <security:filter-chain-map request-matcher="ant">
+ <security:filter-chain pattern="/**" filters="securityContextPersistenceFilter"/>
+ </security:filter-chain-map>
+ </bean>
+
+ <bean id="securityContextRepository" class='org.springframework.security.web.context.NullSecurityContextRepository'/>
+
+ <bean id="securityContextPersistenceFilter"
+ class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
+ <constructor-arg ref="securityContextRepository"/>
+ </bean>
+
+ <bean id="syncopeAuthenticationDetailsSource"
+ class="org.apache.syncope.core.spring.security.SyncopeAuthenticationDetailsSource"/>
+
+ <bean id="mustChangePasswordFilter" class="org.apache.syncope.core.spring.security.MustChangePasswordFilter"/>
+
+ <bean id="syncopeAuthenticationEntryPoint"
+ class="org.apache.syncope.core.spring.security.SyncopeAuthenticationEntryPoint">
+ <property name="realmName" value="Apache Syncope authentication"/>
+ </bean>
+
+ <bean id="syncopeAccessDeniedHandler" class="org.apache.syncope.core.spring.security.SyncopeAccessDeniedHandler"/>
+
+ <security:http security-context-repository-ref="securityContextRepository"
+ use-expressions="false" disable-url-rewriting="false">
+
+ <security:http-basic entry-point-ref="syncopeAuthenticationEntryPoint"
+ authentication-details-source-ref="syncopeAuthenticationDetailsSource"/>
+ <security:anonymous username="${anonymousUser}"/>
+ <security:intercept-url pattern="/**"/>
+
+ <security:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="mustChangePasswordFilter"/>
+
+ <security:access-denied-handler ref="syncopeAccessDeniedHandler"/>
+
+ <security:headers disabled="true"/>
+ <security:csrf disabled="true"/>
+ </security:http>
+
+ <bean class="org.apache.syncope.core.spring.security.AuthDataAccessor"/>
+
+ <bean id="syncopeUserDetailsService" class="org.apache.syncope.core.spring.security.SyncopeUserDetailsService"/>
+
+ <bean id="syncopeAuthenticationProvider"
+ class="org.apache.syncope.core.spring.security.SyncopeAuthenticationProvider">
+ <property name="adminPassword" value="${adminPassword}"/>
+ <property name="adminPasswordAlgorithm" value="${adminPasswordAlgorithm}"/>
+ <property name="anonymousKey" value="${anonymousKey}"/>
+ <property name="userDetailsService" ref="syncopeUserDetailsService"/>
+ </bean>
+
+ <security:authentication-manager>
+ <security:authentication-provider ref="syncopeAuthenticationProvider"/>
+ </security:authentication-manager>
+</beans>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/test/java/org/apache/syncope/core/spring/security/EncryptorTest.java
----------------------------------------------------------------------
diff --git a/core/spring/src/test/java/org/apache/syncope/core/spring/security/EncryptorTest.java b/core/spring/src/test/java/org/apache/syncope/core/spring/security/EncryptorTest.java
new file mode 100644
index 0000000..4bfa0fa
--- /dev/null
+++ b/core/spring/src/test/java/org/apache/syncope/core/spring/security/EncryptorTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 org.apache.syncope.core.spring.security.Encryptor;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.junit.Test;
+
+/**
+ * Test class to test all encryption algorithms.
+ */
+public class EncryptorTest {
+
+ private final String password = "password";
+
+ private final Encryptor encryptor = Encryptor.getInstance();
+
+ /**
+ * Verify all algorithms.
+ */
+ @Test
+ public void testEncoder() throws Exception {
+ for (CipherAlgorithm cipherAlgorithm : CipherAlgorithm.values()) {
+ final String encPassword = encryptor.encode(password, cipherAlgorithm);
+
+ assertNotNull(encPassword);
+ assertTrue(encryptor.verify(password, cipherAlgorithm, encPassword));
+ assertFalse(encryptor.verify("pass", cipherAlgorithm, encPassword));
+
+ // check that same password encoded with BCRYPT or Salted versions results in different digest
+ if (cipherAlgorithm.equals(CipherAlgorithm.BCRYPT) || cipherAlgorithm.getAlgorithm().startsWith("S-")) {
+ final String encSamePassword = encryptor.encode(password, cipherAlgorithm);
+ assertNotNull(encSamePassword);
+ assertFalse(encSamePassword.equals(encPassword));
+ assertTrue(encryptor.verify(password, cipherAlgorithm, encSamePassword));
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/spring/src/test/java/org/apache/syncope/core/spring/security/PasswordGeneratorTest.java
----------------------------------------------------------------------
diff --git a/core/spring/src/test/java/org/apache/syncope/core/spring/security/PasswordGeneratorTest.java b/core/spring/src/test/java/org/apache/syncope/core/spring/security/PasswordGeneratorTest.java
new file mode 100644
index 0000000..02c0ba4
--- /dev/null
+++ b/core/spring/src/test/java/org/apache/syncope/core/spring/security/PasswordGeneratorTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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 org.apache.syncope.core.spring.security.DefaultPasswordGenerator;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
+import org.apache.syncope.common.lib.policy.PasswordRuleConf;
+import org.apache.syncope.core.provisioning.api.utils.policy.InvalidPasswordRuleConf;
+import org.apache.syncope.core.provisioning.api.utils.policy.PolicyPattern;
+import org.junit.Test;
+
+public class PasswordGeneratorTest {
+
+ private final DefaultPasswordGenerator passwordGenerator = new DefaultPasswordGenerator();
+
+ private DefaultPasswordRuleConf createBaseDefaultPasswordRuleConf() {
+ DefaultPasswordRuleConf baseDefaultPasswordRuleConf = new DefaultPasswordRuleConf();
+ baseDefaultPasswordRuleConf.setAlphanumericRequired(false);
+ baseDefaultPasswordRuleConf.setDigitRequired(false);
+ baseDefaultPasswordRuleConf.setLowercaseRequired(false);
+ baseDefaultPasswordRuleConf.setMaxLength(1000);
+ baseDefaultPasswordRuleConf.setMinLength(8);
+ baseDefaultPasswordRuleConf.setMustEndWithAlpha(false);
+ baseDefaultPasswordRuleConf.setMustEndWithDigit(false);
+ baseDefaultPasswordRuleConf.setMustEndWithNonAlpha(false);
+ baseDefaultPasswordRuleConf.setMustStartWithAlpha(false);
+ baseDefaultPasswordRuleConf.setMustStartWithDigit(false);
+ baseDefaultPasswordRuleConf.setMustStartWithNonAlpha(false);
+ baseDefaultPasswordRuleConf.setMustntEndWithAlpha(false);
+ baseDefaultPasswordRuleConf.setMustntEndWithDigit(false);
+ baseDefaultPasswordRuleConf.setMustntEndWithNonAlpha(false);
+ baseDefaultPasswordRuleConf.setMustntStartWithAlpha(false);
+ baseDefaultPasswordRuleConf.setMustntStartWithDigit(false);
+ baseDefaultPasswordRuleConf.setMustntStartWithNonAlpha(false);
+ baseDefaultPasswordRuleConf.setNonAlphanumericRequired(false);
+ baseDefaultPasswordRuleConf.setUppercaseRequired(false);
+ return baseDefaultPasswordRuleConf;
+ }
+
+ @Test
+ public void startEndWithDigit() throws InvalidPasswordRuleConf {
+ DefaultPasswordRuleConf pwdRuleConf = createBaseDefaultPasswordRuleConf();
+ pwdRuleConf.setMustStartWithDigit(true);
+
+ DefaultPasswordRuleConf pwdRuleConf2 = createBaseDefaultPasswordRuleConf();
+ pwdRuleConf2.setMustEndWithDigit(true);
+
+ List<PasswordRuleConf> ruleConfs = new ArrayList<>();
+ ruleConfs.add(pwdRuleConf);
+ ruleConfs.add(pwdRuleConf2);
+ String generatedPassword = passwordGenerator.generate(ruleConfs);
+ assertTrue(Character.isDigit(generatedPassword.charAt(0)));
+ assertTrue(Character.isDigit(generatedPassword.charAt(generatedPassword.length() - 1)));
+ }
+
+ @Test
+ public void startWithDigitAndWithAlpha() throws InvalidPasswordRuleConf {
+ DefaultPasswordRuleConf pwdRuleConf = createBaseDefaultPasswordRuleConf();
+ pwdRuleConf.setMustStartWithDigit(true);
+
+ DefaultPasswordRuleConf pwdRuleConf2 = createBaseDefaultPasswordRuleConf();
+ pwdRuleConf2.setMustEndWithAlpha(true);
+
+ List<PasswordRuleConf> pwdRuleConfs = new ArrayList<>();
+ pwdRuleConfs.add(pwdRuleConf);
+ pwdRuleConfs.add(pwdRuleConf2);
+ String generatedPassword = passwordGenerator.generate(pwdRuleConfs);
+ assertTrue(Character.isDigit(generatedPassword.charAt(0)));
+ assertTrue(Character.isLetter(generatedPassword.charAt(generatedPassword.length() - 1)));
+ }
+
+ @Test
+ public void passwordWithNonAlpha() throws InvalidPasswordRuleConf {
+ DefaultPasswordRuleConf pwdRuleConf = createBaseDefaultPasswordRuleConf();
+ pwdRuleConf.setNonAlphanumericRequired(true);
+
+ DefaultPasswordRuleConf pwdRuleConf2 = createBaseDefaultPasswordRuleConf();
+ pwdRuleConf2.setMustEndWithAlpha(true);
+
+ List<PasswordRuleConf> pwdRuleConfs = new ArrayList<>();
+ pwdRuleConfs.add(pwdRuleConf);
+ pwdRuleConfs.add(pwdRuleConf2);
+ String generatedPassword = passwordGenerator.generate(pwdRuleConfs);
+ assertTrue(PolicyPattern.NON_ALPHANUMERIC.matcher(generatedPassword).matches());
+ assertTrue(Character.isLetter(generatedPassword.charAt(generatedPassword.length() - 1)));
+ }
+
+ @Test(expected = InvalidPasswordRuleConf.class)
+ public void incopatiblePolicies() throws InvalidPasswordRuleConf {
+ DefaultPasswordRuleConf pwdRuleConf = createBaseDefaultPasswordRuleConf();
+ pwdRuleConf.setMinLength(12);
+
+ DefaultPasswordRuleConf pwdRuleConf2 = createBaseDefaultPasswordRuleConf();
+ pwdRuleConf.setMaxLength(10);
+
+ List<PasswordRuleConf> pwdRuleConfs = new ArrayList<>();
+ pwdRuleConfs.add(pwdRuleConf);
+ pwdRuleConfs.add(pwdRuleConf2);
+ passwordGenerator.generate(pwdRuleConfs);
+ }
+
+ @Test
+ public void issueSYNCOPE678() {
+ String password = null;
+ try {
+ password = passwordGenerator.generate(Collections.<PasswordRuleConf>emptyList());
+ } catch (InvalidPasswordRuleConf e) {
+ fail(e.getMessage());
+ }
+ assertNotNull(password);
+
+ DefaultPasswordRuleConf ppSpec = createBaseDefaultPasswordRuleConf();
+ ppSpec.setMinLength(0);
+ password = null;
+ try {
+ password = passwordGenerator.generate(Collections.<PasswordRuleConf>singletonList(ppSpec));
+ } catch (InvalidPasswordRuleConf e) {
+ fail(e.getMessage());
+ }
+ assertNotNull(password);
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/workflow-activiti/pom.xml
----------------------------------------------------------------------
diff --git a/core/workflow-activiti/pom.xml b/core/workflow-activiti/pom.xml
index 07395ef..7a26ca1 100644
--- a/core/workflow-activiti/pom.xml
+++ b/core/workflow-activiti/pom.xml
@@ -68,7 +68,7 @@ under the License.
</dependency>
<dependency>
<groupId>org.apache.syncope.core</groupId>
- <artifactId>syncope-core-misc</artifactId>
+ <artifactId>syncope-core-spring</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiDefinitionLoader.java
----------------------------------------------------------------------
diff --git a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiDefinitionLoader.java b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiDefinitionLoader.java
index 494a03e..e91f595 100644
--- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiDefinitionLoader.java
+++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiDefinitionLoader.java
@@ -32,7 +32,7 @@ import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.apache.commons.io.IOUtils;
-import org.apache.syncope.core.misc.spring.ResourceWithFallbackLoader;
+import org.apache.syncope.core.spring.ResourceWithFallbackLoader;
import org.apache.syncope.core.persistence.api.SyncopeLoader;
import org.apache.syncope.core.workflow.activiti.spring.DomainProcessEngine;
import org.slf4j.Logger;
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
----------------------------------------------------------------------
diff --git a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
index 4436c52..a8a65b6 100644
--- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
+++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
@@ -63,8 +63,8 @@ import org.apache.syncope.common.lib.to.WorkflowFormTO;
import org.apache.syncope.common.lib.types.PropagationByResource;
import org.apache.syncope.common.lib.types.ResourceOperation;
import org.apache.syncope.common.lib.types.WorkflowFormPropertyType;
-import org.apache.syncope.core.misc.security.AuthContextUtils;
-import org.apache.syncope.core.misc.spring.BeanUtils;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.core.spring.BeanUtils;
import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
import org.apache.syncope.core.persistence.api.entity.user.User;
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/spring/DomainProcessEngine.java
----------------------------------------------------------------------
diff --git a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/spring/DomainProcessEngine.java b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/spring/DomainProcessEngine.java
index f851e08..72da026 100644
--- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/spring/DomainProcessEngine.java
+++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/spring/DomainProcessEngine.java
@@ -32,7 +32,7 @@ import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.ProcessEngineImpl;
-import org.apache.syncope.core.misc.security.AuthContextUtils;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
/**
* {@link ProcessEngine} delegating actual method invocation to the inner map of {@link ProcessEngine} instances,
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/workflow-activiti/src/main/resources/workflowActivitiContext.xml
----------------------------------------------------------------------
diff --git a/core/workflow-activiti/src/main/resources/workflowActivitiContext.xml b/core/workflow-activiti/src/main/resources/workflowActivitiContext.xml
index 8070ac2..78c625e 100644
--- a/core/workflow-activiti/src/main/resources/workflowActivitiContext.xml
+++ b/core/workflow-activiti/src/main/resources/workflowActivitiContext.xml
@@ -25,7 +25,7 @@ under the License.
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
- <bean id="userWorkflowDef" class="org.apache.syncope.core.misc.spring.ResourceWithFallbackLoader">
+ <bean id="userWorkflowDef" class="org.apache.syncope.core.spring.ResourceWithFallbackLoader">
<property name="primary" value="file:${wf.directory}/userWorkflow.bpmn20.xml"/>
<property name="fallback" value="classpath:userWorkflow.bpmn20.xml"/>
</bean>
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/deb/core/pom.xml
----------------------------------------------------------------------
diff --git a/deb/core/pom.xml b/deb/core/pom.xml
index 65e5031..b26cc9c 100644
--- a/deb/core/pom.xml
+++ b/deb/core/pom.xml
@@ -156,7 +156,7 @@ under the License.
<filtering>true</filtering>
</resource>
<resource>
- <directory>${basedir}/../../core/misc/src/main/resources</directory>
+ <directory>${basedir}/../../core/spring/src/main/resources</directory>
<includes>
<include>security.properties</include>
</includes>
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/ext/camel/logic/src/main/java/org/apache/syncope/core/logic/init/CamelRouteLoader.java
----------------------------------------------------------------------
diff --git a/ext/camel/logic/src/main/java/org/apache/syncope/core/logic/init/CamelRouteLoader.java b/ext/camel/logic/src/main/java/org/apache/syncope/core/logic/init/CamelRouteLoader.java
index e3401ca..3c72ed2 100644
--- a/ext/camel/logic/src/main/java/org/apache/syncope/core/logic/init/CamelRouteLoader.java
+++ b/ext/camel/logic/src/main/java/org/apache/syncope/core/logic/init/CamelRouteLoader.java
@@ -32,8 +32,8 @@ import javax.xml.transform.stream.StreamResult;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.CamelEntitlement;
-import org.apache.syncope.core.misc.EntitlementsHolder;
-import org.apache.syncope.core.misc.spring.ResourceWithFallbackLoader;
+import org.apache.syncope.core.provisioning.api.EntitlementsHolder;
+import org.apache.syncope.core.spring.ResourceWithFallbackLoader;
import org.apache.syncope.core.persistence.api.DomainsHolder;
import org.apache.syncope.core.persistence.api.SyncopeLoader;
import org.apache.syncope.core.persistence.api.entity.CamelRoute;
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
----------------------------------------------------------------------
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
index 1a918a5..2c1fe6d 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
@@ -36,7 +36,7 @@ import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.PropagationByResource;
import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
import org.apache.syncope.core.provisioning.api.WorkflowResult;
-import org.apache.syncope.core.provisioning.api.sync.ProvisioningReport;
+import org.apache.syncope.core.provisioning.api.syncpull.ProvisioningReport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/SyncopeCamelContext.java
----------------------------------------------------------------------
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/SyncopeCamelContext.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/SyncopeCamelContext.java
index 04b3952..d86e42b 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/SyncopeCamelContext.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/SyncopeCamelContext.java
@@ -29,7 +29,7 @@ import org.apache.camel.model.Constants;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.spring.SpringCamelContext;
import org.apache.commons.io.IOUtils;
-import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.apache.syncope.core.persistence.api.dao.CamelRouteDAO;
import org.apache.syncope.core.persistence.api.entity.CamelRoute;
import org.slf4j.Logger;