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:48 UTC
[43/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/security/SyncopeAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeAuthenticationProvider.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeAuthenticationProvider.java
new file mode 100644
index 0000000..607deb6
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeAuthenticationProvider.java
@@ -0,0 +1,296 @@
+/*
+ * 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.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Set;
+import javax.annotation.Resource;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.persistence.api.dao.ConfDAO;
+import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.core.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttr;
+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.provisioning.api.ConnectorFactory;
+import org.apache.syncope.core.misc.AuditManager;
+import org.apache.syncope.core.misc.MappingUtil;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.transaction.annotation.Transactional;
+
+@Configurable
+public class SyncopeAuthenticationProvider implements AuthenticationProvider {
+
+ /**
+ * Logger.
+ */
+ protected static final Logger LOG = LoggerFactory.getLogger(SyncopeAuthenticationProvider.class);
+
+ @Autowired
+ protected AuditManager auditManager;
+
+ @Autowired
+ protected ConfDAO confDAO;
+
+ @Autowired
+ protected UserDAO userDAO;
+
+ @Autowired
+ protected PolicyDAO policyDAO;
+
+ @Autowired
+ protected ConnectorFactory connFactory;
+
+ @Autowired
+ protected AttributableUtilFactory attrUtilFactory;
+
+ @Resource(name = "adminUser")
+ protected String adminUser;
+
+ @Resource(name = "anonymousUser")
+ protected String anonymousUser;
+
+ protected String adminPassword;
+
+ protected String adminPasswordAlgorithm;
+
+ protected String anonymousKey;
+
+ protected UserDetailsService userDetailsService;
+
+ protected final Encryptor encryptor = Encryptor.getInstance();
+
+ /**
+ * @param adminPassword the adminPassword to set
+ */
+ public void setAdminPassword(final String adminPassword) {
+ this.adminPassword = adminPassword;
+ }
+
+ /**
+ * @param adminPasswordAlgorithm the adminPasswordAlgorithm to set
+ */
+ public void setAdminPasswordAlgorithm(final String adminPasswordAlgorithm) {
+ this.adminPasswordAlgorithm = adminPasswordAlgorithm;
+ }
+
+ /**
+ * @param anonymousKey the anonymousKey to set
+ */
+ public void setAnonymousKey(final String anonymousKey) {
+ this.anonymousKey = anonymousKey;
+ }
+
+ public void setUserDetailsService(final UserDetailsService syncopeUserDetailsService) {
+ this.userDetailsService = syncopeUserDetailsService;
+ }
+
+ @Override
+ @Transactional(noRollbackFor = { BadCredentialsException.class, DisabledException.class })
+ public Authentication authenticate(final Authentication authentication)
+ throws AuthenticationException {
+
+ boolean authenticated = false;
+ User user = null;
+
+ String username = authentication.getName();
+ if (anonymousUser.equals(username)) {
+ authenticated = authentication.getCredentials().toString().equals(anonymousKey);
+ } else if (adminUser.equals(username)) {
+ authenticated = encryptor.verify(
+ authentication.getCredentials().toString(),
+ CipherAlgorithm.valueOf(adminPasswordAlgorithm),
+ adminPassword);
+ } else {
+ user = userDAO.find(username);
+
+ if (user != null) {
+ if (user.isSuspended() != null && user.isSuspended()) {
+ throw new DisabledException("User " + user.getUsername() + " is suspended");
+ }
+
+ CPlainAttr authStatuses = confDAO.find("authentication.statuses");
+ if (authStatuses != null && !authStatuses.getValuesAsStrings().contains(user.getStatus())) {
+ throw new DisabledException("User " + user.getUsername() + " not allowed to authenticate");
+ }
+
+ authenticated = authenticate(user, authentication.getCredentials().toString());
+
+ updateLoginAttributes(user, authenticated);
+ }
+ }
+
+ UsernamePasswordAuthenticationToken token;
+ if (authenticated) {
+ token = new UsernamePasswordAuthenticationToken(
+ authentication.getPrincipal(),
+ null,
+ userDetailsService.loadUserByUsername(authentication.getPrincipal().toString()).getAuthorities());
+
+ token.setDetails(authentication.getDetails());
+
+ auditManager.audit(
+ AuditElements.EventCategoryType.REST,
+ "AuthenticationController",
+ null,
+ "login",
+ Result.SUCCESS,
+ null,
+ authenticated,
+ authentication,
+ "Successfully authenticated, with roles: " + token.getAuthorities());
+
+ LOG.debug("User {} successfully authenticated, with roles {}",
+ authentication.getPrincipal(), token.getAuthorities());
+ } else {
+ auditManager.audit(
+ AuditElements.EventCategoryType.REST,
+ "AuthenticationController",
+ null,
+ "login",
+ Result.FAILURE,
+ null,
+ authenticated,
+ authentication,
+ "User " + authentication.getPrincipal() + " not authenticated");
+
+ LOG.debug("User {} not authenticated", authentication.getPrincipal());
+
+ throw new BadCredentialsException("User " + authentication.getPrincipal() + " not authenticated");
+ }
+
+ return token;
+ }
+
+ protected void updateLoginAttributes(User user, boolean authenticated) {
+ boolean userModified = false;
+
+ if (authenticated) {
+ if (confDAO.find("log.lastlogindate", Boolean.toString(true)).getValues().get(0).getBooleanValue()) {
+ user.setLastLoginDate(new Date());
+ userModified = true;
+ }
+
+ if (user.getFailedLogins() != 0) {
+ user.setFailedLogins(0);
+ userModified = true;
+ }
+ } else {
+ user.setFailedLogins(user.getFailedLogins() + 1);
+ userModified = true;
+ }
+
+ if (userModified) {
+ userDAO.save(user);
+ }
+ }
+
+ protected Set<? extends ExternalResource> getPassthroughResources(final User user) {
+ Set<? extends ExternalResource> result = null;
+
+ // 1. look for directly assigned resources, pick the ones whose account policy has authentication resources
+ for (ExternalResource resource : user.getOwnResources()) {
+ if (resource.getAccountPolicy() != null && !resource.getAccountPolicy().getResources().isEmpty()) {
+ if (result == null) {
+ result = resource.getAccountPolicy().getResources();
+ } else {
+ result.retainAll(resource.getAccountPolicy().getResources());
+ }
+ }
+ }
+
+ // 2. look for owned roles, pick the ones whose account policy has authentication resources
+ for (Role role : user.getRoles()) {
+ if (role.getAccountPolicy() != null && !role.getAccountPolicy().getResources().isEmpty()) {
+ if (result == null) {
+ result = role.getAccountPolicy().getResources();
+ } else {
+ result.retainAll(role.getAccountPolicy().getResources());
+ }
+ }
+ }
+
+ // 3. look for global account policy (if defined)
+ AccountPolicy global = policyDAO.getGlobalAccountPolicy();
+ if (global != null && !global.getResources().isEmpty()) {
+ if (result == null) {
+ result = global.getResources();
+ } else {
+ result.retainAll(global.getResources());
+ }
+ }
+
+ if (result == null) {
+ result = Collections.emptySet();
+ }
+
+ return result;
+ }
+
+ protected boolean authenticate(final User user, final String password) {
+ boolean authenticated = encryptor.verify(password, user.getCipherAlgorithm(), user.getPassword());
+ LOG.debug("{} authenticated on internal storage: {}", user.getUsername(), authenticated);
+
+ final AttributableUtil attrUtil = attrUtilFactory.getInstance(AttributableType.USER);
+ for (Iterator<? extends ExternalResource> itor = getPassthroughResources(user).iterator();
+ itor.hasNext() && !authenticated;) {
+
+ ExternalResource resource = itor.next();
+ String accountId = null;
+ try {
+ accountId = MappingUtil.getAccountIdValue(user, resource, attrUtil.getAccountIdItem(resource));
+ Uid uid = connFactory.getConnector(resource).authenticate(accountId, password, null);
+ if (uid != null) {
+ authenticated = true;
+ }
+ } catch (Exception e) {
+ LOG.debug("Could not authenticate {} on {}", user.getUsername(), resource.getKey(), e);
+ }
+ LOG.debug("{} authenticated on {} as {}: {}",
+ user.getUsername(), resource.getKey(), accountId, authenticated);
+ }
+
+ return authenticated;
+ }
+
+ @Override
+ public boolean supports(final Class<? extends Object> type) {
+ return type.equals(UsernamePasswordAuthenticationToken.class);
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeUserDetailsService.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeUserDetailsService.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeUserDetailsService.java
new file mode 100644
index 0000000..16eecf7
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeUserDetailsService.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.security;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Resource;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.core.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.core.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.Entitlement;
+import org.apache.syncope.core.persistence.api.entity.role.Role;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+@Configurable
+public class SyncopeUserDetailsService implements UserDetailsService {
+
+ @Autowired
+ protected UserDAO userDAO;
+
+ @Autowired
+ protected RoleDAO roleDAO;
+
+ @Autowired
+ protected EntitlementDAO entitlementDAO;
+
+ @Resource(name = "adminUser")
+ protected String adminUser;
+
+ @Resource(name = "anonymousUser")
+ protected String anonymousUser;
+
+ @Override
+ public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException, DataAccessException {
+ final Set<SimpleGrantedAuthority> authorities = new HashSet<>();
+ if (anonymousUser.equals(username)) {
+ authorities.add(new SimpleGrantedAuthority(SyncopeConstants.ANONYMOUS_ENTITLEMENT));
+ } else if (adminUser.equals(username)) {
+ for (Entitlement entitlement : entitlementDAO.findAll()) {
+ authorities.add(new SimpleGrantedAuthority(entitlement.getKey()));
+ }
+ } else {
+ org.apache.syncope.core.persistence.api.entity.user.User user = userDAO.find(username);
+
+ if (user == null) {
+ throw new UsernameNotFoundException("Could not find any user with id " + username);
+ }
+
+ // Give entitlements based on roles assigned to user (and their ancestors)
+ final Set<Role> roles = new HashSet<>(user.getRoles());
+ for (Role role : user.getRoles()) {
+ roles.addAll(roleDAO.findAncestors(role));
+ }
+ for (Role role : roles) {
+ for (Entitlement entitlement : role.getEntitlements()) {
+ authorities.add(new SimpleGrantedAuthority(entitlement.getKey()));
+ }
+ }
+ // Give role operational entitlements for owned roles
+ List<Role> ownedRoles = roleDAO.findOwnedByUser(user.getKey());
+ if (!ownedRoles.isEmpty()) {
+ authorities.add(new SimpleGrantedAuthority("ROLE_CREATE"));
+ authorities.add(new SimpleGrantedAuthority("ROLE_READ"));
+ authorities.add(new SimpleGrantedAuthority("ROLE_UPDATE"));
+ authorities.add(new SimpleGrantedAuthority("ROLE_DELETE"));
+
+ for (Role role : ownedRoles) {
+ authorities.add(new SimpleGrantedAuthority(
+ RoleEntitlementUtil.getEntitlementNameFromRoleKey(role.getKey())));
+ }
+ }
+ }
+
+ return new User(username, "<PASSWORD_PLACEHOLDER>", true, true, true, true, authorities);
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/UnauthorizedRoleException.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/UnauthorizedRoleException.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/UnauthorizedRoleException.java
new file mode 100644
index 0000000..8c29871
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/security/UnauthorizedRoleException.java
@@ -0,0 +1,42 @@
+/*
+ * 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.Collections;
+import java.util.Set;
+
+public class UnauthorizedRoleException extends RuntimeException {
+
+ private static final long serialVersionUID = 7540587364235915081L;
+
+ private final Set<Long> roleIds;
+
+ public UnauthorizedRoleException(final Set<Long> roleIds) {
+ super("Missing entitlement for role(s) " + roleIds);
+ this.roleIds = roleIds;
+ }
+
+ public UnauthorizedRoleException(final Long roleId) {
+ this(Collections.singleton(roleId));
+ }
+
+ public Set<Long> getRoleIds() {
+ return roleIds;
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/AttributeDeserializer.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/AttributeDeserializer.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/AttributeDeserializer.java
new file mode 100644
index 0000000..3da92ee
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/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.core.misc.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/AttributeSerializer.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/AttributeSerializer.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/AttributeSerializer.java
new file mode 100644
index 0000000..2fff021
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/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.core.misc.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/GuardedStringDeserializer.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/GuardedStringDeserializer.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/GuardedStringDeserializer.java
new file mode 100644
index 0000000..d8c372d
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/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.core.misc.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;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/GuardedStringSerializer.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/GuardedStringSerializer.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/GuardedStringSerializer.java
new file mode 100644
index 0000000..de9f60a
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/GuardedStringSerializer.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.misc.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 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 GuardedStringSerializer extends JsonSerializer<GuardedString> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(GuardedStringSerializer.class);
+
+ @Override
+ public void serialize(final GuardedString source, final JsonGenerator jgen, final SerializerProvider sp)
+ throws IOException, JsonProcessingException {
+
+ jgen.writeStartObject();
+
+ boolean readOnly = false;
+ try {
+ Field field = GuardedString.class.getDeclaredField("readOnly");
+ field.setAccessible(true);
+ readOnly = field.getBoolean(source);
+ } catch (Exception e) {
+ LOG.error("Could not get field value", e);
+ }
+ jgen.writeBooleanField("readOnly", readOnly);
+
+ boolean disposed = false;
+ try {
+ Field field = GuardedString.class.getDeclaredField("disposed");
+ field.setAccessible(true);
+ disposed = field.getBoolean(source);
+ } catch (Exception e) {
+ LOG.error("Could not get field value", e);
+ }
+ jgen.writeBooleanField("disposed", disposed);
+
+ final StringBuilder cleartext = new StringBuilder();
+ ((GuardedString) source).access(new GuardedString.Accessor() {
+
+ @Override
+ public void access(final char[] clearChars) {
+ cleartext.append(clearChars);
+ }
+ });
+ final byte[] encryptedBytes =
+ EncryptorFactory.getInstance().getDefaultEncryptor().encrypt(cleartext.toString().getBytes());
+ jgen.writeStringField("encryptedBytes", Base64.encode(encryptedBytes));
+
+ String base64SHA1Hash = null;
+ try {
+ Field field = GuardedString.class.getDeclaredField("base64SHA1Hash");
+ field.setAccessible(true);
+ base64SHA1Hash = field.get(source).toString();
+ } catch (Exception e) {
+ LOG.error("Could not get field value", e);
+ }
+ if (base64SHA1Hash != null) {
+ jgen.writeStringField("base64SHA1Hash", base64SHA1Hash);
+ }
+
+ jgen.writeEndObject();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/POJOHelper.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/POJOHelper.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/POJOHelper.java
new file mode 100644
index 0000000..dd42568
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/POJOHelper.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.misc.serialization;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.SyncToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper class for serialization and deserialization of configuration objects (POJOs) in JSON.
+ */
+public final class POJOHelper {
+
+ private static final Logger LOG = LoggerFactory.getLogger(POJOHelper.class);
+
+ private static final ObjectMapper MAPPER;
+
+ static {
+ SimpleModule pojoModule = new SimpleModule("POJOModule", new Version(1, 0, 0, null, null, null));
+ pojoModule.addSerializer(GuardedString.class, new GuardedStringSerializer());
+ pojoModule.addSerializer(Attribute.class, new AttributeSerializer());
+ pojoModule.addSerializer(SyncToken.class, new SyncTokenSerializer());
+ pojoModule.addDeserializer(GuardedString.class, new GuardedStringDeserializer());
+ pojoModule.addDeserializer(Attribute.class, new AttributeDeserializer());
+ pojoModule.addDeserializer(SyncToken.class, new SyncTokenDeserializer());
+
+ MAPPER = new ObjectMapper();
+ MAPPER.registerModule(pojoModule);
+ MAPPER.registerModule(new AfterburnerModule());
+ }
+
+ public static String serialize(final Object object) {
+ String result = null;
+
+ try {
+ result = MAPPER.writeValueAsString(object);
+ } catch (Exception e) {
+ LOG.error("During serialization", e);
+ }
+
+ return result;
+ }
+
+ public static <T extends Object> T deserialize(final String serialized, final Class<T> reference) {
+ T result = null;
+
+ try {
+ result = MAPPER.readValue(serialized, reference);
+ } catch (Exception e) {
+ LOG.error("During deserialization", e);
+ }
+
+ return result;
+ }
+
+ private POJOHelper() {
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/SyncTokenDeserializer.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/SyncTokenDeserializer.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/SyncTokenDeserializer.java
new file mode 100644
index 0000000..3497373
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/SyncTokenDeserializer.java
@@ -0,0 +1,65 @@
+/*
+ * 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.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 org.apache.commons.codec.binary.Base64;
+import org.identityconnectors.framework.common.objects.SyncToken;
+
+class SyncTokenDeserializer extends JsonDeserializer<SyncToken> {
+
+ @Override
+ public SyncToken deserialize(final JsonParser jp, final DeserializationContext ctx)
+ throws IOException, JsonProcessingException {
+
+ ObjectNode tree = jp.readValueAsTree();
+
+ Object value = null;
+ if (tree.has("value")) {
+ JsonNode node = tree.get("value");
+ value = node.isNull()
+ ? null
+ : node.isBoolean()
+ ? node.asBoolean()
+ : node.isDouble()
+ ? node.asDouble()
+ : node.isLong()
+ ? node.asLong()
+ : node.isInt()
+ ? node.asInt()
+ : node.asText();
+
+ if (value instanceof String) {
+ String base64 = (String) value;
+ if (Base64.isBase64(base64)) {
+ value = Base64.decodeBase64(base64);
+ }
+ }
+ }
+
+ return new SyncToken(value);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/SyncTokenSerializer.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/SyncTokenSerializer.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/SyncTokenSerializer.java
new file mode 100644
index 0000000..c2f2183
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/SyncTokenSerializer.java
@@ -0,0 +1,58 @@
+/*
+ * 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.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.apache.commons.codec.binary.Base64;
+import org.identityconnectors.framework.common.objects.SyncToken;
+
+class SyncTokenSerializer extends JsonSerializer<SyncToken> {
+
+ @Override
+ public void serialize(final SyncToken source, final JsonGenerator jgen, final SerializerProvider sp)
+ throws IOException, JsonProcessingException {
+
+ jgen.writeStartObject();
+
+ jgen.writeFieldName("value");
+
+ if (source.getValue() == null) {
+ jgen.writeNull();
+ } else if (source.getValue() instanceof Boolean) {
+ jgen.writeBoolean((Boolean) source.getValue());
+ } else if (source.getValue() instanceof Double) {
+ jgen.writeNumber((Double) source.getValue());
+ } else if (source.getValue() instanceof Long) {
+ jgen.writeNumber((Long) source.getValue());
+ } else if (source.getValue() instanceof Integer) {
+ jgen.writeNumber((Integer) source.getValue());
+ } else if (source.getValue() instanceof byte[]) {
+ jgen.writeString(Base64.encodeBase64String((byte[]) source.getValue()));
+ } else {
+ jgen.writeString(source.getValue().toString());
+ }
+
+ jgen.writeEndObject();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/UnwrappedObjectMapper.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/UnwrappedObjectMapper.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/UnwrappedObjectMapper.java
new file mode 100644
index 0000000..b79b6bd
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/serialization/UnwrappedObjectMapper.java
@@ -0,0 +1,95 @@
+/*
+ * 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.serialization;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.util.Map;
+
+/**
+ * Jackson ObjectMapper that unwraps singleton map values and enable default
+ * typing for handling abstract types serialization.
+ */
+public class UnwrappedObjectMapper extends ObjectMapper {
+
+ private static final long serialVersionUID = -317191546835195103L;
+
+ /**
+ * Unwraps the given value if it implements the Map interface and contains
+ * only a single entry. Otherwise the value is returned unmodified.
+ *
+ * @param value the potential Map to unwrap
+ * @return the unwrapped map or the original value
+ */
+ private Object unwrapMap(final Object value) {
+ if (value instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) value;
+ if (map.size() == 1) {
+ return map.values().iterator().next();
+ }
+ }
+
+ return value;
+ }
+
+ @Override
+ public void writeValue(final JsonGenerator jgen, final Object value)
+ throws IOException, JsonGenerationException, JsonMappingException {
+
+ super.writeValue(jgen, unwrapMap(value));
+ }
+
+ @Override
+ public void writeValue(final File resultFile, final Object value)
+ throws IOException, JsonGenerationException, JsonMappingException {
+
+ super.writeValue(resultFile, unwrapMap(value));
+ }
+
+ @Override
+ public void writeValue(final OutputStream out, final Object value)
+ throws IOException, JsonGenerationException, JsonMappingException {
+
+ super.writeValue(out, unwrapMap(value));
+ }
+
+ @Override
+ public void writeValue(final Writer w, final Object value)
+ throws IOException, JsonGenerationException, JsonMappingException {
+
+ super.writeValue(w, unwrapMap(value));
+ }
+
+ @Override
+ public byte[] writeValueAsBytes(final Object value) throws JsonProcessingException {
+ return super.writeValueAsBytes(unwrapMap(value));
+ }
+
+ @Override
+ public String writeValueAsString(final Object value) throws JsonProcessingException {
+ return super.writeValueAsString(unwrapMap(value));
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/ApplicationContextProvider.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/ApplicationContextProvider.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/ApplicationContextProvider.java
new file mode 100644
index 0000000..dfbcbe4
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/ApplicationContextProvider.java
@@ -0,0 +1,47 @@
+/*
+ * 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.spring;
+
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+
+public class ApplicationContextProvider implements ApplicationContextAware {
+
+ private static ConfigurableApplicationContext ctx;
+
+ public static ConfigurableApplicationContext getApplicationContext() {
+ return ctx;
+ }
+
+ public static DefaultListableBeanFactory getBeanFactory() {
+ return (DefaultListableBeanFactory) ctx.getBeanFactory();
+ }
+
+ /**
+ * Wiring the ApplicationContext into a static method.
+ *
+ * @param ctx Spring application context
+ */
+ @Override
+ public void setApplicationContext(final ApplicationContext ctx) {
+ ApplicationContextProvider.ctx = (ConfigurableApplicationContext) ctx;
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/BeanUtils.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/BeanUtils.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/BeanUtils.java
new file mode 100644
index 0000000..97b57db
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/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.core.misc.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/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/ResourceWithFallbackLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/ResourceWithFallbackLoader.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/spring/ResourceWithFallbackLoader.java
new file mode 100644
index 0000000..0fdc116
--- /dev/null
+++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/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.core.misc.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/d30c8526/syncope620/core/misc/src/main/resources/security.properties
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/resources/security.properties b/syncope620/core/misc/src/main/resources/security.properties
new file mode 100644
index 0000000..c0fcd37
--- /dev/null
+++ b/syncope620/core/misc/src/main/resources/security.properties
@@ -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.
+adminUser=admin
+adminPassword=5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
+adminPasswordAlgorithm=SHA1
+
+anonymousUser=${anonymousUser}
+anonymousKey=${anonymousKey}
+
+secretKey=${secretKey}
+# default for LDAP / RFC2307 SSHA
+digester.saltIterations=1
+digester.saltSizeBytes=8
+digester.invertPositionOfPlainSaltInEncryptionResults=true
+digester.invertPositionOfSaltInMessageBeforeDigesting=true
+digester.useLenientSaltSizeCheck=true
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/resources/securityContext.xml
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/main/resources/securityContext.xml b/syncope620/core/misc/src/main/resources/securityContext.xml
new file mode 100644
index 0000000..a835970
--- /dev/null
+++ b/syncope620/core/misc/src/main/resources/securityContext.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:security="http://www.springframework.org/schema/security"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/security
+ http://www.springframework.org/schema/security/spring-security.xsd">
+
+ <bean id="adminUser" class="java.lang.String">
+ <constructor-arg value="${adminUser}"/>
+ </bean>
+ <bean id="anonymousUser" class="java.lang.String">
+ <constructor-arg value="${anonymousUser}"/>
+ </bean>
+
+ <security:global-method-security pre-post-annotations="enabled"/>
+
+ <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
+ <security:filter-chain-map path-type="ant">
+ <security:filter-chain pattern="/**" filters="securityContextPersistenceFilter"/>
+ </security:filter-chain-map>
+ </bean>
+
+ <bean id="securityContextRepository" class='org.springframework.security.web.context.NullSecurityContextRepository'/>
+
+ <bean id="securityContextPersistenceFilter"
+ class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
+ <property name="securityContextRepository" ref="securityContextRepository"/>
+ </bean>
+
+ <security:http security-context-repository-ref="securityContextRepository" realm="Apache Syncope authentication">
+ <security:http-basic/>
+ <security:anonymous username="${anonymousUser}"/>
+ <security:intercept-url pattern="/**"/>
+ </security:http>
+
+ <bean id="syncopeUserDetailsService" class="org.apache.syncope.core.misc.security.SyncopeUserDetailsService"/>
+
+ <bean id="syncopeAuthenticationProvider" class="org.apache.syncope.core.misc.security.SyncopeAuthenticationProvider">
+ <property name="adminPassword" value="${adminPassword}"/>
+ <property name="adminPasswordAlgorithm" value="${adminPasswordAlgorithm}"/>
+ <property name="anonymousKey" value="${anonymousKey}"/>
+ <property name="userDetailsService" ref="syncopeUserDetailsService"/>
+ </bean>
+
+ <security:authentication-manager>
+ <security:authentication-provider ref="syncopeAuthenticationProvider"/>
+ </security:authentication-manager>
+</beans>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/search/SearchCondConverterTest.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/search/SearchCondConverterTest.java b/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/search/SearchCondConverterTest.java
new file mode 100644
index 0000000..27cd0b6
--- /dev/null
+++ b/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/search/SearchCondConverterTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import org.apache.syncope.common.lib.search.RoleFiqlSearchConditionBuilder;
+import org.apache.syncope.common.lib.search.SpecialAttr;
+import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder;
+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;
+import org.junit.Test;
+
+public class SearchCondConverterTest {
+
+ @Test
+ public void eq() {
+ String fiqlExpression = new UserFiqlSearchConditionBuilder().is("username").equalTo("rossini").query();
+ assertEquals("username==rossini", fiqlExpression);
+
+ SubjectCond attrCond = new SubjectCond(AttributeCond.Type.EQ);
+ attrCond.setSchema("username");
+ attrCond.setExpression("rossini");
+ SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+
+ assertEquals(simpleCond, SearchCondConverter.convert(fiqlExpression));
+ }
+
+ @Test
+ public void like() {
+ String fiqlExpression = new UserFiqlSearchConditionBuilder().is("username").equalTo("ros*").query();
+ assertEquals("username==ros*", fiqlExpression);
+
+ AttributeCond attrCond = new SubjectCond(AttributeCond.Type.LIKE);
+ attrCond.setSchema("username");
+ attrCond.setExpression("ros%");
+ SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+
+ assertEquals(simpleCond, SearchCondConverter.convert(fiqlExpression));
+ }
+
+ @Test
+ public void isNull() {
+ String fiqlExpression = new UserFiqlSearchConditionBuilder().is("loginDate").nullValue().query();
+ assertEquals("loginDate==" + SpecialAttr.NULL, fiqlExpression);
+
+ AttributeCond attrCond = new AttributeCond(AttributeCond.Type.ISNULL);
+ attrCond.setSchema("loginDate");
+ SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+
+ assertEquals(simpleCond, SearchCondConverter.convert(fiqlExpression));
+ }
+
+ @Test
+ public void isNotNull() {
+ String fiqlExpression = new UserFiqlSearchConditionBuilder().is("loginDate").notNullValue().query();
+ assertEquals("loginDate!=" + SpecialAttr.NULL, fiqlExpression);
+
+ AttributeCond attrCond = new AttributeCond(AttributeCond.Type.ISNOTNULL);
+ attrCond.setSchema("loginDate");
+ SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+
+ assertEquals(simpleCond, SearchCondConverter.convert(fiqlExpression));
+ }
+
+ @Test
+ public void roles() {
+ String fiqlExpression = new UserFiqlSearchConditionBuilder().hasRoles(1L).query();
+ assertEquals(SpecialAttr.ROLES + "==1", fiqlExpression);
+
+ MembershipCond membCond = new MembershipCond();
+ membCond.setRoleId(1L);
+ SearchCond simpleCond = SearchCond.getLeafCond(membCond);
+
+ assertEquals(simpleCond, SearchCondConverter.convert(fiqlExpression));
+ }
+
+ @Test
+ public void resources() {
+ String fiqlExpression = new UserFiqlSearchConditionBuilder().hasResources("resource-ldap").query();
+ assertEquals(SpecialAttr.RESOURCES + "==resource-ldap", fiqlExpression);
+
+ ResourceCond resCond = new ResourceCond();
+ resCond.setResourceName("resource-ldap");
+ SearchCond simpleCond = SearchCond.getLeafCond(resCond);
+
+ assertEquals(simpleCond, SearchCondConverter.convert(fiqlExpression));
+ }
+
+ @Test
+ public void entitlements() {
+ String fiqlExpression = new RoleFiqlSearchConditionBuilder().hasEntitlements("USER_LIST").query();
+ assertEquals(SpecialAttr.ENTITLEMENTS + "==USER_LIST", fiqlExpression);
+
+ EntitlementCond entCond = new EntitlementCond();
+ entCond.setExpression("USER_LIST");
+ SearchCond simpleCond = SearchCond.getLeafCond(entCond);
+
+ assertEquals(simpleCond, SearchCondConverter.convert(fiqlExpression));
+ }
+
+ @Test
+ public void and() {
+ String fiqlExpression = new UserFiqlSearchConditionBuilder().
+ is("fullname").equalTo("*o*").and("fullname").equalTo("*i*").query();
+ assertEquals("fullname==*o*;fullname==*i*", fiqlExpression);
+
+ AttributeCond fullnameLeafCond1 = new AttributeCond(AttributeCond.Type.LIKE);
+ fullnameLeafCond1.setSchema("fullname");
+ fullnameLeafCond1.setExpression("%o%");
+ AttributeCond fullnameLeafCond2 = new AttributeCond(AttributeCond.Type.LIKE);
+ fullnameLeafCond2.setSchema("fullname");
+ fullnameLeafCond2.setExpression("%i%");
+ SearchCond andCond = SearchCond.getAndCond(
+ SearchCond.getLeafCond(fullnameLeafCond1),
+ SearchCond.getLeafCond(fullnameLeafCond2));
+
+ assertEquals(andCond, SearchCondConverter.convert(fiqlExpression));
+ }
+
+ @Test
+ public void or() {
+ String fiqlExpression = new UserFiqlSearchConditionBuilder().
+ is("fullname").equalTo("*o*", "*i*", "*ini").query();
+ assertEquals("fullname==*o*,fullname==*i*,fullname==*ini", fiqlExpression);
+
+ AttributeCond fullnameLeafCond1 = new AttributeCond(AttributeCond.Type.LIKE);
+ fullnameLeafCond1.setSchema("fullname");
+ fullnameLeafCond1.setExpression("%o%");
+ AttributeCond fullnameLeafCond2 = new AttributeCond(AttributeCond.Type.LIKE);
+ fullnameLeafCond2.setSchema("fullname");
+ fullnameLeafCond2.setExpression("%i%");
+ AttributeCond fullnameLeafCond3 = new AttributeCond(AttributeCond.Type.LIKE);
+ fullnameLeafCond3.setSchema("fullname");
+ fullnameLeafCond3.setExpression("%ini");
+ SearchCond orCond = SearchCond.getOrCond(
+ SearchCond.getLeafCond(fullnameLeafCond1),
+ SearchCond.getOrCond(
+ SearchCond.getLeafCond(fullnameLeafCond2),
+ SearchCond.getLeafCond(fullnameLeafCond3)));
+
+ assertEquals(orCond, SearchCondConverter.convert(fiqlExpression));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/security/EncryptorTest.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/security/EncryptorTest.java b/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/security/EncryptorTest.java
new file mode 100644
index 0000000..98e8061
--- /dev/null
+++ b/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/security/EncryptorTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.junit.Test;
+
+/**
+ * Test class to test all encryption algorithms.
+ */
+public class EncryptorTest {
+
+ private final String password = "password";
+
+ private final Encryptor encryptor = Encryptor.getInstance();
+
+ /**
+ * Verify all algorithms.
+ */
+ @Test
+ public void testEncoder() throws Exception {
+ for (CipherAlgorithm cipherAlgorithm : CipherAlgorithm.values()) {
+ final String encPassword = encryptor.encode(password, cipherAlgorithm);
+
+ assertNotNull(encPassword);
+ assertTrue(encryptor.verify(password, cipherAlgorithm, encPassword));
+ assertFalse(encryptor.verify("pass", cipherAlgorithm, encPassword));
+
+ // check that same password encoded with BCRYPT or Salted versions results in different digest
+ if (cipherAlgorithm.equals(CipherAlgorithm.BCRYPT) || cipherAlgorithm.getAlgorithm().startsWith("S-")) {
+ final String encSamePassword = encryptor.encode(password, cipherAlgorithm);
+ assertNotNull(encSamePassword);
+ assertFalse(encSamePassword.equals(encPassword));
+ assertTrue(encryptor.verify(password, cipherAlgorithm, encSamePassword));
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/security/PasswordGeneratorTest.java
----------------------------------------------------------------------
diff --git a/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/security/PasswordGeneratorTest.java b/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/security/PasswordGeneratorTest.java
new file mode 100644
index 0000000..9c959b3
--- /dev/null
+++ b/syncope620/core/misc/src/test/java/org/apache/syncope/core/misc/security/PasswordGeneratorTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.syncope.common.lib.types.PasswordPolicySpec;
+import org.apache.syncope.core.misc.policy.InvalidPasswordPolicySpecException;
+import org.apache.syncope.core.misc.policy.PolicyPattern;
+import org.junit.Test;
+
+public class PasswordGeneratorTest {
+
+ private final PasswordGenerator passwordGenerator = new PasswordGenerator();
+
+ @Test
+ public void startEndWithDigit()
+ throws InvalidPasswordPolicySpecException {
+
+ PasswordPolicySpec passwordPolicySpec = createBasePasswordPolicySpec();
+ passwordPolicySpec.setMustStartWithDigit(true);
+
+ PasswordPolicySpec passwordPolicySpec2 = createBasePasswordPolicySpec();
+ passwordPolicySpec.setMustEndWithDigit(true);
+ List<PasswordPolicySpec> passwordPolicySpecs = new ArrayList<>();
+ passwordPolicySpecs.add(passwordPolicySpec);
+ passwordPolicySpecs.add(passwordPolicySpec2);
+ String generatedPassword = passwordGenerator.generate(passwordPolicySpecs);
+ assertTrue(Character.isDigit(generatedPassword.charAt(0)));
+ assertTrue(Character.isDigit(generatedPassword.charAt(generatedPassword.length() - 1)));
+ }
+
+ @Test
+ public void startWithDigitAndWithAlpha()
+ throws InvalidPasswordPolicySpecException {
+
+ PasswordPolicySpec passwordPolicySpec = createBasePasswordPolicySpec();
+ passwordPolicySpec.setMustStartWithDigit(true);
+
+ PasswordPolicySpec passwordPolicySpec2 = createBasePasswordPolicySpec();
+ passwordPolicySpec.setMustEndWithAlpha(true);
+ List<PasswordPolicySpec> passwordPolicySpecs = new ArrayList<>();
+ passwordPolicySpecs.add(passwordPolicySpec);
+ passwordPolicySpecs.add(passwordPolicySpec2);
+ String generatedPassword = passwordGenerator.generate(passwordPolicySpecs);
+ assertTrue(Character.isDigit(generatedPassword.charAt(0)));
+ assertTrue(Character.isLetter(generatedPassword.charAt(generatedPassword.length() - 1)));
+ }
+
+ @Test
+ public void passwordWithNonAlpha()
+ throws InvalidPasswordPolicySpecException {
+
+ PasswordPolicySpec passwordPolicySpec = createBasePasswordPolicySpec();
+ passwordPolicySpec.setNonAlphanumericRequired(true);
+
+ PasswordPolicySpec passwordPolicySpec2 = createBasePasswordPolicySpec();
+ passwordPolicySpec.setMustEndWithAlpha(true);
+ List<PasswordPolicySpec> passwordPolicySpecs = new ArrayList<>();
+ passwordPolicySpecs.add(passwordPolicySpec);
+ passwordPolicySpecs.add(passwordPolicySpec2);
+ String generatedPassword = passwordGenerator.generate(passwordPolicySpecs);
+ assertTrue(PolicyPattern.NON_ALPHANUMERIC.matcher(generatedPassword).matches());
+ assertTrue(Character.isLetter(generatedPassword.charAt(generatedPassword.length() - 1)));
+ }
+
+ @Test(expected = InvalidPasswordPolicySpecException.class)
+ public void incopatiblePolicies()
+ throws InvalidPasswordPolicySpecException {
+
+ PasswordPolicySpec passwordPolicySpec = createBasePasswordPolicySpec();
+ passwordPolicySpec.setMinLength(12);
+
+ PasswordPolicySpec passwordPolicySpec2 = createBasePasswordPolicySpec();
+ passwordPolicySpec.setMaxLength(10);
+
+ List<PasswordPolicySpec> passwordPolicySpecs = new ArrayList<>();
+ passwordPolicySpecs.add(passwordPolicySpec);
+ passwordPolicySpecs.add(passwordPolicySpec2);
+ passwordGenerator.generate(passwordPolicySpecs);
+ }
+
+ private PasswordPolicySpec createBasePasswordPolicySpec() {
+ PasswordPolicySpec basePasswordPolicySpec = new PasswordPolicySpec();
+ basePasswordPolicySpec.setAlphanumericRequired(false);
+ basePasswordPolicySpec.setDigitRequired(false);
+ basePasswordPolicySpec.setLowercaseRequired(false);
+ basePasswordPolicySpec.setMaxLength(1000);
+ basePasswordPolicySpec.setMinLength(8);
+ basePasswordPolicySpec.setMustEndWithAlpha(false);
+ basePasswordPolicySpec.setMustEndWithDigit(false);
+ basePasswordPolicySpec.setMustEndWithNonAlpha(false);
+ basePasswordPolicySpec.setMustStartWithAlpha(false);
+ basePasswordPolicySpec.setMustStartWithDigit(false);
+ basePasswordPolicySpec.setMustStartWithNonAlpha(false);
+ basePasswordPolicySpec.setMustntEndWithAlpha(false);
+ basePasswordPolicySpec.setMustntEndWithDigit(false);
+ basePasswordPolicySpec.setMustntEndWithNonAlpha(false);
+ basePasswordPolicySpec.setMustntStartWithAlpha(false);
+ basePasswordPolicySpec.setMustntStartWithDigit(false);
+ basePasswordPolicySpec.setMustntStartWithNonAlpha(false);
+ basePasswordPolicySpec.setNonAlphanumericRequired(false);
+ basePasswordPolicySpec.setUppercaseRequired(false);
+ return basePasswordPolicySpec;
+ }
+}