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 2015/02/12 10:14:49 UTC
[44/54] [abbrv] [partial] syncope git commit: [SYNCOPE-620] Renaming
'server' after 'core',
to provide continuity with older releases (especially for archetype)
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/JexlUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/JexlUtil.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/JexlUtil.java
new file mode 100644
index 0000000..e060dca
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.entity.Attributable;
+import org.apache.syncope.core.persistence.api.entity.DerAttr;
+import org.apache.syncope.core.persistence.api.entity.PlainAttr;
+import org.apache.syncope.core.persistence.api.entity.VirAttr;
+import org.apache.syncope.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyEnforcer.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyEnforcer.java
new file mode 100644
index 0000000..c924721
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyEnforcer.java
@@ -0,0 +1,102 @@
+/*
+ * 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.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.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.UserSuspender;
+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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyException.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/AccountPolicyException.java
new file mode 100644
index 0000000..0d3eff7
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/InvalidPasswordPolicySpecException.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/InvalidPasswordPolicySpecException.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/InvalidPasswordPolicySpecException.java
new file mode 100644
index 0000000..7d50951
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyEnforcer.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyEnforcer.java
new file mode 100644
index 0000000..12968c5
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyEnforcer.java
@@ -0,0 +1,197 @@
+/*
+ * 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.misc.policy;
+
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PasswordPolicyEnforcer implements PolicyEnforcer<PasswordPolicySpec, User> {
+
+ @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 mandatory");
+ } 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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyException.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PasswordPolicyException.java
new file mode 100644
index 0000000..7bf530b
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyEnforceException.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyEnforceException.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyEnforceException.java
new file mode 100644
index 0000000..e7211b8
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyEnforcer.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyEnforcer.java
new file mode 100644
index 0000000..fb9f364
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyEvaluator.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyEvaluator.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyEvaluator.java
new file mode 100644
index 0000000..a3bd84e
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.entity.Attributable;
+import org.apache.syncope.core.persistence.api.entity.PlainAttr;
+import org.apache.syncope.core.persistence.api.entity.Policy;
+import org.apache.syncope.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyException.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyException.java
new file mode 100644
index 0000000..5a2ef36
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyPattern.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyPattern.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/policy/PolicyPattern.java
new file mode 100644
index 0000000..ecb4e6c
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/search/SearchCondConverter.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/search/SearchCondConverter.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/search/SearchCondConverter.java
new file mode 100644
index 0000000..dd56314
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/search/SearchCondVisitor.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/search/SearchCondVisitor.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/search/SearchCondVisitor.java
new file mode 100644
index 0000000..3e941ac
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.EntitlementCond;
+import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
+import org.apache.syncope.core.persistence.api.dao.search.ResourceCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/AuthContextUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/AuthContextUtil.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/AuthContextUtil.java
new file mode 100644
index 0000000..6f91fe3
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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<>(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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/Encryptor.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/Encryptor.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/Encryptor.java
new file mode 100644
index 0000000..aac337a
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/PasswordGenerator.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/PasswordGenerator.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/PasswordGenerator.java
new file mode 100644
index 0000000..3f8060e
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.core.persistence.api.entity.role.Role;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.misc.policy.InvalidPasswordPolicySpecException;
+import org.apache.syncope.core.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);
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SecureRandomUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SecureRandomUtil.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SecureRandomUtil.java
new file mode 100644
index 0000000..39a0112
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SecureRandomUtil.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.misc.security;
+
+import java.security.SecureRandom;
+
+import org.apache.commons.lang3.RandomStringUtils;
+
+public final class SecureRandomUtil {
+
+ 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(char[] characters) {
+ return RandomStringUtils.random(1, 0, 0, false, false, characters, RANDOM);
+ }
+
+ private SecureRandomUtil() {
+ // private constructor for static utility class
+ }
+}