syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ilgro...@apache.org
Subject [45/52] [abbrv] [partial] syncope git commit: [SYNCOPE-620] Unit tests all in
Date Mon, 12 Jan 2015 16:32:24 GMT
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/JexlUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/JexlUtil.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/JexlUtil.java
new file mode 100644
index 0000000..cb25033
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/JexlUtil.java
@@ -0,0 +1,289 @@
+/*
+ * 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.server.misc.jexl;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.jexl2.Expression;
+import org.apache.commons.jexl2.JexlContext;
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.JexlException;
+import org.apache.commons.jexl2.MapContext;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.server.persistence.api.entity.Attributable;
+import org.apache.syncope.server.persistence.api.entity.DerAttr;
+import org.apache.syncope.server.persistence.api.entity.PlainAttr;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.misc.DataFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * JEXL <a href="http://commons.apache.org/jexl/reference/index.html">reference</a> is available.
+ */
+public final class JexlUtil {
+
+    /**
+     * Logger.
+     *
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(JexlUtil.class);
+
+    private static final String[] IGNORE_FIELDS = { "password", "clearPassword", "serialVersionUID", "class" };
+
+    private static JexlEngine jexlEngine;
+
+    private static JexlEngine getEngine() {
+        synchronized (LOG) {
+            if (jexlEngine == null) {
+                jexlEngine = new JexlEngine(new ClassFreeUberspectImpl(null), null, null, null);
+                jexlEngine.setClassLoader(new EmptyClassLoader());
+                jexlEngine.setCache(512);
+                jexlEngine.setLenient(true);
+                jexlEngine.setSilent(false);
+            }
+        }
+
+        return jexlEngine;
+    }
+
+    public static boolean isExpressionValid(final String expression) {
+        boolean result;
+        try {
+            getEngine().createExpression(expression);
+            result = true;
+        } catch (JexlException e) {
+            LOG.error("Invalid jexl expression: " + expression, e);
+            result = false;
+        }
+
+        return result;
+    }
+
+    public static String evaluate(final String expression, final JexlContext jexlContext) {
+        String result = StringUtils.EMPTY;
+
+        if (StringUtils.isNotBlank(expression) && jexlContext != null) {
+            try {
+                Expression jexlExpression = getEngine().createExpression(expression);
+                Object evaluated = jexlExpression.evaluate(jexlContext);
+                if (evaluated != null) {
+                    result = evaluated.toString();
+                }
+            } catch (Exception e) {
+                LOG.error("Error while evaluating JEXL expression: " + expression, e);
+            }
+        } else {
+            LOG.debug("Expression not provided or invalid context");
+        }
+
+        return result;
+    }
+
+    public static JexlContext addFieldsToContext(final Object object, final JexlContext jexlContext) {
+        JexlContext context = jexlContext == null ? new MapContext() : jexlContext;
+
+        try {
+            for (PropertyDescriptor desc : Introspector.getBeanInfo(object.getClass()).getPropertyDescriptors()) {
+                final Class<?> type = desc.getPropertyType();
+                final String fieldName = desc.getName();
+
+                if ((!fieldName.startsWith("pc"))
+                        && (!ArrayUtils.contains(IGNORE_FIELDS, fieldName))
+                        && (!Iterable.class.isAssignableFrom(type))
+                        && (!type.isArray())) {
+                    try {
+                        final Method getter = desc.getReadMethod();
+
+                        final Object fieldValue;
+
+                        if (getter == null) {
+                            final Field field = object.getClass().getDeclaredField(fieldName);
+                            field.setAccessible(true);
+                            fieldValue = field.get(object);
+                        } else {
+                            fieldValue = getter.invoke(object);
+                        }
+
+                        context.set(fieldName, fieldValue == null
+                                ? StringUtils.EMPTY
+                                : (type.equals(Date.class)
+                                        ? DataFormat.format((Date) fieldValue, false)
+                                        : fieldValue));
+
+                        LOG.debug("Add field {} with value {}", fieldName, fieldValue);
+
+                    } catch (Exception iae) {
+                        LOG.error("Reading '{}' value error", fieldName, iae);
+                    }
+                }
+            }
+        } catch (IntrospectionException ie) {
+            LOG.error("Reading class attributes error", ie);
+        }
+
+        return context;
+    }
+
+    public static JexlContext addAttrsToContext(final Collection<? extends PlainAttr> attrs,
+            final JexlContext jexlContext) {
+
+        JexlContext context = jexlContext == null
+                ? new MapContext()
+                : jexlContext;
+
+        for (PlainAttr attr : attrs) {
+            if (attr.getSchema() != null) {
+                List<String> attrValues = attr.getValuesAsStrings();
+                String expressionValue = attrValues.isEmpty()
+                        ? StringUtils.EMPTY
+                        : attrValues.get(0);
+
+                LOG.debug("Add attribute {} with value {}", attr.getSchema().getKey(), expressionValue);
+
+                context.set(attr.getSchema().getKey(), expressionValue);
+            }
+        }
+
+        return context;
+    }
+
+    public static JexlContext addDerAttrsToContext(final Collection<? extends DerAttr> derAttrs,
+            final Collection<? extends PlainAttr> attrs, final JexlContext jexlContext) {
+
+        JexlContext context = jexlContext == null
+                ? new MapContext()
+                : jexlContext;
+
+        for (DerAttr derAttr : derAttrs) {
+            if (derAttr.getSchema() != null) {
+                String expressionValue = derAttr.getValue(attrs);
+                if (expressionValue == null) {
+                    expressionValue = StringUtils.EMPTY;
+                }
+
+                LOG.debug("Add derived attribute {} with value {}", derAttr.getSchema().getKey(), expressionValue);
+
+                context.set(derAttr.getSchema().getKey(), expressionValue);
+            }
+        }
+
+        return context;
+    }
+
+    public static JexlContext addVirAttrsToContext(final Collection<? extends VirAttr> virAttrs,
+            final JexlContext jexlContext) {
+
+        JexlContext context = jexlContext == null
+                ? new MapContext()
+                : jexlContext;
+
+        for (VirAttr virAttr : virAttrs) {
+            if (virAttr.getSchema() != null) {
+                List<String> attrValues = virAttr.getValues();
+                String expressionValue = attrValues.isEmpty()
+                        ? StringUtils.EMPTY
+                        : attrValues.get(0);
+
+                LOG.debug("Add virtual attribute {} with value {}", virAttr.getSchema().getKey(), expressionValue);
+
+                context.set(virAttr.getSchema().getKey(), expressionValue);
+            }
+        }
+
+        return context;
+    }
+
+    public static boolean evaluateMandatoryCondition(
+            final String mandatoryCondition, final Attributable<?, ?, ?> attributable) {
+
+        JexlContext jexlContext = new MapContext();
+        addAttrsToContext(attributable.getPlainAttrs(), jexlContext);
+        addDerAttrsToContext(attributable.getDerAttrs(), attributable.getPlainAttrs(), jexlContext);
+        addVirAttrsToContext(attributable.getVirAttrs(), jexlContext);
+
+        return Boolean.parseBoolean(evaluate(mandatoryCondition, jexlContext));
+    }
+
+    public static String evaluate(final String expression,
+            final Attributable<?, ?, ?> attributable, final Collection<? extends PlainAttr> attributes) {
+
+        final JexlContext jexlContext = new MapContext();
+        JexlUtil.addAttrsToContext(attributes, jexlContext);
+        JexlUtil.addFieldsToContext(attributable, jexlContext);
+
+        // Evaluate expression using the context prepared before
+        return evaluate(expression, jexlContext);
+    }
+
+    public static String evaluate(final String expression, final AbstractAttributableTO attributableTO) {
+        final JexlContext context = new MapContext();
+
+        addFieldsToContext(attributableTO, context);
+
+        for (AttrTO plainAttr : attributableTO.getPlainAttrs()) {
+            List<String> values = plainAttr.getValues();
+            String expressionValue = values.isEmpty()
+                    ? StringUtils.EMPTY
+                    : values.get(0);
+
+            LOG.debug("Add plain attribute {} with value {}", plainAttr.getSchema(), expressionValue);
+
+            context.set(plainAttr.getSchema(), expressionValue);
+        }
+        for (AttrTO derAttr : attributableTO.getDerAttrs()) {
+            List<String> values = derAttr.getValues();
+            String expressionValue = values.isEmpty()
+                    ? StringUtils.EMPTY
+                    : values.get(0);
+
+            LOG.debug("Add derived attribute {} with value {}", derAttr.getSchema(), expressionValue);
+
+            context.set(derAttr.getSchema(), expressionValue);
+        }
+        for (AttrTO virAttr : attributableTO.getVirAttrs()) {
+            List<String> values = virAttr.getValues();
+            String expressionValue = values.isEmpty()
+                    ? StringUtils.EMPTY
+                    : values.get(0);
+
+            LOG.debug("Add virtual attribute {} with value {}", virAttr.getSchema(), expressionValue);
+
+            context.set(virAttr.getSchema(), expressionValue);
+        }
+
+        // Evaluate expression using the context prepared before
+        return evaluate(expression, context);
+    }
+
+    /**
+     * Private default constructor, for static-only classes.
+     */
+    private JexlUtil() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/AccountPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/AccountPolicyEnforcer.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/AccountPolicyEnforcer.java
new file mode 100644
index 0000000..f850b98
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/AccountPolicyEnforcer.java
@@ -0,0 +1,101 @@
+/*
+ * 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.server.misc.policy;
+
+import java.util.regex.Pattern;
+import org.apache.syncope.common.lib.types.AccountPolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AccountPolicyEnforcer implements PolicyEnforcer<AccountPolicySpec, User> {
+
+    private static final Pattern DEFAULT_PATTERN = Pattern.compile("[a-zA-Z0-9-_@. ]+");
+
+    @Autowired(required = false)
+    private UserSuspender userSuspender;
+
+    @Override
+    public void enforce(final AccountPolicySpec policy, final PolicyType type, final User user)
+            throws AccountPolicyException, PolicyEnforceException {
+
+        if (user.getUsername() == null) {
+            throw new PolicyEnforceException("Invalid account");
+        }
+
+        if (policy == null) {
+            throw new PolicyEnforceException("Invalid policy");
+        }
+
+        // check min length
+        if (policy.getMinLength() > 0 && policy.getMinLength() > user.getUsername().length()) {
+            throw new AccountPolicyException("Username too short");
+        }
+
+        // check max length
+        if (policy.getMaxLength() > 0 && policy.getMaxLength() < user.getUsername().length()) {
+            throw new AccountPolicyException("Username too long");
+        }
+
+        // check words not permitted
+        for (String word : policy.getWordsNotPermitted()) {
+            if (user.getUsername().contains(word)) {
+                throw new AccountPolicyException("Used word(s) not permitted");
+            }
+        }
+
+        // check case
+        if (policy.isAllUpperCase() && !user.getUsername().equals(user.getUsername().toUpperCase())) {
+            throw new AccountPolicyException("No lowercase characters permitted");
+        }
+        if (policy.isAllLowerCase() && !user.getUsername().equals(user.getUsername().toLowerCase())) {
+            throw new AccountPolicyException("No uppercase characters permitted");
+        }
+
+        // check pattern
+        Pattern pattern = (policy.getPattern() == null) ? DEFAULT_PATTERN : Pattern.compile(policy.getPattern());
+        if (!pattern.matcher(user.getUsername()).matches()) {
+            throw new AccountPolicyException("Username does not match pattern");
+        }
+
+        // check prefix
+        for (String prefix : policy.getPrefixesNotPermitted()) {
+            if (user.getUsername().startsWith(prefix)) {
+                throw new AccountPolicyException("Prefix not permitted");
+            }
+        }
+
+        // check suffix
+        for (String suffix : policy.getSuffixesNotPermitted()) {
+            if (user.getUsername().endsWith(suffix)) {
+                throw new AccountPolicyException("Suffix not permitted");
+            }
+        }
+
+        // check for subsequent failed logins
+        if (userSuspender != null
+                && user.getFailedLogins() != null && policy.getPermittedLoginRetries() > 0
+                && user.getFailedLogins() > policy.getPermittedLoginRetries() && !user.isSuspended()) {
+
+            userSuspender.suspend(user, policy.isPropagateSuspension());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/AccountPolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/AccountPolicyException.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/AccountPolicyException.java
new file mode 100644
index 0000000..bfceecf
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/AccountPolicyException.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.server.misc.policy;
+
+public class AccountPolicyException extends PolicyException {
+
+    private static final long serialVersionUID = 2779416455067691813L;
+
+    public AccountPolicyException() {
+        super();
+    }
+
+    public AccountPolicyException(final String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/InvalidPasswordPolicySpecException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/InvalidPasswordPolicySpecException.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/InvalidPasswordPolicySpecException.java
new file mode 100644
index 0000000..dcd6d9a
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/InvalidPasswordPolicySpecException.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.server.misc.policy;
+
+/**
+ * Raise when the merge of two or more PasswordPolicySpec leds to incompatible condition.
+ *
+ * @see org.apache.syncope.common.lib.types.PasswordPolicySpec
+ */
+public class InvalidPasswordPolicySpecException extends Exception {
+
+    private static final long serialVersionUID = 4810651743226663580L;
+
+    public InvalidPasswordPolicySpecException(final String msg) {
+        super(msg);
+    }
+
+    public InvalidPasswordPolicySpecException(final String msg, final Exception e) {
+        super(msg, e);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PasswordPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PasswordPolicyEnforcer.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PasswordPolicyEnforcer.java
new file mode 100644
index 0000000..ac8513d
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PasswordPolicyEnforcer.java
@@ -0,0 +1,202 @@
+/*
+ * 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.server.misc.policy;
+
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PasswordPolicyEnforcer implements PolicyEnforcer<PasswordPolicySpec, User> {
+
+    /* (non-Javadoc)
+     * @see
+     * org.apache.syncope.core.policy.PasswordPolicyEnforcer#enforce(org.apache.syncope.common.types.PasswordPolicySpec,
+     * org.apache.syncope.common.types.PolicyType, java.lang.String)
+     */
+    @Override
+    public void enforce(final PasswordPolicySpec policy, final PolicyType type, final User user)
+            throws PasswordPolicyException, PolicyEnforceException {
+
+        final String clearPassword = user.getClearPassword();
+        final String password = user.getPassword();
+
+        if (policy == null) {
+            throw new PolicyEnforceException("Invalid policy");
+        }
+
+        if (password == null && !policy.isAllowNullPassword()) {
+            throw new PolicyEnforceException("Password must not be null and must be stored internally");
+        } else if (password != null && clearPassword != null) {
+            // check length
+            if (policy.getMinLength() > 0 && policy.getMinLength() > clearPassword.length()) {
+                throw new PasswordPolicyException("Password too short");
+            }
+
+            if (policy.getMaxLength() > 0 && policy.getMaxLength() < clearPassword.length()) {
+                throw new PasswordPolicyException("Password too long");
+            }
+
+            // check words not permitted
+            for (String word : policy.getWordsNotPermitted()) {
+                if (clearPassword.contains(word)) {
+                    throw new PasswordPolicyException("Used word(s) not permitted");
+                }
+            }
+
+            // check digits occurrence
+            if (policy.isDigitRequired() && !checkForDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain digit(s)");
+            }
+
+            // check lowercase alphabetic characters occurrence
+            if (policy.isLowercaseRequired() && !checkForLowercase(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain lowercase alphabetic character(s)");
+            }
+
+            // check uppercase alphabetic characters occurrence
+            if (policy.isUppercaseRequired() && !checkForUppercase(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain uppercase alphabetic character(s)");
+            }
+
+            // check prefix
+            for (String prefix : policy.getPrefixesNotPermitted()) {
+                if (clearPassword.startsWith(prefix)) {
+                    throw new PasswordPolicyException("Prefix not permitted");
+                }
+            }
+
+            // check suffix
+            for (String suffix : policy.getSuffixesNotPermitted()) {
+                if (clearPassword.endsWith(suffix)) {
+                    throw new PasswordPolicyException("Suffix not permitted");
+                }
+            }
+
+            // check digit first occurrence
+            if (policy.isMustStartWithDigit() && !checkForFirstDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password must start with a digit");
+            }
+
+            if (policy.isMustntStartWithDigit() && checkForFirstDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't start with a digit");
+            }
+
+            // check digit last occurrence
+            if (policy.isMustEndWithDigit() && !checkForLastDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password must end with a digit");
+            }
+
+            if (policy.isMustntEndWithDigit() && checkForLastDigit(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't end with a digit");
+            }
+
+            // check alphanumeric characters occurence
+            if (policy.isAlphanumericRequired() && !checkForAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain alphanumeric character(s)");
+            }
+
+            // check non alphanumeric characters occurence
+            if (policy.isNonAlphanumericRequired() && !checkForNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must contain non-alphanumeric character(s)");
+            }
+
+            // check alphanumeric character first occurrence
+            if (policy.isMustStartWithAlpha() && !checkForFirstAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must start with an alphanumeric character");
+            }
+
+            if (policy.isMustntStartWithAlpha() && checkForFirstAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't start with an alphanumeric character");
+            }
+
+            // check alphanumeric character last occurrence
+            if (policy.isMustEndWithAlpha() && !checkForLastAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must end with an alphanumeric character");
+            }
+
+            if (policy.isMustntEndWithAlpha() && checkForLastAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't end with an alphanumeric character");
+            }
+
+            // check non alphanumeric character first occurrence
+            if (policy.isMustStartWithNonAlpha() && !checkForFirstNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must start with a non-alphanumeric character");
+            }
+
+            if (policy.isMustntStartWithNonAlpha() && checkForFirstNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't start with a non-alphanumeric character");
+            }
+
+            // check non alphanumeric character last occurrence
+            if (policy.isMustEndWithNonAlpha() && !checkForLastNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password must end with a non-alphanumeric character");
+            }
+
+            if (policy.isMustntEndWithNonAlpha() && checkForLastNonAlphanumeric(clearPassword)) {
+                throw new PasswordPolicyException("Password mustn't end with a non-alphanumeric character");
+            }
+        }
+    }
+
+    private boolean checkForDigit(final String str) {
+        return PolicyPattern.DIGIT.matcher(str).matches();
+    }
+
+    private boolean checkForLowercase(final String str) {
+        return PolicyPattern.ALPHA_LOWERCASE.matcher(str).matches();
+    }
+
+    private boolean checkForUppercase(final String str) {
+        return PolicyPattern.ALPHA_UPPERCASE.matcher(str).matches();
+    }
+
+    private boolean checkForFirstDigit(final String str) {
+        return PolicyPattern.FIRST_DIGIT.matcher(str).matches();
+    }
+
+    private boolean checkForLastDigit(final String str) {
+        return PolicyPattern.LAST_DIGIT.matcher(str).matches();
+    }
+
+    private boolean checkForAlphanumeric(final String str) {
+        return PolicyPattern.ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForFirstAlphanumeric(final String str) {
+        return PolicyPattern.FIRST_ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForLastAlphanumeric(final String str) {
+        return PolicyPattern.LAST_ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForNonAlphanumeric(final String str) {
+        return PolicyPattern.NON_ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForFirstNonAlphanumeric(final String str) {
+        return PolicyPattern.FIRST_NON_ALPHANUMERIC.matcher(str).matches();
+    }
+
+    private boolean checkForLastNonAlphanumeric(final String str) {
+        return PolicyPattern.LAST_NON_ALPHANUMERIC.matcher(str).matches();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PasswordPolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PasswordPolicyException.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PasswordPolicyException.java
new file mode 100644
index 0000000..72534ba
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PasswordPolicyException.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.server.misc.policy;
+
+public class PasswordPolicyException extends PolicyException {
+
+    private static final long serialVersionUID = 8072104484395278469L;
+
+    public PasswordPolicyException() {
+        super();
+    }
+
+    public PasswordPolicyException(final String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEnforceException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEnforceException.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEnforceException.java
new file mode 100644
index 0000000..7c33172
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEnforceException.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.server.misc.policy;
+
+public class PolicyEnforceException extends PolicyException {
+
+    private static final long serialVersionUID = 3247084727383061069L;
+
+    public PolicyEnforceException() {
+        super();
+    }
+
+    public PolicyEnforceException(final String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEnforcer.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEnforcer.java
new file mode 100644
index 0000000..ba8e7e1
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEnforcer.java
@@ -0,0 +1,30 @@
+/*
+ * 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.server.misc.policy;
+
+import java.io.InvalidObjectException;
+
+import org.apache.syncope.common.lib.types.PolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+
+public interface PolicyEnforcer<T extends PolicySpec, E> {
+
+    void enforce(final T policy, final PolicyType type, final E object)
+            throws InvalidObjectException, PolicyException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEvaluator.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEvaluator.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEvaluator.java
new file mode 100644
index 0000000..21dde11
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyEvaluator.java
@@ -0,0 +1,109 @@
+/*
+ * 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.server.misc.policy;
+
+import java.util.List;
+import org.apache.syncope.common.lib.types.AccountPolicySpec;
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.common.lib.types.PolicySpec;
+import org.apache.syncope.server.persistence.api.entity.Attributable;
+import org.apache.syncope.server.persistence.api.entity.PlainAttr;
+import org.apache.syncope.server.persistence.api.entity.Policy;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PolicyEvaluator {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(PolicyEvaluator.class);
+
+    @SuppressWarnings("unchecked")
+    public <T extends PolicySpec> T evaluate(final Policy policy, final Attributable<?, ?, ?> attributable) {
+        if (policy == null) {
+            return null;
+        }
+
+        T result = null;
+        switch (policy.getType()) {
+            case PASSWORD:
+            case GLOBAL_PASSWORD:
+                final PasswordPolicySpec ppSpec = policy.getSpecification(PasswordPolicySpec.class);
+                final PasswordPolicySpec evaluatedPPSpec = new PasswordPolicySpec();
+
+                BeanUtils.copyProperties(ppSpec, evaluatedPPSpec, new String[] { "schemasNotPermitted" });
+
+                for (String schema : ppSpec.getSchemasNotPermitted()) {
+                    PlainAttr attr = attributable.getPlainAttr(schema);
+                    if (attr != null) {
+                        List<String> values = attr.getValuesAsStrings();
+                        if (values != null && !values.isEmpty()) {
+                            evaluatedPPSpec.getWordsNotPermitted().add(values.get(0));
+                        }
+                    }
+                }
+
+                // Password history verification and update
+                if (!(attributable instanceof User)) {
+                    LOG.error("Cannot check previous passwords. attributable is not a user object: {}",
+                            attributable.getClass().getName());
+                    result = (T) evaluatedPPSpec;
+                    break;
+                }
+                User user = (User) attributable;
+                if (user.verifyPasswordHistory(user.getClearPassword(), ppSpec.getHistoryLength())) {
+                    evaluatedPPSpec.getWordsNotPermitted().add(user.getClearPassword());
+                }
+                result = (T) evaluatedPPSpec;
+                break;
+
+            case ACCOUNT:
+            case GLOBAL_ACCOUNT:
+                final AccountPolicySpec spec = policy.getSpecification(AccountPolicySpec.class);
+                final AccountPolicySpec accountPolicy = new AccountPolicySpec();
+
+                BeanUtils.copyProperties(spec, accountPolicy, new String[] { "schemasNotPermitted" });
+
+                for (String schema : spec.getSchemasNotPermitted()) {
+                    PlainAttr attr = attributable.getPlainAttr(schema);
+                    if (attr != null) {
+                        List<String> values = attr.getValuesAsStrings();
+                        if (values != null && !values.isEmpty()) {
+                            accountPolicy.getWordsNotPermitted().add(values.get(0));
+                        }
+                    }
+                }
+
+                result = (T) accountPolicy;
+                break;
+
+            case SYNC:
+            case GLOBAL_SYNC:
+            default:
+                result = null;
+        }
+
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyException.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyException.java
new file mode 100644
index 0000000..ca6723c
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyException.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.server.misc.policy;
+
+public class PolicyException extends RuntimeException {
+
+    private static final long serialVersionUID = -6082115004491662910L;
+
+    public PolicyException() {
+        super();
+    }
+
+    public PolicyException(final String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyPattern.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyPattern.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyPattern.java
new file mode 100644
index 0000000..409e6e9
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/PolicyPattern.java
@@ -0,0 +1,50 @@
+/*
+ * 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.server.misc.policy;
+
+import java.util.regex.Pattern;
+
+public final class PolicyPattern {
+
+    public static final Pattern DIGIT = Pattern.compile(".*\\d+.*");
+
+    public static final Pattern ALPHA_LOWERCASE = Pattern.compile(".*[a-z]+.*");
+
+    public static final Pattern ALPHA_UPPERCASE = Pattern.compile(".*[A-Z]+.*");
+
+    public static final Pattern FIRST_DIGIT = Pattern.compile("\\d.*");
+
+    public static final Pattern LAST_DIGIT = Pattern.compile(".*\\d");
+
+    public static final Pattern ALPHANUMERIC = Pattern.compile(".*\\w.*");
+
+    public static final Pattern FIRST_ALPHANUMERIC = Pattern.compile("\\w.*");
+
+    public static final Pattern LAST_ALPHANUMERIC = Pattern.compile(".*\\w");
+
+    public static final Pattern NON_ALPHANUMERIC = Pattern.compile(".*\\W.*");
+
+    public static final Pattern FIRST_NON_ALPHANUMERIC = Pattern.compile("\\W.*");
+
+    public static final Pattern LAST_NON_ALPHANUMERIC = Pattern.compile(".*\\W");
+
+    private PolicyPattern() {
+        // private constructor for static utility class
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/UserSuspender.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/UserSuspender.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/UserSuspender.java
new file mode 100644
index 0000000..1fbf6f6
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/policy/UserSuspender.java
@@ -0,0 +1,26 @@
+/*
+ * 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.server.misc.policy;
+
+import org.apache.syncope.server.persistence.api.entity.user.User;
+
+public interface UserSuspender {
+
+    void suspend(User user, boolean propagateSuspension);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/search/SearchCondConverter.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/search/SearchCondConverter.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/search/SearchCondConverter.java
new file mode 100644
index 0000000..27e0d4a
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/search/SearchCondConverter.java
@@ -0,0 +1,50 @@
+/*
+ * 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.server.misc.search;
+
+import org.apache.cxf.jaxrs.ext.search.SearchBean;
+import org.apache.cxf.jaxrs.ext.search.fiql.FiqlParser;
+import org.apache.syncope.common.lib.search.SyncopeFiqlSearchConditionBuilder;
+import org.apache.syncope.server.persistence.api.dao.search.SearchCond;
+
+/**
+ * Converts FIQL expressions to Syncope's <tt>SearchCond</tt>.
+ */
+public final class SearchCondConverter {
+
+    /**
+     * Parses a FIQL expression into Syncope's <tt>SearchCond</tt>, using CXF's <tt>FiqlParser</tt>.
+     *
+     * @param fiqlExpression FIQL string
+     * @return <tt>SearchCond</tt> instance for given FIQL expression
+     * @see FiqlParser
+     */
+    public static SearchCond convert(final String fiqlExpression) {
+        FiqlParser<SearchBean> fiqlParser = new FiqlParser<SearchBean>(
+                SearchBean.class, SyncopeFiqlSearchConditionBuilder.CONTEXTUAL_PROPERTIES);
+        SearchCondVisitor searchCondVisitor = new SearchCondVisitor();
+
+        searchCondVisitor.visit(fiqlParser.parse(fiqlExpression));
+        return searchCondVisitor.getQuery();
+    }
+
+    private SearchCondConverter() {
+        // empty constructor for static utility class        
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/search/SearchCondVisitor.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/search/SearchCondVisitor.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/search/SearchCondVisitor.java
new file mode 100644
index 0000000..baa96f4
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/search/SearchCondVisitor.java
@@ -0,0 +1,203 @@
+/*
+ * 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.server.misc.search;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.cxf.jaxrs.ext.search.ConditionType;
+import org.apache.cxf.jaxrs.ext.search.SearchBean;
+import org.apache.cxf.jaxrs.ext.search.SearchCondition;
+import org.apache.cxf.jaxrs.ext.search.SearchUtils;
+import org.apache.cxf.jaxrs.ext.search.visitor.AbstractSearchConditionVisitor;
+import org.apache.syncope.common.lib.search.SearchableFields;
+import org.apache.syncope.common.lib.search.SpecialAttr;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.server.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.server.persistence.api.dao.search.EntitlementCond;
+import org.apache.syncope.server.persistence.api.dao.search.MembershipCond;
+import org.apache.syncope.server.persistence.api.dao.search.ResourceCond;
+import org.apache.syncope.server.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.server.persistence.api.dao.search.SubjectCond;
+
+/**
+ * Converts CXF's <tt>SearchCondition</tt> into internal <tt>SearchCond</tt>.
+ */
+public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean, SearchCond> {
+
+    private static final List<String> ATTRIBUTABLE_FIELDS;
+
+    static {
+        ATTRIBUTABLE_FIELDS = new ArrayList<String>();
+        ATTRIBUTABLE_FIELDS.addAll(SearchableFields.get(UserTO.class));
+        ATTRIBUTABLE_FIELDS.addAll(SearchableFields.get(RoleTO.class));
+    }
+
+    private SearchCond searchCond;
+
+    public SearchCondVisitor() {
+        super(null);
+    }
+
+    public SearchCondVisitor(final Map<String, String> fieldMap) {
+        super(fieldMap);
+    }
+
+    private AttributeCond createAttributeCond(final String schema) {
+        AttributeCond attributeCond = ATTRIBUTABLE_FIELDS.contains(schema)
+                ? new SubjectCond()
+                : new AttributeCond();
+        attributeCond.setSchema(schema);
+        return attributeCond;
+    }
+
+    private SearchCond visitPrimitive(final SearchCondition<SearchBean> sc) {
+        String name = getRealPropertyName(sc.getStatement().getProperty());
+        SpecialAttr specialAttrName = SpecialAttr.fromString(name);
+
+        String value = SearchUtils.toSqlWildcardString(sc.getStatement().getValue().toString(), false).
+                replaceAll("\\\\_", "_");
+        SpecialAttr specialAttrValue = SpecialAttr.fromString(value);
+
+        AttributeCond attributeCond = createAttributeCond(name);
+        attributeCond.setExpression(value);
+
+        SearchCond leaf;
+        switch (sc.getConditionType()) {
+            case EQUALS:
+            case NOT_EQUALS:
+                if (specialAttrName == null) {
+                    if (specialAttrValue != null && specialAttrValue == SpecialAttr.NULL) {
+                        attributeCond.setType(AttributeCond.Type.ISNULL);
+                        attributeCond.setExpression(null);
+                    } else if (value.indexOf('%') == -1) {
+                        attributeCond.setType(AttributeCond.Type.EQ);
+                    } else {
+                        attributeCond.setType(AttributeCond.Type.LIKE);
+                    }
+
+                    leaf = SearchCond.getLeafCond(attributeCond);
+                } else {
+                    switch (specialAttrName) {
+                        case ROLES:
+                            MembershipCond membershipCond = new MembershipCond();
+                            membershipCond.setRoleId(Long.valueOf(value));
+                            leaf = SearchCond.getLeafCond(membershipCond);
+                            break;
+
+                        case RESOURCES:
+                            ResourceCond resourceCond = new ResourceCond();
+                            resourceCond.setResourceName(value);
+                            leaf = SearchCond.getLeafCond(resourceCond);
+                            break;
+
+                        case ENTITLEMENTS:
+                            EntitlementCond entitlementCond = new EntitlementCond();
+                            entitlementCond.setExpression(value);
+                            leaf = SearchCond.getLeafCond(entitlementCond);
+                            break;
+
+                        default:
+                            throw new IllegalArgumentException(
+                                    String.format("Special attr name %s is not supported", specialAttrName));
+                    }
+                }
+                if (sc.getConditionType() == ConditionType.NOT_EQUALS) {
+                    if (leaf.getAttributeCond() != null
+                            && leaf.getAttributeCond().getType() == AttributeCond.Type.ISNULL) {
+
+                        leaf.getAttributeCond().setType(AttributeCond.Type.ISNOTNULL);
+                    } else if (leaf.getSubjectCond() != null
+                            && leaf.getSubjectCond().getType() == SubjectCond.Type.ISNULL) {
+
+                        leaf.getSubjectCond().setType(AttributeCond.Type.ISNOTNULL);
+                    } else {
+                        leaf = SearchCond.getNotLeafCond(leaf);
+                    }
+                }
+                break;
+
+            case GREATER_OR_EQUALS:
+                attributeCond.setType(AttributeCond.Type.GE);
+                leaf = SearchCond.getLeafCond(attributeCond);
+                break;
+
+            case GREATER_THAN:
+                attributeCond.setType(AttributeCond.Type.GT);
+                leaf = SearchCond.getLeafCond(attributeCond);
+                break;
+
+            case LESS_OR_EQUALS:
+                attributeCond.setType(AttributeCond.Type.LE);
+                leaf = SearchCond.getLeafCond(attributeCond);
+                break;
+
+            case LESS_THAN:
+                attributeCond.setType(AttributeCond.Type.LT);
+                leaf = SearchCond.getLeafCond(attributeCond);
+                break;
+
+            default:
+                throw new IllegalArgumentException(
+                        String.format("Condition type %s is not supported", sc.getConditionType().name()));
+        }
+
+        return leaf;
+    }
+
+    private SearchCond visitCompount(final SearchCondition<SearchBean> sc) {
+        List<SearchCond> searchConds = new ArrayList<SearchCond>();
+        for (SearchCondition<SearchBean> searchCondition : sc.getSearchConditions()) {
+            searchConds.add(searchCondition.getStatement() == null
+                    ? visitCompount(searchCondition)
+                    : visitPrimitive(searchCondition));
+        }
+
+        SearchCond compound;
+        switch (sc.getConditionType()) {
+            case AND:
+                compound = SearchCond.getAndCond(searchConds);
+                break;
+
+            case OR:
+                compound = SearchCond.getOrCond(searchConds);
+                break;
+
+            default:
+                throw new IllegalArgumentException(
+                        String.format("Condition type %s is not supported", sc.getConditionType().name()));
+        }
+
+        return compound;
+    }
+
+    @Override
+    public void visit(final SearchCondition<SearchBean> sc) {
+        searchCond = sc.getStatement() == null
+                ? visitCompount(sc)
+                : visitPrimitive(sc);
+    }
+
+    @Override
+    public SearchCond getQuery() {
+        return searchCond;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/AuthContextUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/AuthContextUtil.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/AuthContextUtil.java
new file mode 100644
index 0000000..e58a157
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/AuthContextUtil.java
@@ -0,0 +1,74 @@
+/*
+ * 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.server.misc.security;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public final class AuthContextUtil {
+
+    public static String getAuthenticatedUsername() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        return authentication == null ? SyncopeConstants.UNAUTHENTICATED : authentication.getName();
+    }
+
+    public static Set<String> getOwnedEntitlementNames() {
+        final Set<String> result = new HashSet<>();
+
+        final SecurityContext ctx = SecurityContextHolder.getContext();
+
+        if (ctx != null && ctx.getAuthentication() != null && ctx.getAuthentication().getAuthorities() != null) {
+            for (GrantedAuthority authority : ctx.getAuthentication().getAuthorities()) {
+                result.add(authority.getAuthority());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Extend the current authentication context to include the given role.
+     *
+     * @param roleKey role key
+     * @param roleEntitlement role entitlement
+     */
+    public static void extendAuthContext(final Long roleKey, final String roleEntitlement) {
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(auth.getAuthorities());
+        authorities.add(new SimpleGrantedAuthority(roleEntitlement));
+        Authentication newAuth = new UsernamePasswordAuthenticationToken(
+                auth.getPrincipal(), auth.getCredentials(), authorities);
+        SecurityContextHolder.getContext().setAuthentication(newAuth);
+    }
+
+    /**
+     * Private default constructor, for static-only classes.
+     */
+    private AuthContextUtil() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/Encryptor.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/Encryptor.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/Encryptor.java
new file mode 100644
index 0000000..6963472
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/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.server.misc.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<String, Encryptor>();
+
+    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 secretKey;
+
+    private static Integer saltIterations;
+
+    private static Integer saltSizeBytes;
+
+    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);
+
+            secretKey = props.getProperty("secretKey");
+            saltIterations = Integer.valueOf(props.getProperty("digester.saltIterations"));
+            saltSizeBytes = 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 (secretKey == null) {
+            secretKey = DEFAULT_SECRET_KEY;
+            LOG.debug("secretKey not found, reverting to default");
+        }
+        if (saltIterations == null) {
+            saltIterations = DEFAULT_SALT_ITERATIONS;
+            LOG.debug("digester.saltIterations not found, reverting to default");
+        }
+        if (saltSizeBytes == null) {
+            saltSizeBytes = 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(secretKey);
+    }
+
+    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_ENCODING), 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_ENCODING);
+
+                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_ENCODING);
+
+            final Cipher cipher = Cipher.getInstance(CipherAlgorithm.AES.getAlgorithm());
+            cipher.init(Cipher.DECRYPT_MODE, keySpec);
+
+            value = new String(cipher.doFinal(Base64.decode(encoded)), SyncopeConstants.DEFAULT_ENCODING);
+        }
+
+        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(saltIterations);
+            digester.setSaltSizeBytes(saltSizeBytes);
+            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/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/PasswordGenerator.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/PasswordGenerator.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/PasswordGenerator.java
new file mode 100644
index 0000000..3712ae5
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/security/PasswordGenerator.java
@@ -0,0 +1,321 @@
+/*
+ * 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.server.misc.security;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.server.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.misc.policy.InvalidPasswordPolicySpecException;
+import org.apache.syncope.server.misc.policy.PolicyPattern;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Generate random passwords according to given policies.
+ *
+ * @see PasswordPolicy
+ */
+@Component
+public class PasswordGenerator {
+
+    private static final char[] SPECIAL_CHARS = { '!', '£', '%', '&', '(', ')', '?', '#', '$' };
+
+    @Autowired
+    private PolicyDAO policyDAO;
+
+    public String generate(final List<PasswordPolicySpec> ppSpecs)
+            throws InvalidPasswordPolicySpecException {
+
+        PasswordPolicySpec policySpec = merge(ppSpecs);
+
+        check(policySpec);
+
+        return generate(policySpec);
+    }
+
+    public String generate(final User user)
+            throws InvalidPasswordPolicySpecException {
+
+        List<PasswordPolicySpec> ppSpecs = new ArrayList<PasswordPolicySpec>();
+
+        PasswordPolicy globalPP = policyDAO.getGlobalPasswordPolicy();
+        if (globalPP != null && globalPP.getSpecification(PasswordPolicySpec.class) != null) {
+            ppSpecs.add(globalPP.getSpecification(PasswordPolicySpec.class));
+        }
+
+        for (Role role : user.getRoles()) {
+            if (role.getPasswordPolicy() != null
+                    && role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {
+
+                ppSpecs.add(role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
+            }
+        }
+
+        for (ExternalResource resource : user.getResources()) {
+            if (resource.getPasswordPolicy() != null
+                    && resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {
+
+                ppSpecs.add(resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
+            }
+        }
+
+        PasswordPolicySpec policySpec = merge(ppSpecs);
+        check(policySpec);
+        return generate(policySpec);
+    }
+
+    private PasswordPolicySpec merge(final List<PasswordPolicySpec> ppSpecs) {
+        PasswordPolicySpec fpps = new PasswordPolicySpec();
+        fpps.setMinLength(0);
+        fpps.setMaxLength(1000);
+
+        for (PasswordPolicySpec policySpec : ppSpecs) {
+            if (policySpec.getMinLength() > fpps.getMinLength()) {
+                fpps.setMinLength(policySpec.getMinLength());
+            }
+
+            if ((policySpec.getMaxLength() != 0) && ((policySpec.getMaxLength() < fpps.getMaxLength()))) {
+                fpps.setMaxLength(policySpec.getMaxLength());
+            }
+            fpps.getPrefixesNotPermitted().addAll(policySpec.getPrefixesNotPermitted());
+            fpps.getSuffixesNotPermitted().addAll(policySpec.getSuffixesNotPermitted());
+
+            if (!fpps.isNonAlphanumericRequired()) {
+                fpps.setNonAlphanumericRequired(policySpec.isNonAlphanumericRequired());
+            }
+
+            if (!fpps.isAlphanumericRequired()) {
+                fpps.setAlphanumericRequired(policySpec.isAlphanumericRequired());
+            }
+            if (!fpps.isDigitRequired()) {
+                fpps.setDigitRequired(policySpec.isDigitRequired());
+            }
+
+            if (!fpps.isLowercaseRequired()) {
+                fpps.setLowercaseRequired(policySpec.isLowercaseRequired());
+            }
+            if (!fpps.isUppercaseRequired()) {
+                fpps.setUppercaseRequired(policySpec.isUppercaseRequired());
+            }
+            if (!fpps.isMustStartWithDigit()) {
+                fpps.setMustStartWithDigit(policySpec.isMustStartWithDigit());
+            }
+            if (!fpps.isMustntStartWithDigit()) {
+                fpps.setMustntStartWithDigit(policySpec.isMustntStartWithDigit());
+            }
+            if (!fpps.isMustEndWithDigit()) {
+                fpps.setMustEndWithDigit(policySpec.isMustEndWithDigit());
+            }
+            if (fpps.isMustntEndWithDigit()) {
+                fpps.setMustntEndWithDigit(policySpec.isMustntEndWithDigit());
+            }
+            if (!fpps.isMustStartWithAlpha()) {
+                fpps.setMustStartWithAlpha(policySpec.isMustStartWithAlpha());
+            }
+            if (!fpps.isMustntStartWithAlpha()) {
+                fpps.setMustntStartWithAlpha(policySpec.isMustntStartWithAlpha());
+            }
+            if (!fpps.isMustStartWithNonAlpha()) {
+                fpps.setMustStartWithNonAlpha(policySpec.isMustStartWithNonAlpha());
+            }
+            if (!fpps.isMustntStartWithNonAlpha()) {
+                fpps.setMustntStartWithNonAlpha(policySpec.isMustntStartWithNonAlpha());
+            }
+            if (!fpps.isMustEndWithNonAlpha()) {
+                fpps.setMustEndWithNonAlpha(policySpec.isMustEndWithNonAlpha());
+            }
+            if (!fpps.isMustntEndWithNonAlpha()) {
+                fpps.setMustntEndWithNonAlpha(policySpec.isMustntEndWithNonAlpha());
+            }
+            if (!fpps.isMustEndWithAlpha()) {
+                fpps.setMustEndWithAlpha(policySpec.isMustEndWithAlpha());
+            }
+            if (!fpps.isMustntEndWithAlpha()) {
+                fpps.setMustntEndWithAlpha(policySpec.isMustntEndWithAlpha());
+            }
+        }
+        return fpps;
+    }
+
+    private void check(final PasswordPolicySpec policySpec)
+            throws InvalidPasswordPolicySpecException {
+
+        if (policySpec.getMinLength() == 0) {
+            throw new InvalidPasswordPolicySpecException("Minimum length is zero");
+        }
+        if (policySpec.isMustEndWithAlpha() && policySpec.isMustntEndWithAlpha()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustEndWithAlpha and mustntEndWithAlpha are both true");
+        }
+        if (policySpec.isMustEndWithAlpha() && policySpec.isMustEndWithDigit()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustEndWithAlpha and mustEndWithDigit are both true");
+        }
+        if (policySpec.isMustEndWithDigit() && policySpec.isMustntEndWithDigit()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustEndWithDigit and mustntEndWithDigit are both true");
+        }
+        if (policySpec.isMustEndWithNonAlpha() && policySpec.isMustntEndWithNonAlpha()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustEndWithNonAlpha and mustntEndWithNonAlpha are both true");
+        }
+        if (policySpec.isMustStartWithAlpha() && policySpec.isMustntStartWithAlpha()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustStartWithAlpha and mustntStartWithAlpha are both true");
+        }
+        if (policySpec.isMustStartWithAlpha() && policySpec.isMustStartWithDigit()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustStartWithAlpha and mustStartWithDigit are both true");
+        }
+        if (policySpec.isMustStartWithDigit() && policySpec.isMustntStartWithDigit()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustStartWithDigit and mustntStartWithDigit are both true");
+        }
+        if (policySpec.isMustStartWithNonAlpha() && policySpec.isMustntStartWithNonAlpha()) {
+            throw new InvalidPasswordPolicySpecException(
+                    "mustStartWithNonAlpha and mustntStartWithNonAlpha are both true");
+        }
+        if (policySpec.getMinLength() > policySpec.getMaxLength()) {
+            throw new InvalidPasswordPolicySpecException("Minimun length (" + policySpec.getMinLength() + ")"
+                    + "is greater than maximum length (" + policySpec.getMaxLength() + ")");
+        }
+    }
+
+    private String generate(final PasswordPolicySpec policySpec) {
+        String[] generatedPassword = new String[policySpec.getMinLength()];
+
+        for (int i = 0; i < generatedPassword.length; i++) {
+            generatedPassword[i] = "";
+        }
+
+        checkStartChar(generatedPassword, policySpec);
+
+        checkEndChar(generatedPassword, policySpec);
+
+        checkRequired(generatedPassword, policySpec);
+
+        //filled empty chars
+        for (int firstEmptyChar = firstEmptyChar(generatedPassword);
+                firstEmptyChar < generatedPassword.length - 1; firstEmptyChar++) {
+            generatedPassword[firstEmptyChar] = SecureRandomUtil.generateRandomLetter();
+        }
+
+        checkPrefixAndSuffix(generatedPassword, policySpec);
+
+        return StringUtils.join(generatedPassword);
+    }
+
+    private void checkStartChar(final String[] generatedPassword, final PasswordPolicySpec policySpec) {
+        if (policySpec.isMustStartWithAlpha()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomLetter();
+        }
+        if (policySpec.isMustStartWithNonAlpha() || policySpec.isMustStartWithDigit()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomNumber();
+        }
+        if (policySpec.isMustntStartWithAlpha()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomNumber();
+
+        }
+        if (policySpec.isMustntStartWithDigit()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomLetter();
+
+        }
+        if (policySpec.isMustntStartWithNonAlpha()) {
+            generatedPassword[0] = SecureRandomUtil.generateRandomLetter();
+
+        }
+    }
+
+    private void checkEndChar(final String[] generatedPassword, final PasswordPolicySpec policySpec) {
+        if (policySpec.isMustEndWithAlpha()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomLetter();
+        }
+        if (policySpec.isMustEndWithNonAlpha() || policySpec.isMustEndWithDigit()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomNumber();
+        }
+
+        if (policySpec.isMustntEndWithAlpha()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomNumber();
+        }
+        if (policySpec.isMustntEndWithDigit()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomLetter();
+        }
+        if (policySpec.isMustntEndWithNonAlpha()) {
+            generatedPassword[policySpec.getMinLength() - 1] = SecureRandomUtil.generateRandomLetter();
+
+        }
+    }
+
+    private int firstEmptyChar(final String[] generatedPStrings) {
+        int index = 0;
+        while (!generatedPStrings[index].isEmpty()) {
+            index++;
+        }
+        return index;
+    }
+
+    private void checkRequired(final String[] generatedPassword, final PasswordPolicySpec policySpec) {
+        if (policySpec.isDigitRequired()
+                && !PolicyPattern.DIGIT.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+            generatedPassword[firstEmptyChar(generatedPassword)] = SecureRandomUtil.generateRandomNumber();
+        }
+
+        if (policySpec.isUppercaseRequired()
+                && !PolicyPattern.ALPHA_UPPERCASE.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+            generatedPassword[firstEmptyChar(generatedPassword)] = SecureRandomUtil.generateRandomLetter().toUpperCase();
+        }
+
+        if (policySpec.isLowercaseRequired()
+                && !PolicyPattern.ALPHA_LOWERCASE.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+            generatedPassword[firstEmptyChar(generatedPassword)] = SecureRandomUtil.generateRandomLetter().toLowerCase();
+        }
+
+        if (policySpec.isNonAlphanumericRequired()
+                && !PolicyPattern.NON_ALPHANUMERIC.matcher(StringUtils.join(generatedPassword)).matches()) {
+
+            generatedPassword[firstEmptyChar(generatedPassword)] =
+                    SecureRandomUtil.generateRandomSpecialCharacter(SPECIAL_CHARS);
+        }
+    }
+
+    private void checkPrefixAndSuffix(final String[] generatedPassword, final PasswordPolicySpec policySpec) {
+        for (String prefix : policySpec.getPrefixesNotPermitted()) {
+            if (StringUtils.join(generatedPassword).startsWith(prefix)) {
+                checkStartChar(generatedPassword, policySpec);
+            }
+        }
+
+        for (String suffix : policySpec.getSuffixesNotPermitted()) {
+            if (StringUtils.join(generatedPassword).endsWith(suffix)) {
+                checkEndChar(generatedPassword, policySpec);
+            }
+        }
+    }
+
+}


Mime
View raw message