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/12/29 16:12:12 UTC

svn commit: r1554031 [2/2] - in /syncope/trunk: common/src/main/java/org/apache/syncope/common/mod/ common/src/main/java/org/apache/syncope/common/reqres/ common/src/main/java/org/apache/syncope/common/services/ common/src/main/java/org/apache/syncope/...

Modified: syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage.html
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage.html?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage.html (original)
+++ syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage.html Sun Dec 29 15:12:11 2013
@@ -31,11 +31,90 @@ under the License.
       #dataTableSpan table{
         width: 100%;
       }
+
+      form#pwdMgtForm {
+        border: 1px solid #AAAAAA;
+        margin:10px;
+      }
+
+      div#changepwd {
+        display: inline-row;
+        margin-top: 5px;
+      }
+
+      div#changepwd div{
+        display: table-cell;
+      }
+
+      div#changepwd div#label{
+        padding-left: 5px;
+        vertical-align: middle;
+        font-family: Verdana,Arial,sans-serif;
+      }
+
+      div#password {
+        display: inline-row;
+        margin: 5px;
+      }
+
+      div#continue {
+        display: inline-row;
+        margin: 5px;
+      }
+
+      div#continue div{
+        display: table-cell;
+      }
+
+      div#cancelBtmForm {
+        padding-left: 5px;
+      }
     </style>
   </wicket:head>
   <wicket:extend>
     <p class="ui-widget ui-corner-all ui-widget-header"><wicket:message key="title"/></p>
     <span wicket:id="resourceDatatable" id="dataTableSpan">[resources]</span>
+
+    <span wicket:id="pwdMgtFields">[password management]</span>
+
+    <wicket:fragment wicket:id="pwdMgtFragment">
+      <div wicket:id="pwdMgt">
+        <form wicket:id="pwdMgtForm" id="pwdMgtForm">
+          <div id="changepwd">
+            <div id="value">
+              <span wicket:id="changepwd">[changepwd]</span>
+            </div>
+            <div id="label">
+              <label wicket:id="changePwdLabel">[Change password]</label>
+            </div>
+          </div>
+
+          <div id="password">
+            <input type="password" wicket:id="password" id="password" size="25"  style="width: 180px" title="password"/>
+          </div>
+
+          <div id="password">
+            <input type="password" wicket:id="confirm" id="confirm" size="25"  style="width: 180px" title="confirm"/>
+          </div>
+
+          <div id="continue">
+            <div id="continueBtmForm">
+              <input type="button" wicket:id="continue" id="continue"
+                     class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"/>
+            </div>
+
+            <div id="cancelBtmForm">
+              <input type="button" wicket:id="cancel" id="cancel"
+                     class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"/>
+            </div>
+          </div>
+        </form>
+      </div>
+    </wicket:fragment>
+
+    <wicket:fragment wicket:id="emptyFragment">
+    </wicket:fragment>
+
     <wicket:child />
   </wicket:extend>
 </html>

Modified: syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage.properties
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage.properties?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage.properties (original)
+++ syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage.properties Sun Dec 29 15:12:11 2013
@@ -15,3 +15,5 @@
 # specific language governing permissions and limitations
 # under the License.
 title=Global Status
+changePwdLabel=Password propagation
+passwordMismatch=Password mismatch

Modified: syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage_it.properties
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage_it.properties?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage_it.properties (original)
+++ syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage_it.properties Sun Dec 29 15:12:11 2013
@@ -15,3 +15,5 @@
 # specific language governing permissions and limitations
 # under the License.
 title=Stato Globale
+changePwdLabel=Propagazione password
+passwordMismatch=Password non corrispondenti
\ No newline at end of file

Modified: syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage_pt_BR.properties
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage_pt_BR.properties?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage_pt_BR.properties (original)
+++ syncope/trunk/console/src/main/resources/org/apache/syncope/console/pages/AbstractStatusModalPage_pt_BR.properties Sun Dec 29 15:12:11 2013
@@ -15,3 +15,5 @@
 # specific language governing permissions and limitations
 # under the License.
 title=Estatus Global
+changePwdLabel=Password propagation
+passwordMismatch=Password mismatch

Modified: syncope/trunk/console/src/main/resources/org/apache/syncope/console/wicket/markup/html/form/ActionLinksPanel.html
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/resources/org/apache/syncope/console/wicket/markup/html/form/ActionLinksPanel.html?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/console/src/main/resources/org/apache/syncope/console/wicket/markup/html/form/ActionLinksPanel.html (original)
+++ syncope/trunk/console/src/main/resources/org/apache/syncope/console/wicket/markup/html/form/ActionLinksPanel.html Sun Dec 29 15:12:11 2013
@@ -46,8 +46,11 @@ under the License.
     <span wicket:id="panelReload">[plus]</span>
     <span wicket:id="panelChangeView">[plus]</span>
     <span wicket:id="panelUnlink">[plus]</span>
+    <span wicket:id="panelLink">[plus]</span>
     <span wicket:id="panelUnassign">[plus]</span>
+    <span wicket:id="panelAssign">[plus]</span>
     <span wicket:id="panelDeprovision">[plus]</span>
+    <span wicket:id="panelProvision">[plus]</span>
 
     <wicket:fragment wicket:id="fragmentClaim">
       <a href="#" wicket:id="claimLink"><img id="action" src="img/actions/claim.png" alt="claim icon" title="Claim"/></a>
@@ -129,14 +132,26 @@ under the License.
       <a href="#" wicket:id="unlinkLink"><img id="action" src="img/actions/unlink-icon.png" alt="Unlink icon"  title="Unlink"/></a>
     </wicket:fragment>
 
+    <wicket:fragment wicket:id="fragmentLink">
+      <a href="#" wicket:id="linkLink"><img id="action" src="img/actions/link-icon.png" alt="Link icon"  title="Link"/></a>
+    </wicket:fragment>
+
     <wicket:fragment wicket:id="fragmentUnassign">
       <a href="#" wicket:id="unassignLink"><img id="action" src="img/actions/unassign-icon.png" alt="Unassign icon"  title="Unassign"/></a>
     </wicket:fragment>
 
+    <wicket:fragment wicket:id="fragmentAssign">
+      <a href="#" wicket:id="assignLink"><img id="action" src="img/actions/assign-icon.png" alt="Assign icon"  title="Assign"/></a>
+    </wicket:fragment>
+
     <wicket:fragment wicket:id="fragmentDeprovision">
       <a href="#" wicket:id="deprovisionLink"><img id="action" src="img/actions/deprovision-icon.png" alt="De-provision icon"  title="De-provision"/></a>
     </wicket:fragment>
 
+    <wicket:fragment wicket:id="fragmentProvision">
+      <a href="#" wicket:id="provisionLink"><img id="action" src="img/actions/provision-icon.png" alt="Provision icon"  title="Provision"/></a>
+    </wicket:fragment>
+
     <wicket:fragment wicket:id="emptyFragment">
     </wicket:fragment>
   </wicket:panel>

Added: syncope/trunk/console/src/main/webapp/img/actions/assign-icon.png
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/webapp/img/actions/assign-icon.png?rev=1554031&view=auto
==============================================================================
Binary file - no diff available.

Propchange: syncope/trunk/console/src/main/webapp/img/actions/assign-icon.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: syncope/trunk/console/src/main/webapp/img/actions/link-icon.png
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/webapp/img/actions/link-icon.png?rev=1554031&view=auto
==============================================================================
Binary file - no diff available.

Propchange: syncope/trunk/console/src/main/webapp/img/actions/link-icon.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: syncope/trunk/console/src/main/webapp/img/actions/provision-icon.png
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/webapp/img/actions/provision-icon.png?rev=1554031&view=auto
==============================================================================
Binary file - no diff available.

Propchange: syncope/trunk/console/src/main/webapp/img/actions/provision-icon.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Modified: syncope/trunk/console/src/main/webapp/img/actions/unassign-icon.png
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/webapp/img/actions/unassign-icon.png?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
Binary files - no diff available.

Modified: syncope/trunk/console/src/test/java/org/apache/syncope/console/UserTestITCase.java
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/test/java/org/apache/syncope/console/UserTestITCase.java?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/console/src/test/java/org/apache/syncope/console/UserTestITCase.java (original)
+++ syncope/trunk/console/src/test/java/org/apache/syncope/console/UserTestITCase.java Sun Dec 29 15:12:11 2013
@@ -97,4 +97,18 @@ public class UserTestITCase extends Abst
 
         selenium.waitForCondition("selenium.isTextPresent(" + "\"Operation executed successfully\");", "30000");
     }
+
+    @Test
+    public void browsProvisioningFeatures() {
+        selenium.click("css=img[alt=\"Users\"]");
+
+        selenium.waitForCondition("selenium.isElementPresent(\"//div[@id='tabs']\");", "30000");
+
+        //Edit vivaldi
+        selenium.click("//*[@id=\"users-contain\"]//*[div=3]/../td[5]/div/span[2]/a");
+        selenium.waitForCondition("selenium.isElementPresent(" + "\"//td[div='ws-target-resource-1']\");", "30000");
+        selenium.waitForCondition("selenium.isElementPresent(" + "\"//td[div='resource-testdb']\");", "30000");
+
+        selenium.click("css=a.w_close");
+    }
 }

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java Sun Dec 29 15:12:11 2013
@@ -393,9 +393,25 @@ public class PropagationManager {
      */
     public List<PropagationTask> getUserDeleteTaskIds(final Long userId, final String noPropResourceName)
             throws NotFoundException, UnauthorizedRoleException {
+        return getUserDeleteTaskIds(userId, Collections.<String>singleton(noPropResourceName));
+    }
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param userId to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if user is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
+     */
+    public List<PropagationTask> getUserDeleteTaskIds(final Long userId, final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
 
         SyncopeUser user = userDataBinder.getUserFromId(userId);
-        return getDeleteTaskIds(user, Collections.<String>singleton(noPropResourceName));
+        return getDeleteTaskIds(user, user.getResourceNames(), noPropResourceNames);
     }
 
     /**
@@ -409,11 +425,12 @@ public class PropagationManager {
      * @throws NotFoundException if user is not found
      * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
      */
-    public List<PropagationTask> getUserDeleteTaskIds(final Long userId, final Collection<String> noPropResourceNames)
+    public List<PropagationTask> getUserDeleteTaskIds(
+            final Long userId, final Set<String> resourceNames, final Collection<String> noPropResourceNames)
             throws NotFoundException, UnauthorizedRoleException {
 
         SyncopeUser user = userDataBinder.getUserFromId(userId);
-        return getDeleteTaskIds(user, noPropResourceNames);
+        return getDeleteTaskIds(user, resourceNames, noPropResourceNames);
     }
 
     /**
@@ -477,14 +494,35 @@ public class PropagationManager {
             throws NotFoundException, UnauthorizedRoleException {
 
         SyncopeRole role = roleDataBinder.getRoleFromId(roleId);
-        return getDeleteTaskIds(role, noPropResourceNames);
+        return getDeleteTaskIds(role, role.getResourceNames(), noPropResourceNames);
+    }
+    
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param roleId to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    public List<PropagationTask> getRoleDeleteTaskIds(
+            final Long roleId, final Set<String> resourceNames, final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        SyncopeRole role = roleDataBinder.getRoleFromId(roleId);
+        return getDeleteTaskIds(role, resourceNames, noPropResourceNames);
     }
 
-    protected List<PropagationTask> getDeleteTaskIds(final AbstractAttributable attributable,
+    protected List<PropagationTask> getDeleteTaskIds(
+            final AbstractAttributable attributable,
+            final Set<String> resourceNames,
             final Collection<String> noPropResourceNames) {
 
         final PropagationByResource propByRes = new PropagationByResource();
-        propByRes.set(ResourceOperation.DELETE, attributable.getResourceNames());
+        propByRes.set(ResourceOperation.DELETE, resourceNames);
         if (noPropResourceNames != null && !noPropResourceNames.isEmpty()) {
             propByRes.get(ResourceOperation.DELETE).removeAll(noPropResourceNames);
         }

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/AbstractResourceAssociator.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/AbstractResourceAssociator.java?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/AbstractResourceAssociator.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/AbstractResourceAssociator.java Sun Dec 29 15:12:11 2013
@@ -25,7 +25,13 @@ public abstract class AbstractResourceAs
 
     public abstract T unlink(Long id, Collection<String> resources);
 
+    public abstract T link(Long id, Collection<String> resources);
+
     public abstract T unassign(Long id, Collection<String> resources);
 
+    public abstract T assign(Long id, Collection<String> resources, boolean changepwd, String password);
+
     public abstract T deprovision(Long userId, Collection<String> resources);
+
+    public abstract T provision(Long userId, Collection<String> resources, boolean changepwd, String password);
 }

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/RoleController.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/RoleController.java?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/RoleController.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/RoleController.java Sun Dec 29 15:12:11 2013
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import javax.annotation.Resource;
@@ -56,6 +57,7 @@ import org.springframework.security.acce
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
 
 /**
  * Note that this controller does not extend AbstractTransactionalController, hence does not provide any
@@ -343,12 +345,18 @@ public class RoleController extends Abst
     public RoleTO unlink(final Long roleId, final Collection<String> resources) {
         final RoleMod roleMod = new RoleMod();
         roleMod.setId(roleId);
-
         roleMod.getResourcesToRemove().addAll(resources);
+        return binder.getRoleTO(rwfAdapter.update(roleMod).getResult());
+    }
 
-        final WorkflowResult<Long> updated = rwfAdapter.update(roleMod);
-
-        return binder.getRoleTO(updated.getResult());
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO link(final Long roleId, final Collection<String> resources) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setId(roleId);
+        roleMod.getResourcesToAdd().addAll(resources);
+        return binder.getRoleTO(rwfAdapter.update(roleMod).getResult());
     }
 
     @PreAuthorize("hasRole('ROLE_UPDATE')")
@@ -358,20 +366,31 @@ public class RoleController extends Abst
         final RoleMod roleMod = new RoleMod();
         roleMod.setId(roleId);
         roleMod.getResourcesToRemove().addAll(resources);
-
         return update(roleMod);
     }
 
     @PreAuthorize("hasRole('ROLE_UPDATE')")
     @Transactional(rollbackFor = { Throwable.class })
     @Override
+    public RoleTO assign(
+            final Long roleId, final Collection<String> resources, final boolean changePwd, final String password) {
+        final RoleMod userMod = new RoleMod();
+        userMod.setId(roleId);
+        userMod.getResourcesToAdd().addAll(resources);
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
     public RoleTO deprovision(final Long roleId, final Collection<String> resources) {
         final SyncopeRole role = binder.getRoleFromId(roleId);
 
         final Set<String> noPropResourceName = role.getResourceNames();
         noPropResourceName.removeAll(resources);
 
-        final List<PropagationTask> tasks = propagationManager.getRoleDeleteTaskIds(roleId, noPropResourceName);
+        final List<PropagationTask> tasks =
+                propagationManager.getRoleDeleteTaskIds(roleId, new HashSet<String>(resources), noPropResourceName);
         PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().getBean(
                 PropagationReporter.class);
         try {
@@ -386,6 +405,22 @@ public class RoleController extends Abst
         return updatedTO;
     }
 
+    @PreAuthorize("hasRole('ROLE_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public RoleTO provision(
+            final Long roleId, final Collection<String> resources, final boolean changePwd, final String password) {
+        final RoleTO original = binder.getRoleTO(roleId);
+
+        //trick: assign and retrieve propagation statuses ...
+        original.getPropagationStatusTOs().addAll(
+                assign(roleId, resources, changePwd, password).getPropagationStatusTOs());
+
+        // .... rollback.
+        TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
+        return original;
+    }
+
     /**
      * {@inheritDoc}
      */

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java Sun Dec 29 15:12:11 2013
@@ -61,6 +61,7 @@ import org.springframework.beans.factory
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
 
 /**
  * Note that this controller does not extend AbstractTransactionalController, hence does not provide any
@@ -433,12 +434,18 @@ public class UserController extends Abst
     public UserTO unlink(final Long userId, final Collection<String> resources) {
         final UserMod userMod = new UserMod();
         userMod.setId(userId);
-
         userMod.getResourcesToRemove().addAll(resources);
+        return binder.getUserTO(uwfAdapter.update(userMod).getResult().getKey().getId());
+    }
 
-        WorkflowResult<Map.Entry<UserMod, Boolean>> updated = uwfAdapter.update(userMod);
-
-        return binder.getUserTO(updated.getResult().getKey().getId());
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO link(final Long userId, final Collection<String> resources) {
+        final UserMod userMod = new UserMod();
+        userMod.setId(userId);
+        userMod.getResourcesToAdd().addAll(resources);
+        return binder.getUserTO(uwfAdapter.update(userMod).getResult().getKey().getId());
     }
 
     @PreAuthorize("hasRole('USER_UPDATE')")
@@ -448,6 +455,28 @@ public class UserController extends Abst
         final UserMod userMod = new UserMod();
         userMod.setId(userId);
         userMod.getResourcesToRemove().addAll(resources);
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO assign(
+            final Long userId,
+            final Collection<String> resources,
+            final boolean changepwd,
+            final String password) {
+        final UserMod userMod = new UserMod();
+        userMod.setId(userId);
+        userMod.getResourcesToAdd().addAll(resources);
+
+        if (changepwd) {
+            StatusMod statusMod = new StatusMod();
+            statusMod.setOnSyncope(false);
+            statusMod.getResourceNames().addAll(resources);
+            userMod.setPwdPropRequest(statusMod);
+            userMod.setPassword(password);
+        }
 
         return update(userMod);
     }
@@ -461,9 +490,10 @@ public class UserController extends Abst
         final Set<String> noPropResourceName = user.getResourceNames();
         noPropResourceName.removeAll(resources);
 
-        final List<PropagationTask> tasks = propagationManager.getUserDeleteTaskIds(userId, noPropResourceName);
-        PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().
-                getBean(PropagationReporter.class);
+        final List<PropagationTask> tasks =
+                propagationManager.getUserDeleteTaskIds(userId, new HashSet<String>(resources), noPropResourceName);
+        final PropagationReporter propagationReporter =
+                ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class);
         try {
             taskExecutor.execute(tasks, propagationReporter);
         } catch (PropagationException e) {
@@ -476,6 +506,26 @@ public class UserController extends Abst
         return updatedUserTO;
     }
 
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(readOnly = true)
+    @Override
+    public UserTO provision(
+            final Long userId,
+            final Collection<String> resources,
+            final boolean changePwd,
+            final String password) {
+
+        final UserTO original = binder.getUserTO(userId);
+
+        //trick: assign and retrieve propagation statuses ...
+        original.getPropagationStatusTOs().addAll(
+                assign(userId, resources, changePwd, password).getPropagationStatusTOs());
+
+        // .... rollback.
+        TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
+        return original;
+    }
+
     /**
      * {@inheritDoc}
      */

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/services/RoleServiceImpl.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/services/RoleServiceImpl.java?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/services/RoleServiceImpl.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/services/RoleServiceImpl.java Sun Dec 29 15:12:11 2013
@@ -25,11 +25,14 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 
 import org.apache.syncope.common.mod.RoleMod;
+import org.apache.syncope.common.reqres.BulkActionResult;
 import org.apache.syncope.common.services.RoleService;
 import org.apache.syncope.common.reqres.PagedResult;
+import org.apache.syncope.common.to.PropagationStatus;
 import org.apache.syncope.common.wrap.ResourceName;
 import org.apache.syncope.common.to.RoleTO;
 import org.apache.syncope.common.types.ResourceAssociationActionType;
+import org.apache.syncope.common.types.ResourceDeAssociationActionType;
 import org.apache.syncope.common.util.CollectionWrapper;
 import org.apache.syncope.core.persistence.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.dao.search.SearchCond;
@@ -143,8 +146,8 @@ public class RoleServiceImpl extends Abs
     }
 
     @Override
-    public Response associate(final Long roleId, final ResourceAssociationActionType type,
-            final List<ResourceName> resourceNames) {
+    public Response bulkDeassociation(
+            final Long roleId, final ResourceDeAssociationActionType type, final List<ResourceName> resourceNames) {
 
         RoleTO role = controller.read(roleId);
 
@@ -169,7 +172,68 @@ public class RoleServiceImpl extends Abs
                     updated = controller.read(roleId);
             }
 
-            builder = modificationResponse(updated);
+            final BulkActionResult res = new BulkActionResult();
+
+            if (type == ResourceDeAssociationActionType.UNLINK) {
+                for (ResourceName resourceName : resourceNames) {
+                    res.add(resourceName.getName(), updated.getResources().contains(resourceName.getName())
+                            ? BulkActionResult.Status.FAILURE
+                            : BulkActionResult.Status.SUCCESS);
+                }
+            } else {
+                for (PropagationStatus propagationStatusTO : updated.getPropagationStatusTOs()) {
+                    res.add(propagationStatusTO.getResource(), propagationStatusTO.getStatus().toString());
+                }
+            }
+
+            builder = modificationResponse(res);
+        }
+
+        return builder.build();
+    }
+
+    @Override
+    public Response bulkAssociation(
+            final Long roleId, final ResourceAssociationActionType type, final List<ResourceName> resourceNames) {
+
+        RoleTO role = controller.read(roleId);
+
+        ResponseBuilder builder = messageContext.getRequest().evaluatePreconditions(new EntityTag(role.getETagValue()));
+        if (builder == null) {
+            RoleTO updated;
+
+            switch (type) {
+                case LINK:
+                    updated = controller.link(roleId, CollectionWrapper.unwrap(resourceNames));
+                    break;
+
+                case ASSIGN:
+                    updated = controller.assign(roleId, CollectionWrapper.unwrap(resourceNames), false, null);
+                    break;
+
+                case PROVISION:
+                    updated = controller.provision(roleId, CollectionWrapper.unwrap(resourceNames), false, null);
+                    break;
+
+                default:
+                    updated = controller.read(roleId);
+            }
+
+            final BulkActionResult res = new BulkActionResult();
+
+            if (type == ResourceAssociationActionType.LINK) {
+                for (ResourceName resourceName : resourceNames) {
+                    res.add(resourceName.getName(), updated.getResources().contains(resourceName.getName())
+                            ? BulkActionResult.Status.FAILURE
+                            : BulkActionResult.Status.SUCCESS);
+                }
+            } else {
+                for (PropagationStatus propagationStatusTO : updated.getPropagationStatusTOs()) {
+                    res.add(propagationStatusTO.getResource(), propagationStatusTO.getStatus().toString());
+                }
+            }
+
+            builder = modificationResponse(res);
         }
 
         return builder.build();

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/services/UserServiceImpl.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/services/UserServiceImpl.java?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/services/UserServiceImpl.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/services/UserServiceImpl.java Sun Dec 29 15:12:11 2013
@@ -23,16 +23,19 @@ import javax.ws.rs.core.EntityTag;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
+import org.apache.syncope.common.mod.ResourceAssociationMod;
 import org.apache.syncope.common.mod.StatusMod;
 import org.apache.syncope.common.mod.UserMod;
-import org.apache.syncope.common.services.UserService;
 import org.apache.syncope.common.reqres.BulkAction;
+import org.apache.syncope.common.services.UserService;
 import org.apache.syncope.common.reqres.BulkActionResult;
 import org.apache.syncope.common.reqres.PagedResult;
+import org.apache.syncope.common.to.PropagationStatus;
 import org.apache.syncope.common.wrap.ResourceName;
 import org.apache.syncope.common.to.UserTO;
 import org.apache.syncope.common.types.RESTHeaders;
 import org.apache.syncope.common.types.ResourceAssociationActionType;
+import org.apache.syncope.common.types.ResourceDeAssociationActionType;
 import org.apache.syncope.common.util.CollectionWrapper;
 import org.apache.syncope.core.persistence.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.dao.search.SearchCond;
@@ -159,17 +162,13 @@ public class UserServiceImpl extends Abs
     }
 
     @Override
-    public BulkActionResult bulk(final BulkAction bulkAction) {
-        return controller.bulk(bulkAction);
-    }
+    public Response bulkDeassociation(
+            final Long userId, final ResourceDeAssociationActionType type, final List<ResourceName> resourceNames) {
 
-    @Override
-    public Response associate(final Long userId, final ResourceAssociationActionType type,
-            final List<ResourceName> resourceNames) {
-
-        UserTO user = controller.read(userId);
+        final UserTO user = controller.read(userId);
 
         ResponseBuilder builder = messageContext.getRequest().evaluatePreconditions(new EntityTag(user.getETagValue()));
+
         if (builder == null) {
             UserTO updated;
 
@@ -190,9 +189,85 @@ public class UserServiceImpl extends Abs
                     updated = controller.read(userId);
             }
 
-            builder = modificationResponse(updated);
+            final BulkActionResult res = new BulkActionResult();
+
+            if (type == ResourceDeAssociationActionType.UNLINK) {
+                for (ResourceName resourceName : resourceNames) {
+                    res.add(resourceName.getName(), updated.getResources().contains(resourceName.getName())
+                            ? BulkActionResult.Status.FAILURE
+                            : BulkActionResult.Status.SUCCESS);
+                }
+            } else {
+                for (PropagationStatus propagationStatusTO : updated.getPropagationStatusTOs()) {
+                    res.add(propagationStatusTO.getResource(), propagationStatusTO.getStatus().toString());
+                }
+            }
+
+            builder = modificationResponse(res);
+        }
+
+        return builder.build();
+    }
+
+    @Override
+    public Response bulkAssociation(
+            final Long userId, final ResourceAssociationActionType type, final ResourceAssociationMod associationMod) {
+
+        final UserTO user = controller.read(userId);
+
+        ResponseBuilder builder = messageContext.getRequest().evaluatePreconditions(new EntityTag(user.getETagValue()));
+        if (builder == null) {
+            UserTO updated;
+
+            switch (type) {
+                case LINK:
+                    updated = controller.link(
+                            userId,
+                            CollectionWrapper.unwrap(associationMod.getTargetResources()));
+                    break;
+
+                case ASSIGN:
+                    updated = controller.assign(
+                            userId,
+                            CollectionWrapper.unwrap(associationMod.getTargetResources()),
+                            associationMod.isChangePwd(),
+                            associationMod.getPassword());
+                    break;
+
+                case PROVISION:
+                    updated = controller.provision(
+                            userId,
+                            CollectionWrapper.unwrap(associationMod.getTargetResources()),
+                            associationMod.isChangePwd(),
+                            associationMod.getPassword());
+                    break;
+
+                default:
+                    updated = controller.read(userId);
+            }
+
+            final BulkActionResult res = new BulkActionResult();
+
+            if (type == ResourceAssociationActionType.LINK) {
+                for (ResourceName resourceName : associationMod.getTargetResources()) {
+                    res.add(resourceName.getName(), updated.getResources().contains(resourceName.getName())
+                            ? BulkActionResult.Status.FAILURE
+                            : BulkActionResult.Status.SUCCESS);
+                }
+            } else {
+                for (PropagationStatus propagationStatusTO : updated.getPropagationStatusTOs()) {
+                    res.add(propagationStatusTO.getResource(), propagationStatusTO.getStatus().toString());
+                }
+            }
+
+            builder = modificationResponse(res);
         }
 
         return builder.build();
     }
+
+    @Override
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        return controller.bulk(bulkAction);
+    }
 }

Modified: syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/RoleTestITCase.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/RoleTestITCase.java?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/RoleTestITCase.java (original)
+++ syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/RoleTestITCase.java Sun Dec 29 15:12:11 2013
@@ -45,10 +45,12 @@ import org.apache.syncope.common.types.A
 import org.apache.syncope.common.types.ClientExceptionType;
 import org.apache.syncope.common.types.Preference;
 import org.apache.syncope.common.types.RESTHeaders;
-import org.apache.syncope.common.types.ResourceAssociationActionType;
 import org.apache.syncope.common.types.SchemaType;
 import org.apache.syncope.common.util.CollectionWrapper;
 import org.apache.syncope.common.SyncopeClientException;
+import org.apache.syncope.common.reqres.BulkActionResult;
+import org.apache.syncope.common.types.ResourceAssociationActionType;
+import org.apache.syncope.common.types.ResourceDeAssociationActionType;
 import org.identityconnectors.framework.common.objects.Name;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
@@ -402,34 +404,60 @@ public class RoleTestITCase extends Abst
 
         assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId()));
 
-        actual = roleService.associate(actual.getId(),
-                ResourceAssociationActionType.UNLINK,
-                CollectionWrapper.wrap("resource-ldap", ResourceName.class)).
-                readEntity(RoleTO.class);
-        assertNotNull(actual);
-        assertTrue(actual.getResources().isEmpty());
+        assertNotNull(roleService.bulkDeassociation(actual.getId(),
+                ResourceDeAssociationActionType.UNLINK,
+                CollectionWrapper.wrap(RESOURCE_NAME_LDAP, ResourceName.class)).
+                readEntity(BulkActionResult.class));
 
         actual = roleService.read(actual.getId());
         assertNotNull(actual);
-
         assertTrue(actual.getResources().isEmpty());
 
         assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId()));
     }
 
     @Test
+    public void link() {
+        RoleTO roleTO = buildRoleTO("link");
+        roleTO.getResources().clear();
+
+        RoleTO actual = createRole(roleTO);
+        assertNotNull(actual);
+
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+
+        assertNotNull(roleService.bulkAssociation(actual.getId(),
+                ResourceAssociationActionType.LINK,
+                CollectionWrapper.wrap(RESOURCE_NAME_LDAP, ResourceName.class)).
+                readEntity(BulkActionResult.class));
+
+        actual = roleService.read(actual.getId());
+        assertFalse(actual.getResources().isEmpty());
+
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+    }
+
+    @Test
     public void unassign() {
         RoleTO actual = createRole(buildRoleTO("unassign"));
         assertNotNull(actual);
 
         assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId()));
 
-        actual = roleService.associate(actual.getId(),
-                ResourceAssociationActionType.UNASSIGN,
-                CollectionWrapper.wrap("resource-ldap", ResourceName.class)).
-                readEntity(RoleTO.class);
-        assertNotNull(actual);
-        assertTrue(actual.getResources().isEmpty());
+        assertNotNull(roleService.bulkDeassociation(actual.getId(),
+                ResourceDeAssociationActionType.UNASSIGN,
+                CollectionWrapper.wrap(RESOURCE_NAME_LDAP, ResourceName.class)).
+                readEntity(BulkActionResult.class));
 
         actual = roleService.read(actual.getId());
         assertNotNull(actual);
@@ -444,22 +472,113 @@ public class RoleTestITCase extends Abst
     }
 
     @Test
+    public void assign() {
+        RoleTO roleTO = buildRoleTO("assign");
+        roleTO.getResources().clear();
+
+        RoleTO actual = createRole(roleTO);
+        assertNotNull(actual);
+
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+
+        assertNotNull(roleService.bulkAssociation(actual.getId(),
+                ResourceAssociationActionType.ASSIGN,
+                CollectionWrapper.wrap(RESOURCE_NAME_LDAP, ResourceName.class)).
+                readEntity(BulkActionResult.class));
+
+        actual = roleService.read(actual.getId());
+        assertFalse(actual.getResources().isEmpty());
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId()));
+    }
+
+    @Test
     public void deprovision() {
         RoleTO actual = createRole(buildRoleTO("deprovision"));
         assertNotNull(actual);
 
         assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId()));
 
-        actual = roleService.associate(actual.getId(),
-                ResourceAssociationActionType.DEPROVISION,
-                CollectionWrapper.wrap("resource-ldap", ResourceName.class)).
-                readEntity(RoleTO.class);
+        assertNotNull(roleService.bulkDeassociation(actual.getId(),
+                ResourceDeAssociationActionType.DEPROVISION,
+                CollectionWrapper.wrap(RESOURCE_NAME_LDAP, ResourceName.class)).
+                readEntity(BulkActionResult.class));
+
+        actual = roleService.read(actual.getId());
         assertNotNull(actual);
         assertFalse(actual.getResources().isEmpty());
 
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+    }
+
+    @Test
+    public void provision() {
+        RoleTO roleTO = buildRoleTO("assign");
+        roleTO.getResources().clear();
+
+        RoleTO actual = createRole(roleTO);
+        assertNotNull(actual);
+
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+
+        assertNotNull(roleService.bulkAssociation(actual.getId(),
+                ResourceAssociationActionType.PROVISION,
+                CollectionWrapper.wrap(RESOURCE_NAME_LDAP, ResourceName.class)).
+                readEntity(BulkActionResult.class));
+
         actual = roleService.read(actual.getId());
+        assertTrue(actual.getResources().isEmpty());
+
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId()));
+    }
+
+    @Test
+    public void deprovisionUnlinked() {
+        RoleTO roleTO = buildRoleTO("assign");
+        roleTO.getResources().clear();
+
+        RoleTO actual = createRole(roleTO);
         assertNotNull(actual);
-        assertFalse(actual.getResources().isEmpty());
+
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+
+        assertNotNull(roleService.bulkAssociation(actual.getId(),
+                ResourceAssociationActionType.PROVISION,
+                CollectionWrapper.wrap("resource-ldap", ResourceName.class)).
+                readEntity(BulkActionResult.class));
+
+        actual = roleService.read(actual.getId());
+        assertTrue(actual.getResources().isEmpty());
+
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId()));
+
+        assertNotNull(roleService.bulkDeassociation(actual.getId(),
+                ResourceDeAssociationActionType.DEPROVISION,
+                CollectionWrapper.wrap(RESOURCE_NAME_LDAP, ResourceName.class)).
+                readEntity(BulkActionResult.class));
+
+        actual = roleService.read(actual.getId());
+        assertNotNull(actual);
+        assertTrue(actual.getResources().isEmpty());
 
         try {
             resourceService.getConnectorObject(RESOURCE_NAME_LDAP, AttributableType.ROLE, actual.getId());

Modified: syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java?rev=1554031&r1=1554030&r2=1554031&view=diff
==============================================================================
--- syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java (original)
+++ syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java Sun Dec 29 15:12:11 2013
@@ -62,7 +62,6 @@ import org.apache.syncope.common.types.C
 import org.apache.syncope.common.types.PolicyType;
 import org.apache.syncope.common.types.PropagationTaskExecStatus;
 import org.apache.syncope.common.types.ClientExceptionType;
-import org.apache.syncope.common.types.ResourceAssociationActionType;
 import org.apache.syncope.common.types.TaskType;
 import org.apache.syncope.common.util.AttributableOperations;
 import org.apache.syncope.common.util.CollectionWrapper;
@@ -79,10 +78,13 @@ import javax.xml.ws.WebServiceException;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.syncope.client.SyncopeClient;
+import org.apache.syncope.common.mod.ResourceAssociationMod;
 import org.apache.syncope.common.services.UserService;
 import org.apache.syncope.common.reqres.PagedResult;
 import org.apache.syncope.common.types.Preference;
 import org.apache.syncope.common.types.RESTHeaders;
+import org.apache.syncope.common.types.ResourceAssociationActionType;
+import org.apache.syncope.common.types.ResourceDeAssociationActionType;
 import org.identityconnectors.framework.common.objects.Name;
 import org.junit.Assume;
 import org.junit.FixMethodOrder;
@@ -1848,26 +1850,56 @@ public class UserTestITCase extends Abst
 
         UserTO actual = createUser(userTO);
         assertNotNull(actual);
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId()));
 
-        ConnObjectTO connObjectTO =
-                resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
-        assertNotNull(connObjectTO);
-
-        actual = userService.associate(actual.getId(),
-                ResourceAssociationActionType.UNLINK,
+        assertNotNull(userService.bulkDeassociation(actual.getId(),
+                ResourceDeAssociationActionType.UNLINK,
                 CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class)).
-                readEntity(UserTO.class);
-        assertNotNull(actual);
-        assertTrue(actual.getResources().isEmpty());
+                readEntity(BulkActionResult.class));
 
         actual = userService.read(actual.getId());
         assertNotNull(actual);
+        assertTrue(actual.getResources().isEmpty());
+
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId()));
+    }
+
+    @Test
+    public void link() {
+        UserTO userTO = getUniqueSampleTO("link@syncope.apache.org");
+        userTO.getResources().clear();
+        userTO.getMemberships().clear();
+        userTO.getDerAttrs().clear();
+        userTO.getVirAttrs().clear();
+        userTO.getDerAttrs().add(attributeTO("csvuserid", null));
 
+        UserTO actual = createUser(userTO);
+        assertNotNull(actual);
         assertTrue(actual.getResources().isEmpty());
 
-        connObjectTO =
-                resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
-        assertNotNull(connObjectTO);
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+
+        final ResourceAssociationMod associationMod = new ResourceAssociationMod();
+        associationMod.getTargetResources().addAll(CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class));
+
+        assertNotNull(userService.bulkAssociation(
+                actual.getId(), ResourceAssociationActionType.LINK, associationMod).readEntity(BulkActionResult.class));
+
+        actual = userService.read(actual.getId());
+        assertNotNull(actual);
+        assertFalse(actual.getResources().isEmpty());
+
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
     }
 
     @Test
@@ -1882,19 +1914,35 @@ public class UserTestITCase extends Abst
 
         UserTO actual = createUser(userTO);
         assertNotNull(actual);
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId()));
 
-        ConnObjectTO connObjectTO =
-                resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
-        assertNotNull(connObjectTO);
-
-        actual = userService.associate(actual.getId(),
-                ResourceAssociationActionType.UNASSIGN,
+        assertNotNull(userService.bulkDeassociation(actual.getId(),
+                ResourceDeAssociationActionType.UNASSIGN,
                 CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class)).
-                readEntity(UserTO.class);
+                readEntity(BulkActionResult.class));
+
+        actual = userService.read(actual.getId());
         assertNotNull(actual);
         assertTrue(actual.getResources().isEmpty());
 
-        actual = userService.read(actual.getId());
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+    }
+
+    @Test
+    public void assign() {
+        UserTO userTO = getUniqueSampleTO("assign@syncope.apache.org");
+        userTO.getResources().clear();
+        userTO.getMemberships().clear();
+        userTO.getDerAttrs().clear();
+        userTO.getVirAttrs().clear();
+        userTO.getDerAttrs().add(attributeTO("csvuserid", null));
+
+        UserTO actual = createUser(userTO);
         assertNotNull(actual);
         assertTrue(actual.getResources().isEmpty());
 
@@ -1904,6 +1952,19 @@ public class UserTestITCase extends Abst
         } catch (Exception e) {
             assertNotNull(e);
         }
+
+        final ResourceAssociationMod associationMod = new ResourceAssociationMod();
+        associationMod.getTargetResources().addAll(CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class));
+        associationMod.setChangePwd(true);
+        associationMod.setPassword("password");
+
+        assertNotNull(userService.bulkAssociation(actual.getId(), ResourceAssociationActionType.ASSIGN, associationMod)
+                .readEntity(BulkActionResult.class));
+
+        actual = userService.read(actual.getId());
+        assertNotNull(actual);
+        assertFalse(actual.getResources().isEmpty());
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId()));
     }
 
     @Test
@@ -1918,21 +1979,102 @@ public class UserTestITCase extends Abst
 
         UserTO actual = createUser(userTO);
         assertNotNull(actual);
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId()));
 
-        ConnObjectTO connObjectTO =
-                resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
-        assertNotNull(connObjectTO);
-
-        actual = userService.associate(actual.getId(),
-                ResourceAssociationActionType.DEPROVISION,
+        assertNotNull(userService.bulkDeassociation(actual.getId(),
+                ResourceDeAssociationActionType.DEPROVISION,
                 CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class)).
-                readEntity(UserTO.class);
+                readEntity(BulkActionResult.class));
+
+        actual = userService.read(actual.getId());
         assertNotNull(actual);
         assertFalse(actual.getResources().isEmpty());
 
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+    }
+
+    @Test
+    public void provision() {
+        UserTO userTO = getUniqueSampleTO("provision@syncope.apache.org");
+        userTO.getResources().clear();
+        userTO.getMemberships().clear();
+        userTO.getDerAttrs().clear();
+        userTO.getVirAttrs().clear();
+        userTO.getDerAttrs().add(attributeTO("csvuserid", null));
+
+        UserTO actual = createUser(userTO);
+        assertNotNull(actual);
+        assertTrue(actual.getResources().isEmpty());
+
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+
+        final ResourceAssociationMod associationMod = new ResourceAssociationMod();
+        associationMod.getTargetResources().addAll(CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class));
+        associationMod.setChangePwd(true);
+        associationMod.setPassword("password");
+
+        assertNotNull(userService.bulkAssociation(actual.getId(), ResourceAssociationActionType.PROVISION,
+                associationMod)
+                .readEntity(BulkActionResult.class));
+
         actual = userService.read(actual.getId());
         assertNotNull(actual);
-        assertFalse(actual.getResources().isEmpty());
+        assertTrue(actual.getResources().isEmpty());
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId()));
+    }
+
+    @Test
+    public void deprovisionUnlinked() {
+        UserTO userTO = getUniqueSampleTO("provision@syncope.apache.org");
+        userTO.getResources().clear();
+        userTO.getMemberships().clear();
+        userTO.getDerAttrs().clear();
+        userTO.getVirAttrs().clear();
+        userTO.getDerAttrs().add(attributeTO("csvuserid", null));
+
+        UserTO actual = createUser(userTO);
+        assertNotNull(actual);
+        assertTrue(actual.getResources().isEmpty());
+
+        try {
+            resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
+            fail();
+        } catch (Exception e) {
+            assertNotNull(e);
+        }
+
+        final ResourceAssociationMod associationMod = new ResourceAssociationMod();
+        associationMod.getTargetResources().addAll(CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class));
+        associationMod.setChangePwd(true);
+        associationMod.setPassword("password");
+
+        assertNotNull(userService.bulkAssociation(actual.getId(), ResourceAssociationActionType.PROVISION,
+                associationMod)
+                .readEntity(BulkActionResult.class));
+
+        actual = userService.read(actual.getId());
+        assertNotNull(actual);
+        assertTrue(actual.getResources().isEmpty());
+        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId()));
+
+        assertNotNull(userService.bulkDeassociation(actual.getId(),
+                ResourceDeAssociationActionType.DEPROVISION,
+                CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceName.class)).
+                readEntity(BulkActionResult.class));
+
+        actual = userService.read(actual.getId());
+        assertNotNull(actual);
+        assertTrue(actual.getResources().isEmpty());
 
         try {
             resourceService.getConnectorObject(RESOURCE_NAME_CSV, AttributableType.USER, actual.getId());
@@ -2062,5 +2204,4 @@ public class UserTestITCase extends Abst
                 "password123",
                 connObject.getAttrMap().get(Name.NAME).getValues().get(0)));
     }
-
 }