You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by fm...@apache.org on 2013/01/03 11:13:01 UTC

svn commit: r1428262 - in /syncope/branches/1_0_X/core/src: main/java/org/apache/syncope/core/persistence/beans/ main/java/org/apache/syncope/core/propagation/ main/java/org/apache/syncope/core/util/ test/java/org/apache/syncope/core/rest/

Author: fmartelli
Date: Thu Jan  3 10:13:01 2013
New Revision: 1428262

URL: http://svn.apache.org/viewvc?rev=1428262&view=rev
Log:
Fixes SYNCOPE-260

Added:
    syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/Attributable.java   (with props)
Modified:
    syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/AbstractAttributable.java
    syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java
    syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java
    syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java

Modified: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/AbstractAttributable.java
URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/AbstractAttributable.java?rev=1428262&r1=1428261&r2=1428262&view=diff
==============================================================================
--- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/AbstractAttributable.java (original)
+++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/AbstractAttributable.java Thu Jan  3 10:13:01 2013
@@ -21,11 +21,10 @@ package org.apache.syncope.core.persiste
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-public abstract class AbstractAttributable extends AbstractBaseBean {
+public abstract class AbstractAttributable extends AbstractBaseBean implements Attributable {
 
     private static final long serialVersionUID = -4801685541488201119L;
 
@@ -108,32 +107,6 @@ public abstract class AbstractAttributab
         return map;
     }
 
-    public abstract Long getId();
-
-    public abstract <T extends AbstractAttr> boolean addAttribute(T attribute);
-
-    public abstract <T extends AbstractAttr> boolean removeAttribute(T attribute);
-
-    public abstract List<? extends AbstractAttr> getAttributes();
-
-    public abstract void setAttributes(List<? extends AbstractAttr> attributes);
-
-    public abstract <T extends AbstractDerAttr> boolean addDerivedAttribute(T derivedAttribute);
-
-    public abstract <T extends AbstractDerAttr> boolean removeDerivedAttribute(T derivedAttribute);
-
-    public abstract List<? extends AbstractDerAttr> getDerivedAttributes();
-
-    public abstract void setDerivedAttributes(List<? extends AbstractDerAttr> derivedAttributes);
-
-    public abstract <T extends AbstractVirAttr> boolean addVirtualAttribute(T virtualAttributes);
-
-    public abstract <T extends AbstractVirAttr> boolean removeVirtualAttribute(T virtualAttribute);
-
-    public abstract List<? extends AbstractVirAttr> getVirtualAttributes();
-
-    public abstract void setVirtualAttributes(List<? extends AbstractVirAttr> virtualAttributes);
-
     protected abstract Set<ExternalResource> resources();
 
     public boolean addResource(final ExternalResource resource) {

Added: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/Attributable.java
URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/Attributable.java?rev=1428262&view=auto
==============================================================================
--- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/Attributable.java (added)
+++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/Attributable.java Thu Jan  3 10:13:01 2013
@@ -0,0 +1,71 @@
+/*
+ * 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.persistence.beans;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Entity interface. It has been introduced to specify a dynamic proxy for AbstractAttributable instances in order to 
+ * manage virtual attributes during propagation.
+ */
+public interface Attributable {
+
+    <T extends AbstractAttr> T getAttribute(final String schemaName);
+
+    <T extends AbstractDerAttr> T getDerivedAttribute(final String derivedSchemaName);
+
+    <T extends AbstractVirAttr> T getVirtualAttribute(final String virtualSchemaName);
+
+    Long getId();
+
+    <T extends AbstractAttr> boolean addAttribute(T attribute);
+
+    <T extends AbstractAttr> boolean removeAttribute(T attribute);
+
+    List<? extends AbstractAttr> getAttributes();
+
+    void setAttributes(List<? extends AbstractAttr> attributes);
+
+    <T extends AbstractDerAttr> boolean addDerivedAttribute(T derivedAttribute);
+
+    <T extends AbstractDerAttr> boolean removeDerivedAttribute(T derivedAttribute);
+
+    List<? extends AbstractDerAttr> getDerivedAttributes();
+
+    void setDerivedAttributes(List<? extends AbstractDerAttr> derivedAttributes);
+
+    <T extends AbstractVirAttr> boolean addVirtualAttribute(T virtualAttributes);
+
+    <T extends AbstractVirAttr> boolean removeVirtualAttribute(T virtualAttribute);
+
+    List<? extends AbstractVirAttr> getVirtualAttributes();
+
+    void setVirtualAttributes(List<? extends AbstractVirAttr> virtualAttributes);
+
+    boolean addResource(final ExternalResource resource);
+
+    boolean removeResource(final ExternalResource resource);
+
+    Set<ExternalResource> getResources();
+
+    Set<String> getResourceNames();
+
+    void setResources(final Set<ExternalResource> resources);
+}

Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/Attributable.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/Attributable.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/persistence/beans/Attributable.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java
URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java?rev=1428262&r1=1428261&r2=1428262&view=diff
==============================================================================
--- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java (original)
+++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java Thu Jan  3 10:13:01 2013
@@ -19,10 +19,15 @@
 package org.apache.syncope.core.propagation;
 
 import java.io.PrintWriter;
+import java.io.Serializable;
 import java.io.StringWriter;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -34,8 +39,9 @@ import org.apache.syncope.client.mod.Att
 import org.apache.syncope.client.to.AttributeTO;
 import org.apache.syncope.core.init.ConnInstanceLoader;
 import org.apache.syncope.core.persistence.beans.AbstractAttrValue;
-import org.apache.syncope.core.persistence.beans.AbstractAttributable;
 import org.apache.syncope.core.persistence.beans.AbstractSchema;
+import org.apache.syncope.core.persistence.beans.AbstractVirAttr;
+import org.apache.syncope.core.persistence.beans.Attributable;
 import org.apache.syncope.core.persistence.beans.ExternalResource;
 import org.apache.syncope.core.persistence.beans.PropagationTask;
 import org.apache.syncope.core.persistence.beans.SchemaMapping;
@@ -284,7 +290,15 @@ public class PropagationManager {
             localPropByRes.get(PropagationOperation.DELETE).removeAll(syncResourceNames);
         }
 
-        return provision(user, password, enable, false, localPropByRes);
+        // Provide a proxy handler in order to take into consideration all the info about virtual attributes to
+        // be removed/updated as well.
+
+        final Attributable handleObject = (Attributable) Proxy.newProxyInstance(
+                Attributable.class.getClassLoader(),
+                new Class<?>[]{Attributable.class, Serializable.class},
+                new AttributableHandler(user, vAttrsToBeRemoved, vAttrsToBeUpdated));
+
+        return provision(handleObject, password, enable, false, localPropByRes);
     }
 
     /**
@@ -335,24 +349,29 @@ public class PropagationManager {
      * @return account link + prepare attributes
      * @throws ClassNotFoundException if schema type for given mapping does not exists in current class loader
      */
-    private Map.Entry<String, Attribute> prepareAttribute(final SchemaMapping mapping, final SyncopeUser user,
-            final String password)
+    private Map.Entry<String, Attribute> prepareAttribute(
+            final SchemaMapping mapping, final Attributable user, final String password)
             throws ClassNotFoundException {
 
-        final List<AbstractAttributable> attributables = new ArrayList<AbstractAttributable>();
+        // Retrieve attributable ...
+        final Attributable attributable = user instanceof Proxy
+                ? ((AttributableHandler) Proxy.getInvocationHandler(user)).getObject()
+                : user;
+        
+        final List<Attributable> attributables = new ArrayList<Attributable>();
 
         switch (mapping.getIntMappingType().getAttributableType()) {
             case USER:
                 attributables.addAll(Collections.singleton(user));
                 break;
             case ROLE:
-                final List<Membership> memberships = user.getMemberships();
+                final List<Membership> memberships = ((SyncopeUser) attributable).getMemberships();
                 for (Membership membership : memberships) {
                     attributables.add(membership.getSyncopeRole());
                 }
                 break;
             case MEMBERSHIP:
-                attributables.addAll(user.getMemberships());
+                attributables.addAll(((SyncopeUser) attributable).getMemberships());
                 break;
             default:
         }
@@ -416,11 +435,16 @@ public class PropagationManager {
      * @param resource target resource
      * @return account link + prepared attributes
      */
-    private Map.Entry<String, Set<Attribute>> prepareAttributes(final SyncopeUser user, final String password,
+    private Map.Entry<String, Set<Attribute>> prepareAttributes(final Attributable user, final String password,
             final Boolean enable, final ExternalResource resource) {
 
+        // Retrieve attributable ...
+        final Attributable attributable = user instanceof Proxy
+                ? ((AttributableHandler) Proxy.getInvocationHandler(user)).getObject()
+                : user;
+
         LOG.debug("Preparing resource attributes for {} on resource {} with attributes {}",
-                new Object[]{user, resource, user.getAttributes()});
+                new Object[]{attributable, resource, attributable.getAttributes()});
 
         Set<Attribute> attributes = new HashSet<Attribute>();
         String accountId = null;
@@ -437,8 +461,8 @@ public class PropagationManager {
                 }
 
                 if (preparedAttribute.getValue() != null) {
-                    final Attribute alreadyAdded = AttributeUtil.find(preparedAttribute.getValue().getName(),
-                            attributes);
+                    final Attribute alreadyAdded =
+                            AttributeUtil.find(preparedAttribute.getValue().getName(), attributes);
 
                     if (alreadyAdded == null) {
                         attributes.add(preparedAttribute.getValue());
@@ -464,7 +488,7 @@ public class PropagationManager {
         }
 
         // Evaluate AccountLink expression
-        String evalAccountLink = jexlUtil.evaluate(resource.getAccountLink(), user);
+        String evalAccountLink = jexlUtil.evaluate(resource.getAccountLink(), (SyncopeUser) attributable);
 
         // AccountId must be propagated. It could be a simple attribute for
         // the target resource or the key (depending on the accountLink)
@@ -498,10 +522,18 @@ public class PropagationManager {
      * @param propByRes operation to be performed per resource
      * @return list of propagation tasks created
      */
-    protected List<PropagationTask> provision(final SyncopeUser user, final String password, final Boolean enable,
+    protected List<PropagationTask> provision(
+            final Attributable user,
+            final String password,
+            final Boolean enable,
             final boolean deleteOnResource, final PropagationByResource propByRes) {
 
-        LOG.debug("Provisioning with user {}:\n{}", user, propByRes);
+        // Retrieve attributable ...
+        final Attributable attributable = user instanceof Proxy
+                ? ((AttributableHandler) Proxy.getInvocationHandler(user)).getObject()
+                : user;
+
+        LOG.debug("Provisioning with user {}:\n{}", attributable, propByRes);
 
         // Avoid duplicates - see javadoc
         propByRes.purge();
@@ -521,13 +553,14 @@ public class PropagationManager {
                 PropagationTask task = new PropagationTask();
                 task.setResource(resource);
                 if (!deleteOnResource) {
-                    task.setSyncopeUser(user);
+                    task.setSyncopeUser((SyncopeUser) attributable);
                 }
                 task.setPropagationOperation(operation);
                 task.setPropagationMode(resource.getPropagationMode());
                 task.setOldAccountId(propByRes.getOldAccountId(resource.getName()));
 
                 Map.Entry<String, Set<Attribute>> preparedAttrs = prepareAttributes(user, password, enable, resource);
+
                 task.setAccountId(preparedAttrs.getKey());
                 task.setAttributes(preparedAttrs.getValue());
 
@@ -715,19 +748,18 @@ public class PropagationManager {
                             LOG.debug("{} not found on external resource: ignoring delete", task.getAccountId());
                         } else {
                             /*
-                             * We must choose here whether to
-                             *  a. actually delete the provided user from the external resource
-                             *  b. just update the provided user data onto the external resource
+                             * We must choose here whether to a. actually delete the provided user from the external
+                             * resource b. just update the provided user data onto the external resource
                              *
                              * (a) happens when either there is no user associated with the PropagationTask (this takes
                              * place when the task is generated via UserController.delete()) or the provided updated
                              * user hasn't the current resource assigned (when the task is generated via
                              * UserController.update()).
                              *
-                             * (b) happens when the provided updated user does have the current resource assigned
-                             * (when the task is generated via UserController.update()): this basically means that
-                             * before such update, this user used to have the current resource assigned by more than
-                             * one mean (for example, two different memberships with the same resource).
+                             * (b) happens when the provided updated user does have the current resource assigned (when
+                             * the task is generated via UserController.update()): this basically means that before such
+                             * update, this user used to have the current resource assigned by more than one mean (for
+                             * example, two different memberships with the same resource).
                              */
 
                             SyncopeUser user = null;
@@ -860,4 +892,55 @@ public class PropagationManager {
             return null;
         }
     }
+
+    public static class AttributableHandler implements InvocationHandler {
+
+        private Attributable object;
+
+        private Set<String> vAttrsToBeRemoved;
+
+        private Map<String, AttributeMod> vAttrsToBeUpdated;
+
+        public AttributableHandler(
+                final Attributable object,
+                final Set<String> vAttrsToBeRemoved,
+                final Set<AttributeMod> vAttrsToBeUpdated) {
+            this.object = object;
+            this.vAttrsToBeRemoved = vAttrsToBeRemoved;
+
+            if (vAttrsToBeUpdated != null) {
+                this.vAttrsToBeUpdated = new HashMap<String, AttributeMod>(vAttrsToBeUpdated.size());
+
+                for (AttributeMod attrMod : vAttrsToBeUpdated) {
+                    this.vAttrsToBeUpdated.put(attrMod.getSchema(), attrMod);
+                }
+            } else {
+                this.vAttrsToBeUpdated = Collections.EMPTY_MAP;
+            }
+        }
+
+        @Override
+        public Object invoke(final Object proxy, final Method method, final Object[] args)
+                throws Throwable {
+            if ("getVirtualAttribute".equals(method.getName()) && args.length == 1 && (args[0] instanceof String)) {
+                final AbstractVirAttr attr = object.getVirtualAttribute((String) args[0]);
+
+                if (vAttrsToBeUpdated.containsKey((String) args[0])) {
+                    attr.setValues(vAttrsToBeUpdated.get((String) args[0]).getValuesToBeAdded());
+                } else if (vAttrsToBeRemoved.contains((String) args[0])) {
+                    attr.getValues().clear();
+                } else {
+                    throw new RuntimeException("Virtual attribute has not to be updated");
+                }
+
+                return attr;
+            } else {
+                return method.invoke(object, args);
+            }
+        }
+
+        public Attributable getObject() {
+            return object;
+        }
+    }
 }

Modified: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java
URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java?rev=1428262&r1=1428261&r2=1428262&view=diff
==============================================================================
--- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java (original)
+++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java Thu Jan  3 10:13:01 2013
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.util;
 
+import java.lang.reflect.Proxy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -33,6 +34,7 @@ import org.apache.syncope.core.persisten
 import org.apache.syncope.core.persistence.beans.AbstractDerAttr;
 import org.apache.syncope.core.persistence.beans.AbstractSchema;
 import org.apache.syncope.core.persistence.beans.AbstractVirAttr;
+import org.apache.syncope.core.persistence.beans.Attributable;
 import org.apache.syncope.core.persistence.beans.SchemaMapping;
 import org.apache.syncope.core.persistence.beans.membership.MDerSchema;
 import org.apache.syncope.core.persistence.beans.membership.MSchema;
@@ -46,11 +48,13 @@ import org.apache.syncope.core.persisten
 import org.apache.syncope.core.persistence.beans.user.USchema;
 import org.apache.syncope.core.persistence.beans.user.UVirSchema;
 import org.apache.syncope.core.persistence.dao.SchemaDAO;
+import org.apache.syncope.core.propagation.PropagationManager.AttributableHandler;
 import org.apache.syncope.types.IntMappingType;
 import org.identityconnectors.framework.common.objects.OperationalAttributes;
 import org.identityconnectors.framework.common.objects.Uid;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import sun.security.util.Password;
 
 public class SchemaMappingUtil {
 
@@ -135,7 +139,7 @@ public class SchemaMappingUtil {
      * @return schema and attribute values.
      */
     public static Map.Entry<AbstractSchema, List<AbstractAttrValue>> getIntValues(final SchemaMapping mapping,
-            final List<AbstractAttributable> attributables, final String password, final SchemaDAO schemaDAO) {
+            final List<Attributable> attributables, final String password, final SchemaDAO schemaDAO) {
 
         LOG.debug("Get attributes for '{}' and mapping type '{}'", attributables, mapping.getIntMappingType());
 
@@ -150,7 +154,7 @@ public class SchemaMappingUtil {
                 schema = schemaDAO.find(mapping.getIntAttrName(), SchemaMappingUtil.getIntMappingTypeClass(mapping.
                         getIntMappingType()));
 
-                for (AbstractAttributable attributable : attributables) {
+                for (Attributable attributable : attributables) {
                     final AbstractAttr attr = attributable.getAttribute(mapping.getIntAttrName());
 
                     if (attr != null && attr.getValues() != null) {
@@ -169,7 +173,7 @@ public class SchemaMappingUtil {
             case RoleVirtualSchema:
             case MembershipVirtualSchema:
 
-                for (AbstractAttributable attributable : attributables) {
+                for (Attributable attributable : attributables) {
                     AbstractVirAttr virAttr = attributable.getVirtualAttribute(mapping.getIntAttrName());
 
                     if (virAttr != null && virAttr.getValues() != null) {
@@ -189,7 +193,7 @@ public class SchemaMappingUtil {
             case UserDerivedSchema:
             case RoleDerivedSchema:
             case MembershipDerivedSchema:
-                for (AbstractAttributable attributable : attributables) {
+                for (Attributable attributable : attributables) {
                     AbstractDerAttr derAttr = attributable.getDerivedAttribute(mapping.getIntAttrName());
 
                     if (derAttr != null) {
@@ -205,15 +209,19 @@ public class SchemaMappingUtil {
                 break;
 
             case Username:
-                for (AbstractAttributable attributable : attributables) {
+                for (Attributable attributable : attributables) {
                     AbstractAttrValue attrValue = new UAttrValue();
-                    attrValue.setStringValue(((SyncopeUser) attributable).getUsername());
+                    SyncopeUser user = attributable instanceof Proxy?
+                            (SyncopeUser)((AttributableHandler)Proxy.getInvocationHandler(attributable)).getObject():
+                            (SyncopeUser)attributable;
+                     
+                    attrValue.setStringValue(user.getUsername());
                     values.add(attrValue);
                 }
                 break;
 
             case SyncopeUserId:
-                for (AbstractAttributable attributable : attributables) {
+                for (Attributable attributable : attributables) {
                     AbstractAttrValue attrValue = new UAttrValue();
                     attrValue.setStringValue(attributable.getId().toString());
                     values.add(attrValue);

Modified: syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java
URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java?rev=1428262&r1=1428261&r2=1428262&view=diff
==============================================================================
--- syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java (original)
+++ syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java Thu Jan  3 10:13:01 2013
@@ -38,9 +38,7 @@ import org.springframework.http.HttpStat
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.web.client.HttpStatusCodeException;
 import org.apache.syncope.client.http.PreemptiveAuthHttpRequestFactory;
-import org.apache.syncope.client.mod.AttributeMod;
-import org.apache.syncope.client.mod.MembershipMod;
-import org.apache.syncope.client.mod.UserMod;
+import org.apache.syncope.client.mod.*;
 import org.apache.syncope.client.to.AttributeTO;
 import org.apache.syncope.client.search.AttributeCond;
 import org.apache.syncope.client.search.SyncopeUserCond;
@@ -576,8 +574,7 @@ public class UserTestITCase extends Abst
         assertEquals(maxTaskExecutions, taskTO.getExecutions().size());
 
         // 3. verify password
-        Boolean verify = restTemplate.
-                getForObject(BASE_URL + "user/verifyPassword/{username}.json?password=password123",
+        Boolean verify = restTemplate.getForObject(BASE_URL + "user/verifyPassword/{username}.json?password=password123",
                 Boolean.class, newUserTO.getUsername());
         assertTrue(verify);
 
@@ -902,8 +899,7 @@ public class UserTestITCase extends Abst
             assertNotNull(user);
         }
 
-        users = Arrays.
-                asList(restTemplate.getForObject(BASE_URL + "user/list/{page}/{size}.json", UserTO[].class, 2, 2));
+        users = Arrays.asList(restTemplate.getForObject(BASE_URL + "user/list/{page}/{size}.json", UserTO[].class, 2, 2));
 
         assertNotNull(users);
         assertFalse(users.isEmpty());
@@ -2037,4 +2033,50 @@ public class UserTestITCase extends Abst
         }
         assertNotNull(sce);
     }
+
+    @Test
+    public void issueSYNCOPE260() {
+        // 1. create user with SOAP resource, succesfully propagated
+        UserTO userTO = getSampleTO("syncope260@apache.org");
+        userTO.addResource("ws-target-resource-2");
+
+        userTO = restTemplate.postForObject(BASE_URL + "user/create", userTO, UserTO.class);
+        assertNotNull(userTO);
+        assertFalse(userTO.getPropagationTOs().isEmpty());
+        assertEquals("ws-target-resource-2", userTO.getPropagationTOs().get(0).getResourceName());
+        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationTOs().get(0).getStatus());
+
+        // 3. try to find this user on the external SOAP resource
+        ConnObjectTO connObjectTO = restTemplate.getForObject(
+                BASE_URL + "/resource/{resourceName}/read/{objectId}.json",
+                ConnObjectTO.class, "ws-target-resource-2", userTO.getUsername());
+        assertNotNull(connObjectTO);
+        assertEquals("virtualvalue", connObjectTO.getAttributeMap().get("NAME").getValues().get(0));
+        
+        UserMod userMod = new UserMod();
+        userMod.setId(userTO.getId());
+        
+        AttributeMod attrMod = new AttributeMod();
+        attrMod.setSchema("surname");
+        attrMod.addValueToBeRemoved("Surname");
+        attrMod.addValueToBeAdded("Surname2");
+        
+        userMod.addAttributeToBeUpdated(attrMod);
+        
+        userTO = restTemplate.postForObject(BASE_URL + "user/update", userMod, UserTO.class);
+        assertNotNull(userTO);
+        assertFalse(userTO.getPropagationTOs().isEmpty());
+        assertEquals("ws-target-resource-2", userTO.getPropagationTOs().get(0).getResourceName());
+        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationTOs().get(0).getStatus());
+        
+        connObjectTO = restTemplate.getForObject(
+                BASE_URL + "/resource/{resourceName}/read/{objectId}.json",
+                ConnObjectTO.class, "ws-target-resource-2", userTO.getUsername());
+        assertNotNull(connObjectTO);
+        assertEquals("Surname2", connObjectTO.getAttributeMap().get("SURNAME").getValues().get(0));
+        
+        // attribute "name" mapped on virtual attribute "virtualdata" shouldn't be changed
+        assertFalse(connObjectTO.getAttributeMap().get("NAME").getValues().isEmpty());
+        assertEquals("virtualvalue", connObjectTO.getAttributeMap().get("NAME").getValues().get(0));
+    }
 }