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;