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/01/01 19:12:10 UTC
[15/32] syncope git commit: [SYNCOPE-620] JPA entities + basic tests
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/BeanUtils.java
----------------------------------------------------------------------
diff --git a/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/BeanUtils.java b/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/BeanUtils.java
new file mode 100644
index 0000000..f937b40
--- /dev/null
+++ b/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/BeanUtils.java
@@ -0,0 +1,201 @@
+/*
+ * 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.spring;
+
+import static org.springframework.beans.BeanUtils.getPropertyDescriptor;
+import static org.springframework.beans.BeanUtils.getPropertyDescriptors;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.FatalBeanException;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Overrides Spring's BeanUtils not using collection setters but instead getters + addAll() / putAll(),
+ * in a JAXB friendly way.
+ *
+ * Refer to <a href="https://issues.apache.org/jira/browse/SYNCOPE-246">SYNCOPE-246</a> for more information.
+ *
+ * @see org.springframework.beans.BeanUtils
+ */
+public final class BeanUtils {
+
+ private BeanUtils() {
+ // Empty private constructor for static utility classes
+ }
+
+ /**
+ * Copy the property values of the given source bean into the target bean.
+ * <p>
+ * Note: The source and target classes do not have to match or even be derived
+ * from each other, as long as the properties match. Any bean properties that the
+ * source bean exposes but the target bean does not will silently be ignored.
+ * </p><p>
+ * This is just a convenience method. For more complex transfer needs,
+ * consider using a full BeanWrapper.
+ * </p>
+ * @param source the source bean
+ * @param target the target bean
+ * @throws BeansException if the copying failed
+ * @see org.springframework.beans.BeanWrapper
+ */
+ public static void copyProperties(final Object source, final Object target) throws BeansException {
+ copyProperties(source, target, null, (String[]) null);
+ }
+
+ /**
+ * Copy the property values of the given source bean into the given target bean,
+ * only setting properties defined in the given "editable" class (or interface).
+ * <p>
+ * Note: The source and target classes do not have to match or even be derived
+ * from each other, as long as the properties match. Any bean properties that the
+ * source bean exposes but the target bean does not will silently be ignored.
+ * </p><p>
+ * This is just a convenience method. For more complex transfer needs,
+ * consider using a full BeanWrapper.
+ * </p>
+ *
+ * @param source the source bean
+ * @param target the target bean
+ * @param editable the class (or interface) to restrict property setting to
+ * @throws BeansException if the copying failed
+ * @see org.springframework.beans.BeanWrapper
+ */
+ public static void copyProperties(final Object source, final Object target, final Class<?> editable)
+ throws BeansException {
+
+ copyProperties(source, target, editable, (String[]) null);
+ }
+
+ /**
+ * Copy the property values of the given source bean into the given target bean,
+ * ignoring the given "ignoreProperties".
+ * <p>
+ * Note: The source and target classes do not have to match or even be derived
+ * from each other, as long as the properties match. Any bean properties that the
+ * source bean exposes but the target bean does not will silently be ignored.
+ * </p><p>
+ * This is just a convenience method. For more complex transfer needs,
+ * consider using a full BeanWrapper.
+ * </p>
+ *
+ * @param source the source bean
+ * @param target the target bean
+ * @param ignoreProperties array of property names to ignore
+ * @throws BeansException if the copying failed
+ * @see org.springframework.beans.BeanWrapper
+ */
+ public static void copyProperties(final Object source, final Object target, final String... ignoreProperties)
+ throws BeansException {
+
+ copyProperties(source, target, null, ignoreProperties);
+ }
+
+ /**
+ * Copy the property values of the given source bean into the given target bean.
+ * <p>
+ * Note: The source and target classes do not have to match or even be derived
+ * from each other, as long as the properties match. Any bean properties that the
+ * source bean exposes but the target bean does not will silently be ignored.
+ * </p>
+ *
+ * @param source the source bean
+ * @param target the target bean
+ * @param editable the class (or interface) to restrict property setting to
+ * @param ignoreProperties array of property names to ignore
+ * @throws BeansException if the copying failed
+ * @see org.springframework.beans.BeanWrapper
+ */
+ @SuppressWarnings("unchecked")
+ private static void copyProperties(final Object source, final Object target, final Class<?> editable,
+ final String... ignoreProperties) throws BeansException {
+
+ Assert.notNull(source, "Source must not be null");
+ Assert.notNull(target, "Target must not be null");
+
+ Class<?> actualEditable = target.getClass();
+ if (editable != null) {
+ if (!editable.isInstance(target)) {
+ throw new IllegalArgumentException("Target class [" + target.getClass().getName()
+ + "] not assignable to Editable class [" + editable.getName() + "]");
+ }
+ actualEditable = editable;
+ }
+ PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
+ List<String> ignoreList = (ignoreProperties == null)
+ ? Collections.<String>emptyList() : Arrays.asList(ignoreProperties);
+
+ for (PropertyDescriptor targetPd : targetPds) {
+ if (ignoreProperties == null || (!ignoreList.contains(targetPd.getName()))) {
+ PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
+ if (sourcePd != null) {
+ Method readMethod = sourcePd.getReadMethod();
+ if (readMethod != null) {
+ Method writeMethod = targetPd.getWriteMethod();
+
+ try {
+ // Diverts from Spring's BeanUtils: if no write method is found and property is collection,
+ // try to use addAll() / putAll().
+ if (writeMethod == null) {
+ Object value = readMethod.invoke(source);
+ Method targetReadMethod = targetPd.getReadMethod();
+ if (targetReadMethod != null) {
+ if (!Modifier.isPublic(targetReadMethod.getDeclaringClass().getModifiers())) {
+ targetReadMethod.setAccessible(true);
+ }
+ Object destValue = targetReadMethod.invoke(target);
+
+ if (value instanceof Collection && destValue instanceof Collection) {
+ ((Collection) destValue).clear();
+ ((Collection) destValue).addAll((Collection) value);
+ } else if (value instanceof Map && destValue instanceof Map) {
+ ((Map) destValue).clear();
+ ((Map) destValue).putAll((Map) value);
+ }
+ }
+ } else if (ClassUtils.isAssignable(
+ writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
+
+ if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
+ readMethod.setAccessible(true);
+ }
+ Object value = readMethod.invoke(source);
+ if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
+ writeMethod.setAccessible(true);
+ }
+ writeMethod.invoke(target, value);
+ }
+ } catch (Throwable ex) {
+ throw new FatalBeanException(
+ "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
+ }
+ }
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/ResourceWithFallbackLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/ResourceWithFallbackLoader.java b/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/ResourceWithFallbackLoader.java
new file mode 100644
index 0000000..9b771b3
--- /dev/null
+++ b/syncope620/server/spring/src/main/java/org/apache/syncope/server/spring/ResourceWithFallbackLoader.java
@@ -0,0 +1,82 @@
+/*
+ * 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.spring;
+
+import java.io.IOException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+public class ResourceWithFallbackLoader implements ResourceLoaderAware, ResourcePatternResolver {
+
+ private ResourcePatternResolver resolver;
+
+ private String primary;
+
+ private String fallback;
+
+ @Override
+ public void setResourceLoader(final ResourceLoader resourceLoader) {
+ this.resolver = (ResourcePatternResolver) resourceLoader;
+ }
+
+ public void setPrimary(final String primary) {
+ this.primary = primary;
+ }
+
+ public void setFallback(final String fallback) {
+ this.fallback = fallback;
+ }
+
+ @Override
+ public Resource getResource(final String location) {
+ Resource resource = resolver.getResource(primary + location);
+ if (!resource.exists()) {
+ resource = resolver.getResource(fallback + location);
+ }
+
+ return resource;
+ }
+
+ public Resource getResource() {
+ return getResource(StringUtils.EMPTY);
+ }
+
+ @Override
+ public Resource[] getResources(final String locationPattern) throws IOException {
+ Resource[] resources = resolver.getResources(primary + locationPattern);
+ if (ArrayUtils.isEmpty(resources)) {
+ resources = resolver.getResources(fallback + locationPattern);
+ }
+
+ return resources;
+ }
+
+ public Resource[] getResources() throws IOException {
+ return getResources(StringUtils.EMPTY);
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return resolver.getClassLoader();
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/pom.xml b/syncope620/server/utils/pom.xml
new file mode 100644
index 0000000..af4a55e
--- /dev/null
+++ b/syncope620/server/utils/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.syncope</groupId>
+ <artifactId>syncope-server</artifactId>
+ <version>2.0.0-SNAPSHOT</version>
+ </parent>
+
+ <name>Apache Syncope Server Utilities</name>
+ <description>Apache Syncope Server Utilities</description>
+ <groupId>org.apache.syncope.server</groupId>
+ <artifactId>syncope-server-utils</artifactId>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-jexl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-afterburner</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.syncope.server</groupId>
+ <artifactId>syncope-persistence-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/DataFormat.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/DataFormat.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/DataFormat.java
new file mode 100644
index 0000000..d30f3c4
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/DataFormat.java
@@ -0,0 +1,117 @@
+/*
+ * 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.utils;
+
+import java.text.DecimalFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import org.apache.commons.lang3.time.DateUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+
+/**
+ * Utility class for parsing / formatting date and numbers.
+ */
+public final class DataFormat {
+
+ private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
+
+ @Override
+ protected SimpleDateFormat initialValue() {
+ SimpleDateFormat sdf = new SimpleDateFormat();
+ sdf.applyPattern(SyncopeConstants.DEFAULT_DATE_PATTERN);
+ return sdf;
+ }
+ };
+
+ private static final ThreadLocal<DecimalFormat> DECIMAL_FORMAT = new ThreadLocal<DecimalFormat>() {
+
+ @Override
+ protected DecimalFormat initialValue() {
+ return new DecimalFormat();
+ }
+ };
+
+ public static String format(final Date date) {
+ return format(date, true);
+ }
+
+ public static String format(final Date date, final boolean lenient) {
+ return format(date, lenient, null);
+ }
+
+ public static String format(final Date date, final boolean lenient, final String conversionPattern) {
+ SimpleDateFormat sdf = DATE_FORMAT.get();
+ if (conversionPattern != null) {
+ sdf.applyPattern(conversionPattern);
+ }
+ sdf.setLenient(lenient);
+ return sdf.format(date);
+ }
+
+ public static String format(final long number) {
+ return format(number, null);
+ }
+
+ public static String format(final long number, final String conversionPattern) {
+ DecimalFormat df = DECIMAL_FORMAT.get();
+ if (conversionPattern != null) {
+ df.applyPattern(conversionPattern);
+ }
+ return df.format(number);
+ }
+
+ public static String format(final double number) {
+ return format(number, null);
+ }
+
+ public static String format(final double number, final String conversionPattern) {
+ DecimalFormat df = DECIMAL_FORMAT.get();
+ if (conversionPattern != null) {
+ df.applyPattern(conversionPattern);
+ }
+ return df.format(number);
+ }
+
+ public static Date parseDate(final String source) throws ParseException {
+ return DateUtils.parseDate(source, SyncopeConstants.DATE_PATTERNS);
+ }
+
+ public static Date parseDate(final String source, final String conversionPattern) throws ParseException {
+ SimpleDateFormat sdf = DATE_FORMAT.get();
+ sdf.applyPattern(conversionPattern);
+ sdf.setLenient(false);
+ return sdf.parse(source);
+ }
+
+ public static Number parseNumber(final String source, final String conversionPattern) throws ParseException {
+ DecimalFormat df = DECIMAL_FORMAT.get();
+ df.applyPattern(conversionPattern);
+ return df.parse(source);
+ }
+
+ public static void clear() {
+ DATE_FORMAT.remove();
+ DECIMAL_FORMAT.remove();
+ }
+
+ private DataFormat() {
+ // private empty constructor
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java
new file mode 100644
index 0000000..903b8ca
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/URIUtil.java
@@ -0,0 +1,61 @@
+/*
+ * 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.utils;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public final class URIUtil {
+
+ private URIUtil() {
+ // empty constructor for static utility class
+ }
+
+ /**
+ * Build a valid URI out of the given location.
+ * Only "file", "connid" and "connids" schemes are allowed.
+ * For "file", invalid characters are handled via intermediate transformation into URL.
+ *
+ * @param location the candidate location for URI
+ * @return valid URI for the given location
+ * @throws MalformedURLException if the intermediate URL is not valid
+ * @throws URISyntaxException if the given location does not correspond to a valid URI
+ */
+ public static URI buildForConnId(final String location) throws MalformedURLException, URISyntaxException {
+ final String candidate = location.trim();
+
+ if (!candidate.startsWith("file:")
+ && !candidate.startsWith("connid:") && !candidate.startsWith("connids:")) {
+
+ throw new IllegalArgumentException(candidate + " is not a valid URI for file or connid(s) schemes");
+ }
+
+ URI uri;
+ if (candidate.startsWith("file:")) {
+ uri = new File(new URL(candidate).getFile()).getAbsoluteFile().toURI();
+ } else {
+ uri = new URI(candidate);
+ }
+
+ return uri;
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/ClassFreeUberspectImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/ClassFreeUberspectImpl.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/ClassFreeUberspectImpl.java
new file mode 100644
index 0000000..15d06dd
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/ClassFreeUberspectImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.utils.jexl;
+
+import org.apache.commons.jexl2.JexlInfo;
+import org.apache.commons.jexl2.introspection.JexlMethod;
+import org.apache.commons.jexl2.introspection.JexlPropertyGet;
+import org.apache.commons.jexl2.introspection.UberspectImpl;
+import org.apache.commons.logging.Log;
+
+class ClassFreeUberspectImpl extends UberspectImpl {
+
+ public ClassFreeUberspectImpl(final Log runtimeLogger) {
+ super(runtimeLogger);
+ }
+
+ @Override
+ public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier, final JexlInfo info) {
+ return "class".equals(identifier) ? null : super.getPropertyGet(obj, identifier, info);
+ }
+
+ @Override
+ public JexlMethod getMethod(final Object obj, final String method, final Object[] args, final JexlInfo info) {
+ return "getClass".equals(method) ? null : super.getMethod(obj, method, args, info);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/EmptyClassLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/EmptyClassLoader.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/EmptyClassLoader.java
new file mode 100644
index 0000000..4564aad
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/EmptyClassLoader.java
@@ -0,0 +1,36 @@
+/*
+ * 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.utils.jexl;
+
+/**
+ * A class loader that will throw {@link ClassNotFoundException} for every class name.
+ */
+class EmptyClassLoader extends ClassLoader {
+
+ @Override
+ public Class<?> loadClass(final String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException("This classloader won't attemp to load " + name);
+ }
+
+ @Override
+ protected Class<?> loadClass(final String name, boolean resolve) throws ClassNotFoundException {
+ throw new ClassNotFoundException("This classloader won't attemp to load " + name);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/JexlUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/JexlUtil.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/jexl/JexlUtil.java
new file mode 100644
index 0000000..d160ef4
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.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.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.DerAttr;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.utils.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyEnforcer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyEnforcer.java
new file mode 100644
index 0000000..0a4bdd3
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.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.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/AccountPolicyException.java
new file mode 100644
index 0000000..7173f0e
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyEnforcer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyEnforcer.java
new file mode 100644
index 0000000..ff0a3f6
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.policy;
+
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PasswordPolicyException.java
new file mode 100644
index 0000000..866e8ff
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforceException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforceException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforceException.java
new file mode 100644
index 0000000..c4d2aac
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforcer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforcer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEnforcer.java
new file mode 100644
index 0000000..b057773
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEvaluator.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEvaluator.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyEvaluator.java
new file mode 100644
index 0000000..58b31b2
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.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.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.Policy;
+import org.apache.syncope.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyException.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyException.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyException.java
new file mode 100644
index 0000000..622d2f0
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyPattern.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyPattern.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/PolicyPattern.java
new file mode 100644
index 0000000..9089995
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.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/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/UserSuspender.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/UserSuspender.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/policy/UserSuspender.java
new file mode 100644
index 0000000..3b4da6a
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/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.utils.policy;
+
+import org.apache.syncope.persistence.api.entity.user.User;
+
+public interface UserSuspender {
+
+ void suspend(User user, boolean propagateSuspension);
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeDeserializer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeDeserializer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeDeserializer.java
new file mode 100644
index 0000000..b1a3790
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeDeserializer.java
@@ -0,0 +1,84 @@
+/*
+ * 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.utils.serialization;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.identityconnectors.common.Base64;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.Name;
+import org.identityconnectors.framework.common.objects.Uid;
+
+class AttributeDeserializer extends JsonDeserializer<Attribute> {
+
+ @Override
+ public Attribute deserialize(final JsonParser jp, final DeserializationContext ctx)
+ throws IOException, JsonProcessingException {
+
+ ObjectNode tree = jp.readValueAsTree();
+
+ String name = tree.get("name").asText();
+
+ List<Object> values = new ArrayList<Object>();
+ for (Iterator<JsonNode> itor = tree.get("value").iterator(); itor.hasNext();) {
+ JsonNode node = itor.next();
+ if (node.isNull()) {
+ values.add(null);
+ } else if (node.isObject()) {
+ values.add(((ObjectNode) node).traverse(jp.getCodec()).readValueAs(GuardedString.class));
+ } else if (node.isBoolean()) {
+ values.add(node.asBoolean());
+ } else if (node.isDouble()) {
+ values.add(node.asDouble());
+ } else if (node.isLong()) {
+ values.add(node.asLong());
+ } else if (node.isInt()) {
+ values.add(node.asInt());
+ } else {
+ String text = node.asText();
+ if (text.startsWith(AttributeSerializer.BYTE_ARRAY_PREFIX)
+ && text.endsWith(AttributeSerializer.BYTE_ARRAY_SUFFIX)) {
+
+ values.add(Base64.decode(StringUtils.substringBetween(
+ text, AttributeSerializer.BYTE_ARRAY_PREFIX, AttributeSerializer.BYTE_ARRAY_SUFFIX)));
+ } else {
+ values.add(text);
+ }
+ }
+ }
+
+ return Uid.NAME.equals(name)
+ ? new Uid(values.isEmpty() || values.get(0) == null ? null : values.get(0).toString())
+ : Name.NAME.equals(name)
+ ? new Name(values.isEmpty() || values.get(0) == null ? null : values.get(0).toString())
+ : AttributeBuilder.build(name, values);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeSerializer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeSerializer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeSerializer.java
new file mode 100644
index 0000000..2a75eec
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/AttributeSerializer.java
@@ -0,0 +1,78 @@
+/*
+ * 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.utils.serialization;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import org.identityconnectors.common.Base64;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class AttributeSerializer extends JsonSerializer<Attribute> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AttributeSerializer.class);
+
+ public static final String BYTE_ARRAY_PREFIX = "<binary>";
+
+ public static final String BYTE_ARRAY_SUFFIX = "</binary>";
+
+ @Override
+ public void serialize(final Attribute source, final JsonGenerator jgen, final SerializerProvider sp)
+ throws IOException, JsonProcessingException {
+
+ jgen.writeStartObject();
+
+ jgen.writeStringField("name", source.getName());
+
+ jgen.writeFieldName("value");
+ if (source.getValue() == null) {
+ jgen.writeNull();
+ } else {
+ jgen.writeStartArray();
+ for (Object value : source.getValue()) {
+ if (value == null) {
+ jgen.writeNull();
+ } else if (value instanceof GuardedString) {
+ jgen.writeObject(value);
+ } else if (value instanceof Integer) {
+ jgen.writeNumber((Integer) value);
+ } else if (value instanceof Long) {
+ jgen.writeNumber((Long) value);
+ } else if (value instanceof Double) {
+ jgen.writeNumber((Double) value);
+ } else if (value instanceof Boolean) {
+ jgen.writeBoolean((Boolean) value);
+ } else if (value instanceof byte[]) {
+ jgen.writeString(BYTE_ARRAY_PREFIX + Base64.encode((byte[]) value) + BYTE_ARRAY_SUFFIX);
+ } else {
+ jgen.writeString(value.toString());
+ }
+ }
+ jgen.writeEndArray();
+ }
+
+ jgen.writeEndObject();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/556d5186/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/GuardedStringDeserializer.java
----------------------------------------------------------------------
diff --git a/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/GuardedStringDeserializer.java b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/GuardedStringDeserializer.java
new file mode 100644
index 0000000..c05c94a
--- /dev/null
+++ b/syncope620/server/utils/src/main/java/org/apache/syncope/server/utils/serialization/GuardedStringDeserializer.java
@@ -0,0 +1,94 @@
+/*
+ * 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.utils.serialization;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import org.identityconnectors.common.Base64;
+import org.identityconnectors.common.security.EncryptorFactory;
+import org.identityconnectors.common.security.GuardedString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class GuardedStringDeserializer extends JsonDeserializer<GuardedString> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(GuardedStringDeserializer.class);
+
+ @Override
+ public GuardedString deserialize(final JsonParser jp, final DeserializationContext ctx)
+ throws IOException, JsonProcessingException {
+
+ ObjectNode tree = jp.readValueAsTree();
+
+ boolean readOnly = false;
+ if (tree.has("readOnly")) {
+ readOnly = tree.get("readOnly").asBoolean();
+ }
+ boolean disposed = false;
+ if (tree.has("disposed")) {
+ disposed = tree.get("disposed").asBoolean();
+ }
+ byte[] encryptedBytes = null;
+ if (tree.has("encryptedBytes")) {
+ encryptedBytes = Base64.decode(tree.get("encryptedBytes").asText());
+ }
+ String base64SHA1Hash = null;
+ if (tree.has("base64SHA1Hash")) {
+ base64SHA1Hash = tree.get("base64SHA1Hash").asText();
+ }
+
+ final byte[] clearBytes = EncryptorFactory.getInstance().getDefaultEncryptor().decrypt(encryptedBytes);
+
+ GuardedString dest = new GuardedString(new String(clearBytes).toCharArray());
+
+ try {
+ Field field = GuardedString.class.getDeclaredField("readOnly");
+ field.setAccessible(true);
+ field.setBoolean(dest, readOnly);
+ } catch (Exception e) {
+ LOG.error("Could not set field value to {}", readOnly, e);
+ }
+
+ try {
+ Field field = GuardedString.class.getDeclaredField("disposed");
+ field.setAccessible(true);
+ field.setBoolean(dest, disposed);
+ } catch (Exception e) {
+ LOG.error("Could not set field value to {}", disposed, e);
+ }
+
+ if (base64SHA1Hash != null) {
+ try {
+ Field field = GuardedString.class.getDeclaredField("base64SHA1Hash");
+ field.setAccessible(true);
+ field.set(dest, base64SHA1Hash);
+ } catch (Exception e) {
+ LOG.error("Could not set field value to {}", base64SHA1Hash, e);
+ }
+ }
+
+ return dest;
+ }
+
+}