You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by md...@apache.org on 2015/08/14 10:30:52 UTC

[23/31] syncope git commit: [SYNCOPE-652] Now account lockout is working properly again

[SYNCOPE-652] Now account lockout is working properly again


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/5cf6aae8
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/5cf6aae8
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/5cf6aae8

Branch: refs/heads/SYNCOPE-156
Commit: 5cf6aae8274d8e10b884ec9faeca54f623234836
Parents: 6dfedd8
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Tue Aug 11 08:18:54 2015 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Aug 13 17:16:51 2015 +0200

----------------------------------------------------------------------
 .../client/console/rest/UserRestClient.java     |   6 +-
 .../syncope/common/lib/mod/StatusMod.java       |   2 +
 .../common/rest/api/service/UserService.java    |   2 +-
 .../core/misc/security/AuthDataAccessor.java    | 282 +++++++++++++++++++
 .../security/SyncopeAuthenticationProvider.java | 276 +++++++-----------
 .../security/SyncopeUserDetailsService.java     |  88 +-----
 .../misc/src/main/resources/securityContext.xml |   2 +
 .../core/persistence/api/dao/UserDAO.java       |   3 +
 .../core/persistence/jpa/dao/JPADomainDAO.java  |   2 +
 .../core/persistence/jpa/dao/JPAUserDAO.java    |  21 +-
 .../api/UserProvisioningManager.java            |   3 +-
 .../core/provisioning/api/UserSuspender.java    |  26 --
 .../java/DefaultUserProvisioningManager.java    |  13 +-
 .../provisioning/java/UserSuspenderImpl.java    |  51 ----
 .../java/data/UserDataBinderImpl.java           |   2 +-
 .../core/rest/cxf/service/UserServiceImpl.java  |   5 +-
 .../activiti/ActivitiUserWorkflowAdapter.java   |  15 +-
 .../core/workflow/api/UserWorkflowAdapter.java  |   9 +-
 .../java/AbstractUserWorkflowAdapter.java       |  54 +++-
 .../camel/AbstractCamelProvisioningManager.java |   4 +-
 .../camel/CamelUserProvisioningManager.java     |  10 +-
 .../processor/UserInnerSuspendProcessor.java    |  61 ----
 .../processor/UserInternalSuspendProcessor.java |  61 ++++
 .../src/main/resources/userRoutes.xml           |  12 +-
 .../core/reference/AuthenticationITCase.java    |   5 +-
 .../syncope/fit/core/reference/UserITCase.java  |  33 ++-
 .../fit/core/reference/VirAttrITCase.java       |   6 +-
 pom.xml                                         |   2 +-
 28 files changed, 559 insertions(+), 497 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
index 5c5c062..d161658 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
@@ -124,20 +124,22 @@ public class UserRestClient extends AbstractAnyRestClient {
 
     public void suspend(final String etag, final long userKey, final List<StatusBean> statuses) {
         StatusMod statusMod = StatusUtils.buildStatusMod(statuses, false);
+        statusMod.setKey(userKey);
         statusMod.setType(StatusMod.ModType.SUSPEND);
         synchronized (this) {
             UserService service = getService(etag, UserService.class);
-            service.status(userKey, statusMod);
+            service.status(statusMod);
             resetClient(UserService.class);
         }
     }
 
     public void reactivate(final String etag, final long userKey, final List<StatusBean> statuses) {
         StatusMod statusMod = StatusUtils.buildStatusMod(statuses, true);
+        statusMod.setKey(userKey);
         statusMod.setType(StatusMod.ModType.REACTIVATE);
         synchronized (this) {
             UserService service = getService(etag, UserService.class);
-            service.status(userKey, statusMod);
+            service.status(statusMod);
             resetClient(UserService.class);
         }
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/common/lib/src/main/java/org/apache/syncope/common/lib/mod/StatusMod.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/mod/StatusMod.java b/common/lib/src/main/java/org/apache/syncope/common/lib/mod/StatusMod.java
index 082b6f8..f989ecf 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/mod/StatusMod.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/mod/StatusMod.java
@@ -21,6 +21,7 @@ package org.apache.syncope.common.lib.mod;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.ArrayList;
 import java.util.List;
+import javax.ws.rs.PathParam;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlEnum;
@@ -66,6 +67,7 @@ public class StatusMod extends AbstractBaseBean {
      */
     private final List<String> resourceNames = new ArrayList<>();
 
+    @PathParam("key")
     public long getKey() {
         return key;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
index bf1e6b7..6dbb56c 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
@@ -107,5 +107,5 @@ public interface UserService extends AnyService<UserTO, UserMod> {
     @Path("{key}/status")
     @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    Response status(@NotNull @PathParam("key") Long key, @NotNull StatusMod statusMod);
+    Response status(@NotNull StatusMod statusMod);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/misc/src/main/java/org/apache/syncope/core/misc/security/AuthDataAccessor.java
----------------------------------------------------------------------
diff --git a/core/misc/src/main/java/org/apache/syncope/core/misc/security/AuthDataAccessor.java b/core/misc/src/main/java/org/apache/syncope/core/misc/security/AuthDataAccessor.java
new file mode 100644
index 0000000..7643f9d
--- /dev/null
+++ b/core/misc/src/main/java/org/apache/syncope/core/misc/security/AuthDataAccessor.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.misc.security;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Resource;
+import org.apache.commons.collections4.Closure;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.IteratorUtils;
+import org.apache.commons.collections4.PredicateUtils;
+import org.apache.commons.collections4.SetUtils;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.Entitlement;
+import org.apache.syncope.core.misc.AuditManager;
+import org.apache.syncope.core.misc.MappingUtils;
+import org.apache.syncope.core.misc.RealmUtils;
+import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
+import org.apache.syncope.core.persistence.api.dao.ConfDAO;
+import org.apache.syncope.core.persistence.api.dao.DomainDAO;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.Domain;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttr;
+import org.apache.syncope.core.persistence.api.entity.group.Group;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.ConnectorFactory;
+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.security.authentication.DisabledException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.transaction.annotation.Transactional;
+
+public class AuthDataAccessor {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(AuthDataAccessor.class);
+
+    @Resource(name = "adminUser")
+    protected String adminUser;
+
+    @Resource(name = "anonymousUser")
+    protected String anonymousUser;
+
+    @Autowired
+    protected DomainDAO domainDAO;
+
+    @Autowired
+    protected ConfDAO confDAO;
+
+    @Autowired
+    protected RealmDAO realmDAO;
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
+    protected GroupDAO groupDAO;
+
+    @Autowired
+    protected AnyTypeDAO anyTypeDAO;
+
+    @Autowired
+    protected ConnectorFactory connFactory;
+
+    @Autowired
+    protected AuditManager auditManager;
+
+    protected final Encryptor encryptor = Encryptor.getInstance();
+
+    @Transactional(readOnly = true)
+    public Domain findDomain(final String key) {
+        Domain domain = domainDAO.find(key);
+        if (domain == null) {
+            throw new NotFoundException("Could not find domain " + key);
+        }
+        return domain;
+    }
+
+    @Transactional(noRollbackFor = DisabledException.class)
+    public Pair<Long, Boolean> authenticate(final Authentication authentication) {
+        Long key = null;
+        Boolean authenticated = false;
+
+        User user = userDAO.find(authentication.getName());
+        if (user != null) {
+            key = user.getKey();
+
+            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");
+            }
+
+            boolean userModified = false;
+            authenticated = authenticate(user, authentication.getCredentials().toString());
+            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);
+            }
+        }
+
+        return ImmutablePair.of(key, authenticated);
+    }
+
+    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);
+
+        for (Iterator<? extends ExternalResource> itor = getPassthroughResources(user).iterator();
+                itor.hasNext() && !authenticated;) {
+
+            ExternalResource resource = itor.next();
+            String connObjectKey = null;
+            try {
+                connObjectKey = MappingUtils.getConnObjectKeyValue(user, resource.getProvision(anyTypeDAO.findUser()));
+                Uid uid = connFactory.getConnector(resource).authenticate(connObjectKey, 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(), connObjectKey, authenticated);
+        }
+
+        return authenticated;
+    }
+
+    protected Set<? extends ExternalResource> getPassthroughResources(final User user) {
+        Set<? extends ExternalResource> result = null;
+
+        // 1. look for assigned resources, pick the ones whose account policy has authentication resources
+        for (ExternalResource resource : userDAO.findAllResources(user)) {
+            if (resource.getAccountPolicy() != null && !resource.getAccountPolicy().getResources().isEmpty()) {
+                if (result == null) {
+                    result = resource.getAccountPolicy().getResources();
+                } else {
+                    result.retainAll(resource.getAccountPolicy().getResources());
+                }
+            }
+        }
+
+        // 2. look for realms, pick the ones whose account policy has authentication resources
+        for (Realm realm : realmDAO.findAncestors(user.getRealm())) {
+            if (realm.getAccountPolicy() != null && !realm.getAccountPolicy().getResources().isEmpty()) {
+                if (result == null) {
+                    result = realm.getAccountPolicy().getResources();
+                } else {
+                    result.retainAll(realm.getAccountPolicy().getResources());
+                }
+            }
+        }
+
+        return SetUtils.emptyIfNull(result);
+    }
+
+    @Transactional(readOnly = true)
+    public void audit(
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final String event,
+            final AuditElements.Result result,
+            final Object before,
+            final Object output,
+            final Object... input) {
+
+        auditManager.audit(type, category, subcategory, event, result, before, output, input);
+    }
+
+    @Transactional
+    public Set<SyncopeGrantedAuthority> load(final String username) {
+        final Set<SyncopeGrantedAuthority> authorities = new HashSet<>();
+        if (anonymousUser.equals(username)) {
+            authorities.add(new SyncopeGrantedAuthority(Entitlement.ANONYMOUS));
+        } else if (adminUser.equals(username)) {
+            CollectionUtils.collect(IteratorUtils.filteredIterator(Entitlement.values().iterator(),
+                    PredicateUtils.notPredicate(PredicateUtils.equalPredicate(Entitlement.ANONYMOUS))),
+                    new Transformer<String, SyncopeGrantedAuthority>() {
+
+                        @Override
+                        public SyncopeGrantedAuthority transform(final String entitlement) {
+                            return new SyncopeGrantedAuthority(entitlement, SyncopeConstants.ROOT_REALM);
+                        }
+                    },
+                    authorities);
+        } else {
+            User user = userDAO.find(username);
+            if (user == null) {
+                throw new UsernameNotFoundException("Could not find any user with id " + username);
+            }
+
+            // Give entitlements as assigned by roles (with realms, where applicable) - assigned either
+            // statically and dynamically
+            for (final Role role : userDAO.findAllRoles(user)) {
+                CollectionUtils.forAllDo(role.getEntitlements(), new Closure<String>() {
+
+                    @Override
+                    public void execute(final String entitlement) {
+                        SyncopeGrantedAuthority authority = new SyncopeGrantedAuthority(entitlement);
+                        authorities.add(authority);
+
+                        List<String> realmFullPahs = new ArrayList<>();
+                        CollectionUtils.collect(role.getRealms(), new Transformer<Realm, String>() {
+
+                            @Override
+                            public String transform(final Realm realm) {
+                                return realm.getFullPath();
+                            }
+                        }, realmFullPahs);
+                        authority.addRealms(realmFullPahs);
+                    }
+                });
+            }
+
+            // Give group entitlements for owned groups
+            for (Group group : groupDAO.findOwnedByUser(user.getKey())) {
+                for (String entitlement : Arrays.asList(
+                        Entitlement.GROUP_READ, Entitlement.GROUP_UPDATE, Entitlement.GROUP_DELETE)) {
+
+                    SyncopeGrantedAuthority authority = new SyncopeGrantedAuthority(entitlement);
+                    authority.addRealm(RealmUtils.getGroupOwnerRealm(group.getRealm().getFullPath(), group.getKey()));
+                    authorities.add(authority);
+                }
+            }
+        }
+
+        return authorities;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeAuthenticationProvider.java b/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeAuthenticationProvider.java
index f452128..c8fa53d 100644
--- a/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeAuthenticationProvider.java
+++ b/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeAuthenticationProvider.java
@@ -18,44 +18,26 @@
  */
 package org.apache.syncope.core.misc.security;
 
-import java.util.Date;
-import java.util.Iterator;
-import java.util.Set;
 import javax.annotation.Resource;
-import org.apache.commons.collections4.SetUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeConstants;
 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.AnyUtilsFactory;
-import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
-import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttr;
-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.MappingUtils;
-import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
-import org.apache.syncope.core.persistence.api.dao.DomainDAO;
-import org.apache.syncope.core.persistence.api.dao.NotFoundException;
-import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.entity.Domain;
-import org.apache.syncope.core.persistence.api.entity.Realm;
-import org.identityconnectors.framework.common.objects.Uid;
+import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
 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.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.transaction.annotation.Transactional;
 
 @Configurable
 public class SyncopeAuthenticationProvider implements AuthenticationProvider {
@@ -63,31 +45,10 @@ public class SyncopeAuthenticationProvider implements AuthenticationProvider {
     protected static final Logger LOG = LoggerFactory.getLogger(SyncopeAuthenticationProvider.class);
 
     @Autowired
-    protected AuditManager auditManager;
+    protected AuthDataAccessor dataAccessor;
 
     @Autowired
-    protected DomainDAO domainDAO;
-
-    @Autowired
-    protected ConfDAO confDAO;
-
-    @Autowired
-    protected RealmDAO realmDAO;
-
-    @Autowired
-    protected UserDAO userDAO;
-
-    @Autowired
-    protected PolicyDAO policyDAO;
-
-    @Autowired
-    protected AnyTypeDAO anyTypeDAO;
-
-    @Autowired
-    protected ConnectorFactory connFactory;
-
-    @Autowired
-    protected AnyUtilsFactory attrUtilsFactory;
+    protected UserProvisioningManager provisioningManager;
 
     @Resource(name = "adminUser")
     protected String adminUser;
@@ -130,19 +91,26 @@ public class SyncopeAuthenticationProvider implements AuthenticationProvider {
         this.userDetailsService = syncopeUserDetailsService;
     }
 
+    protected <T> T execWithAuthContext(final String domainKey, final Executable<T> executable) {
+        SecurityContext ctx = SecurityContextHolder.getContext();
+        AuthContextUtils.setFakeAuth(domainKey);
+        try {
+            return executable.exec();
+        } finally {
+            AuthContextUtils.clearFakeAuth();
+            SecurityContextHolder.setContext(ctx);
+        }
+    }
+
     @Override
-    @Transactional(noRollbackFor = { BadCredentialsException.class, DisabledException.class })
     public Authentication authenticate(final Authentication authentication) {
-        boolean authenticated = false;
-
-        String domainKey = authentication.getDetails() instanceof SyncopeAuthenticationDetails
-                ? SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain()
-                : null;
+        String domainKey = SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain();
         if (StringUtils.isBlank(domainKey)) {
             domainKey = SyncopeConstants.MASTER_DOMAIN;
         }
         SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).setDomain(domainKey);
 
+        boolean authenticated;
         if (anonymousUser.equals(authentication.getName())) {
             authenticated = authentication.getCredentials().toString().equals(anonymousKey);
         } else if (adminUser.equals(authentication.getName())) {
@@ -152,67 +120,90 @@ public class SyncopeAuthenticationProvider implements AuthenticationProvider {
                         CipherAlgorithm.valueOf(adminPasswordAlgorithm),
                         adminPassword);
             } else {
-                Domain domain = domainDAO.find(domainKey);
-                if (domain == null) {
-                    throw new NotFoundException("Could not find domain " + domainKey);
-                }
-
-                authenticated = encryptor.verify(
-                        authentication.getCredentials().toString(),
-                        domain.getAdminCipherAlgorithm(),
-                        domain.getAdminPwd());
+                final String domainToFind = domainKey;
+                authenticated = execWithAuthContext(SyncopeConstants.MASTER_DOMAIN, new Executable<Boolean>() {
+
+                    @Override
+                    public Boolean exec() {
+                        Domain domain = dataAccessor.findDomain(domainToFind);
+
+                        return encryptor.verify(
+                                authentication.getCredentials().toString(),
+                                domain.getAdminCipherAlgorithm(),
+                                domain.getAdminPwd());
+                    }
+                });
             }
         } else {
-            User user = userDAO.find(authentication.getName());
-
-            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);
+            final Pair<Long, Boolean> authResult =
+                    execWithAuthContext(domainKey, new Executable<Pair<Long, Boolean>>() {
+
+                        @Override
+                        public Pair<Long, Boolean> exec() {
+                            return dataAccessor.authenticate(authentication);
+                        }
+                    });
+            authenticated = authResult.getValue();
+            if (!authenticated) {
+                execWithAuthContext(domainKey, new Executable<Void>() {
+
+                    @Override
+                    public Void exec() {
+                        provisioningManager.internalSuspend(authResult.getKey());
+                        return null;
+                    }
+                });
             }
         }
 
+        final boolean isAuthenticated = 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,
-                    AuditElements.AUTHENTICATION_CATEGORY,
-                    null,
-                    AuditElements.LOGIN_EVENT,
-                    Result.SUCCESS,
-                    null,
-                    authenticated,
-                    authentication,
-                    "Successfully authenticated, with entitlements: " + token.getAuthorities());
+        if (isAuthenticated) {
+            token = execWithAuthContext(domainKey, new Executable<UsernamePasswordAuthenticationToken>() {
+
+                @Override
+                public UsernamePasswordAuthenticationToken exec() {
+                    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+                            authentication.getPrincipal(),
+                            null,
+                            userDetailsService.loadUserByUsername(authentication.getPrincipal().toString()).
+                            getAuthorities());
+                    token.setDetails(authentication.getDetails());
+
+                    dataAccessor.audit(
+                            AuditElements.EventCategoryType.REST,
+                            AuditElements.AUTHENTICATION_CATEGORY,
+                            null,
+                            AuditElements.LOGIN_EVENT,
+                            Result.SUCCESS,
+                            null,
+                            isAuthenticated,
+                            authentication,
+                            "Successfully authenticated, with entitlements: " + token.getAuthorities());
+                    return token;
+                }
+            });
 
             LOG.debug("User {} successfully authenticated, with groups {}",
                     authentication.getPrincipal(), token.getAuthorities());
         } else {
-            auditManager.audit(
-                    AuditElements.EventCategoryType.REST,
-                    AuditElements.AUTHENTICATION_CATEGORY,
-                    null,
-                    AuditElements.LOGIN_EVENT,
-                    Result.FAILURE,
-                    null,
-                    authenticated,
-                    authentication,
-                    "User " + authentication.getPrincipal() + " not authenticated");
+            execWithAuthContext(domainKey, new Executable<Void>() {
+
+                @Override
+                public Void exec() {
+                    dataAccessor.audit(
+                            AuditElements.EventCategoryType.REST,
+                            AuditElements.AUTHENTICATION_CATEGORY,
+                            null,
+                            AuditElements.LOGIN_EVENT,
+                            Result.FAILURE,
+                            null,
+                            isAuthenticated,
+                            authentication,
+                            "User " + authentication.getPrincipal() + " not authenticated");
+                    return null;
+                }
+            });
 
             LOG.debug("User {} not authenticated", authentication.getPrincipal());
 
@@ -222,84 +213,13 @@ public class SyncopeAuthenticationProvider implements AuthenticationProvider {
         return token;
     }
 
-    protected void updateLoginAttributes(final User user, final 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 assigned resources, pick the ones whose account policy has authentication resources
-        for (ExternalResource resource : userDAO.findAllResources(user)) {
-            if (resource.getAccountPolicy() != null && !resource.getAccountPolicy().getResources().isEmpty()) {
-                if (result == null) {
-                    result = resource.getAccountPolicy().getResources();
-                } else {
-                    result.retainAll(resource.getAccountPolicy().getResources());
-                }
-            }
-        }
-
-        // 2. look for realms, pick the ones whose account policy has authentication resources
-        for (Realm realm : realmDAO.findAncestors(user.getRealm())) {
-            if (realm.getAccountPolicy() != null && !realm.getAccountPolicy().getResources().isEmpty()) {
-                if (result == null) {
-                    result = realm.getAccountPolicy().getResources();
-                } else {
-                    result.retainAll(realm.getAccountPolicy().getResources());
-                }
-            }
-        }
-
-        return SetUtils.emptyIfNull(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);
-
-        for (Iterator<? extends ExternalResource> itor = getPassthroughResources(user).iterator();
-                itor.hasNext() && !authenticated;) {
-
-            ExternalResource resource = itor.next();
-            String connObjectKey = null;
-            try {
-                connObjectKey = MappingUtils.getConnObjectKeyValue(user, resource.getProvision(anyTypeDAO.findUser()));
-                Uid uid = connFactory.getConnector(resource).authenticate(connObjectKey, 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(), connObjectKey, authenticated);
-        }
-
-        return authenticated;
-    }
-
     @Override
     public boolean supports(final Class<? extends Object> type) {
         return type.equals(UsernamePasswordAuthenticationToken.class);
     }
+
+    protected interface Executable<T> {
+
+        T exec();
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeUserDetailsService.java
----------------------------------------------------------------------
diff --git a/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeUserDetailsService.java b/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeUserDetailsService.java
index a179e75..70ec6ac 100644
--- a/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeUserDetailsService.java
+++ b/core/misc/src/main/java/org/apache/syncope/core/misc/security/SyncopeUserDetailsService.java
@@ -18,104 +18,20 @@
  */
 package org.apache.syncope.core.misc.security;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import javax.annotation.Resource;
-import org.apache.commons.collections4.Closure;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.collections4.IteratorUtils;
-import org.apache.commons.collections4.PredicateUtils;
-import org.apache.commons.collections4.Transformer;
-import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.types.Entitlement;
-import org.apache.syncope.core.misc.RealmUtils;
-import org.apache.syncope.core.persistence.api.dao.GroupDAO;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
-import org.apache.syncope.core.persistence.api.entity.Realm;
-import org.apache.syncope.core.persistence.api.entity.Role;
-import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Configurable;
 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 GroupDAO groupDAO;
-
-    @Resource(name = "adminUser")
-    protected String adminUser;
-
-    @Resource(name = "anonymousUser")
-    protected String anonymousUser;
+    protected AuthDataAccessor dataAccessor;
 
     @Override
     public UserDetails loadUserByUsername(final String username) {
-        final Set<SyncopeGrantedAuthority> authorities = new HashSet<>();
-        if (anonymousUser.equals(username)) {
-            authorities.add(new SyncopeGrantedAuthority(Entitlement.ANONYMOUS));
-        } else if (adminUser.equals(username)) {
-            CollectionUtils.collect(IteratorUtils.filteredIterator(Entitlement.values().iterator(),
-                    PredicateUtils.notPredicate(PredicateUtils.equalPredicate(Entitlement.ANONYMOUS))),
-                    new Transformer<String, SyncopeGrantedAuthority>() {
-
-                        @Override
-                        public SyncopeGrantedAuthority transform(final String entitlement) {
-                            return new SyncopeGrantedAuthority(entitlement, SyncopeConstants.ROOT_REALM);
-                        }
-                    },
-                    authorities);
-        } 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 as assigned by roles (with realms, where applicable) - assigned either
-            // statically and dynamically
-            for (final Role role : userDAO.findAllRoles(user)) {
-                CollectionUtils.forAllDo(role.getEntitlements(), new Closure<String>() {
-
-                    @Override
-                    public void execute(final String entitlement) {
-                        SyncopeGrantedAuthority authority = new SyncopeGrantedAuthority(entitlement);
-                        authorities.add(authority);
-
-                        List<String> realmFullPahs = new ArrayList<>();
-                        CollectionUtils.collect(role.getRealms(), new Transformer<Realm, String>() {
-
-                            @Override
-                            public String transform(final Realm realm) {
-                                return realm.getFullPath();
-                            }
-                        }, realmFullPahs);
-                        authority.addRealms(realmFullPahs);
-                    }
-                });
-            }
-
-            // Give group entitlements for owned groups
-            for (Group group : groupDAO.findOwnedByUser(user.getKey())) {
-                for (String entitlement : Arrays.asList(
-                        Entitlement.GROUP_READ, Entitlement.GROUP_UPDATE, Entitlement.GROUP_DELETE)) {
-
-                    SyncopeGrantedAuthority authority = new SyncopeGrantedAuthority(entitlement);
-                    authority.addRealm(RealmUtils.getGroupOwnerRealm(group.getRealm().getFullPath(), group.getKey()));
-                    authorities.add(authority);
-                }
-            }
-        }
-
-        return new User(username, "<PASSWORD_PLACEHOLDER>", authorities);
+        return new User(username, "<PASSWORD_PLACEHOLDER>", dataAccessor.load(username));
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/misc/src/main/resources/securityContext.xml
----------------------------------------------------------------------
diff --git a/core/misc/src/main/resources/securityContext.xml b/core/misc/src/main/resources/securityContext.xml
index 57b1980..1022815 100644
--- a/core/misc/src/main/resources/securityContext.xml
+++ b/core/misc/src/main/resources/securityContext.xml
@@ -62,6 +62,8 @@ under the License.
     <security:csrf disabled="true"/>
   </security:http>
 
+  <bean class="org.apache.syncope.core.misc.security.AuthDataAccessor"/>
+
   <bean id="syncopeUserDetailsService" class="org.apache.syncope.core.misc.security.SyncopeUserDetailsService"/>
 
   <bean id="syncopeAuthenticationProvider" class="org.apache.syncope.core.misc.security.SyncopeAuthenticationProvider">

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
index a1e4d09..3fc9b99 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/UserDAO.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.persistence.api.dao;
 
 import java.util.Collection;
 import java.util.List;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.core.persistence.api.entity.Role;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
@@ -49,4 +50,6 @@ public interface UserDAO extends AnyDAO<User> {
     Collection<ExternalResource> findAllResources(User user);
 
     Collection<String> findAllResourceNames(User user);
+
+    Pair<Boolean, Boolean> enforcePolicies(User user);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADomainDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADomainDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADomainDAO.java
index b3bb188..eeee97c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADomainDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADomainDAO.java
@@ -24,10 +24,12 @@ import org.apache.syncope.core.persistence.api.dao.DomainDAO;
 import org.apache.syncope.core.persistence.api.entity.Domain;
 import org.apache.syncope.core.persistence.jpa.entity.JPADomain;
 import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
 
 @Repository
 public class JPADomainDAO extends AbstractDAO<Domain, String> implements DomainDAO {
 
+    @Transactional(readOnly = true)
     @Override
     public Domain find(final String key) {
         return entityManager().find(JPADomain.class, key);

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index 50564f2..4e9afe1 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -63,7 +63,6 @@ import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.persistence.jpa.entity.JPAAnyUtilsFactory;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPADynRoleMembership;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership;
-import org.apache.syncope.core.provisioning.api.UserSuspender;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Propagation;
@@ -96,9 +95,6 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
     @Autowired
     private AccountPolicyEnforcer apEnforcer;
 
-    @Autowired(required = false)
-    private UserSuspender suspender;
-
     @Override
     protected AnyUtils init() {
         return new JPAAnyUtilsFactory().getInstance(AnyTypeKind.USER);
@@ -210,11 +206,9 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
     private List<AccountPolicy> getAccountPolicies(final User user) {
         List<AccountPolicy> policies = new ArrayList<>();
 
-        AccountPolicy policy;
-
         // add resource policies        
         for (ExternalResource resource : findAllResources(user)) {
-            policy = resource.getAccountPolicy();
+            AccountPolicy policy = resource.getAccountPolicy();
             if (policy != null) {
                 policies.add(policy);
             }
@@ -222,7 +216,7 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
 
         // add realm policies
         for (Realm realm : realmDAO.findAncestors(user.getRealm())) {
-            policy = realm.getAccountPolicy();
+            AccountPolicy policy = realm.getAccountPolicy();
             if (policy != null) {
                 policies.add(policy);
             }
@@ -231,7 +225,9 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         return policies;
     }
 
-    private Pair<Boolean, Boolean> enforcePolicies(final User user) {
+    @Transactional(readOnly = true)
+    @Override
+    public Pair<Boolean, Boolean> enforcePolicies(final User user) {
         // ------------------------------
         // Verify password policies
         // ------------------------------
@@ -310,18 +306,13 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         JPAUser.class.cast(merged).setClearPassword(clearPwd);
 
         // 4. enforce password and account policies
-        Pair<Boolean, Boolean> enforceSuspend = null;
         try {
-            enforceSuspend = enforcePolicies(merged);
+            enforcePolicies(merged);
         } catch (InvalidEntityException e) {
             entityManager().remove(merged);
             throw e;
         }
 
-        if (suspender != null && enforceSuspend.getKey()) {
-            suspender.suspend(user, enforceSuspend.getValue());
-        }
-
         roleDAO.refreshDynMemberships(merged);
         groupDAO.refreshDynMemberships(merged);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserProvisioningManager.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserProvisioningManager.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserProvisioningManager.java
index 3378707..21acad2 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserProvisioningManager.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserProvisioningManager.java
@@ -26,7 +26,6 @@ import org.apache.syncope.common.lib.mod.StatusMod;
 import org.apache.syncope.common.lib.mod.UserMod;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult;
 
 public interface UserProvisioningManager extends ProvisioningManager<UserTO, UserMod> {
@@ -37,7 +36,7 @@ public interface UserProvisioningManager extends ProvisioningManager<UserTO, Use
 
     Pair<Long, List<PropagationStatus>> suspend(StatusMod statusMod);
 
-    void innerSuspend(User user, boolean propagate);
+    void internalSuspend(Long key);
 
     Pair<Long, List<PropagationStatus>> create(UserTO userTO, boolean storePassword);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserSuspender.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserSuspender.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserSuspender.java
deleted file mode 100644
index 610513c..0000000
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserSuspender.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.provisioning.api;
-
-import org.apache.syncope.core.persistence.api.entity.user.User;
-
-public interface UserSuspender {
-
-    void suspend(User user, boolean propagateSuspension);
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
index 0809607..38264de 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
@@ -288,19 +288,18 @@ public class DefaultUserProvisioningManager implements UserProvisioningManager {
     }
 
     @Override
-    public void innerSuspend(final User user, final boolean propagate) {
-        final WorkflowResult<Long> updated = uwfAdapter.suspend(user);
+    public void internalSuspend(final Long key) {
+        Pair<WorkflowResult<Long>, Boolean> updated = uwfAdapter.internalSuspend(key);
 
         // propagate suspension if and only if it is required by policy
-        if (propagate) {
+        if (updated != null && updated.getValue()) {
             UserMod userMod = new UserMod();
-            userMod.setKey(updated.getResult());
+            userMod.setKey(updated.getKey().getResult());
 
-            final List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(
+            List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(
                     new WorkflowResult<Pair<UserMod, Boolean>>(
                             new ImmutablePair<>(userMod, Boolean.FALSE),
-                            updated.getPropByRes(), updated.getPerformedTasks()));
-
+                            updated.getKey().getPropByRes(), updated.getKey().getPerformedTasks()));
             taskExecutor.execute(tasks);
         }
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/UserSuspenderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/UserSuspenderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/UserSuspenderImpl.java
deleted file mode 100644
index 7ff9e84..0000000
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/UserSuspenderImpl.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.provisioning.java;
-
-import org.apache.syncope.core.persistence.api.entity.user.User;
-import org.apache.syncope.core.provisioning.api.UserSuspender;
-import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-@Component
-public class UserSuspenderImpl implements UserSuspender {
-
-    private static final Logger LOG = LoggerFactory.getLogger(UserSuspenderImpl.class);
-
-    @Autowired
-    private UserProvisioningManager provisioningManager;
-
-    @Override
-    public void suspend(final User user, final boolean suspend) {
-        try {
-            LOG.debug("User {}:{} is over to max failed logins", user.getKey(), user.getUsername());
-
-            // reduce failed logins number to avoid multiple request
-            user.setFailedLogins(user.getFailedLogins() - 1);
-
-            // disable user and propagate suspension if and only if it is required by policy          
-            provisioningManager.innerSuspend(user, suspend);
-        } catch (Exception e) {
-            LOG.error("Error during user suspension", e);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index f52b941..459c052 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -88,7 +88,7 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
     public UserTO getAuthenticatedUserTO() {
         final UserTO authUserTO;
 
-        final String authUsername = AuthContextUtils.getUsername();
+        String authUsername = AuthContextUtils.getUsername();
         if (anonymousUser.equals(authUsername)) {
             authUserTO = new UserTO();
             authUserTO.setKey(-2);

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
index 391aea8..2447697 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
@@ -62,12 +62,11 @@ public class UserServiceImpl extends AbstractAnyService<UserTO, UserMod> impleme
     }
 
     @Override
-    public Response status(final Long key, final StatusMod statusMod) {
-        UserTO user = logic.read(key);
+    public Response status(final StatusMod statusMod) {
+        UserTO user = logic.read(statusMod.getKey());
 
         checkETag(user.getETagValue());
 
-        statusMod.setKey(key);
         UserTO updated = logic.status(statusMod);
         return modificationResponse(updated);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
----------------------------------------------------------------------
diff --git a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
index 1ce0d97..7a4ba38 100644
--- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
+++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
@@ -74,8 +74,6 @@ import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import org.apache.syncope.core.workflow.api.WorkflowDefinitionFormat;
 import org.apache.syncope.core.workflow.api.WorkflowException;
 import org.apache.syncope.core.workflow.java.AbstractUserWorkflowAdapter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -84,8 +82,6 @@ import org.springframework.transaction.annotation.Transactional;
  */
 public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
 
-    protected static final Logger LOG = LoggerFactory.getLogger(ActivitiUserWorkflowAdapter.class);
-
     protected static final String[] PROPERTY_IGNORE_PROPS = { "type" };
 
     public static final String WF_PROCESS_ID = "userWorkflow";
@@ -279,7 +275,7 @@ public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
     protected Set<String> doExecuteTask(final User user, final String task, final Map<String, Object> moreVariables) {
         Set<String> preTasks = getPerformedTasks(user);
 
-        final Map<String, Object> variables = new HashMap<>();
+        Map<String, Object> variables = new HashMap<>();
         variables.put(WF_EXECUTOR, AuthContextUtils.getUsername());
         variables.put(TASK, task);
 
@@ -345,7 +341,6 @@ public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
     }
 
     @Override
-    @Transactional(rollbackFor = { Throwable.class })
     protected WorkflowResult<Long> doSuspend(final User user) {
         Set<String> performedTasks = doExecuteTask(user, "suspend", null);
         updateStatus(user);
@@ -661,7 +656,7 @@ public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
     public List<WorkflowFormTO> getForms() {
         List<WorkflowFormTO> forms = new ArrayList<>();
 
-        final String authUser = AuthContextUtils.getUsername();
+        String authUser = AuthContextUtils.getUsername();
         if (adminUser.equals(authUser)) {
             forms.addAll(getForms(engine.getTaskService().createTaskQuery().
                     taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE)));
@@ -772,10 +767,9 @@ public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
         return new ImmutablePair<>(task, formData);
     }
 
-    @Transactional
     @Override
     public WorkflowFormTO claimForm(final String taskId) {
-        final String authUser = AuthContextUtils.getUsername();
+        String authUser = AuthContextUtils.getUsername();
         Pair<Task, TaskFormData> checked = checkTask(taskId, authUser);
 
         if (!adminUser.equals(authUser)) {
@@ -798,10 +792,9 @@ public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
         return getFormTO(task, checked.getValue());
     }
 
-    @Transactional
     @Override
     public WorkflowResult<UserMod> submitForm(final WorkflowFormTO form) {
-        final String authUser = AuthContextUtils.getUsername();
+        String authUser = AuthContextUtils.getUsername();
         Pair<Task, TaskFormData> checked = checkTask(form.getTaskId(), authUser);
 
         if (!checked.getKey().getOwner().equals(authUser)) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java
----------------------------------------------------------------------
diff --git a/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java b/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java
index fbf29c3..7122b84 100644
--- a/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java
+++ b/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java
@@ -22,7 +22,6 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
 import org.apache.syncope.common.lib.mod.UserMod;
 import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.core.persistence.api.entity.user.User;
 
 /**
  * Interface for calling underlying workflow implementations.
@@ -96,12 +95,12 @@ public interface UserWorkflowAdapter extends WorkflowAdapter {
     WorkflowResult<Long> suspend(Long key);
 
     /**
-     * Suspend an user.
+     * Suspend an user (used by internal authentication process)
      *
-     * @param user user to be suspended
-     * @return user just suspended
+     * @param key to be suspended
+     * @return user just suspended and information whether to propagate suspension
      */
-    WorkflowResult<Long> suspend(User user);
+    Pair<WorkflowResult<Long>, Boolean> internalSuspend(Long key);
 
     /**
      * Reactivate an user.

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
----------------------------------------------------------------------
diff --git a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
index ecc1c01..b829137 100644
--- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
+++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.workflow.java;
 
+import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.mod.UserMod;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
@@ -28,6 +29,8 @@ import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
 import org.identityconnectors.common.Base64;
 import org.identityconnectors.common.security.EncryptorFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
@@ -35,6 +38,8 @@ import org.springframework.transaction.annotation.Transactional;
 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = { Throwable.class })
 public abstract class AbstractUserWorkflowAdapter implements UserWorkflowAdapter {
 
+    protected static final Logger LOG = LoggerFactory.getLogger(UserWorkflowAdapter.class);
+
     @Autowired
     protected UserDataBinder dataBinder;
 
@@ -62,8 +67,8 @@ public abstract class AbstractUserWorkflowAdapter implements UserWorkflowAdapter
     protected abstract WorkflowResult<Long> doActivate(User user, String token);
 
     @Override
-    public WorkflowResult<Long> activate(final Long userKey, final String token) {
-        return doActivate(userDAO.authFind(userKey), token);
+    public WorkflowResult<Long> activate(final Long key, final String token) {
+        return doActivate(userDAO.authFind(key), token);
     }
 
     protected abstract WorkflowResult<Pair<UserMod, Boolean>> doUpdate(User user, UserMod userMod);
@@ -76,23 +81,42 @@ public abstract class AbstractUserWorkflowAdapter implements UserWorkflowAdapter
     protected abstract WorkflowResult<Long> doSuspend(User user);
 
     @Override
-    public WorkflowResult<Long> suspend(final Long userKey) {
-        return suspend(userDAO.authFind(userKey));
-    }
+    public WorkflowResult<Long> suspend(final Long key) {
+        User user = userDAO.authFind(key);
 
-    @Override
-    public WorkflowResult<Long> suspend(final User user) {
         // set suspended flag
         user.setSuspended(Boolean.TRUE);
 
         return doSuspend(user);
     }
 
+    @Override
+    public Pair<WorkflowResult<Long>, Boolean> internalSuspend(final Long key) {
+        User user = userDAO.authFind(key);
+
+        Pair<WorkflowResult<Long>, Boolean> result = null;
+
+        Pair<Boolean, Boolean> enforce = userDAO.enforcePolicies(user);
+        if (enforce.getKey()) {
+            LOG.debug("User {} {} is over the max failed logins", user.getKey(), user.getUsername());
+
+            // reduce failed logins number to avoid multiple request       
+            user.setFailedLogins(user.getFailedLogins() - 1);
+
+            // set suspended flag
+            user.setSuspended(Boolean.TRUE);
+
+            result = ImmutablePair.of(doSuspend(user), enforce.getValue());
+        }
+
+        return result;
+    }
+
     protected abstract WorkflowResult<Long> doReactivate(User user);
 
     @Override
-    public WorkflowResult<Long> reactivate(final Long userKey) {
-        final User user = userDAO.authFind(userKey);
+    public WorkflowResult<Long> reactivate(final Long key) {
+        User user = userDAO.authFind(key);
 
         // reset failed logins
         user.setFailedLogins(0);
@@ -106,21 +130,21 @@ public abstract class AbstractUserWorkflowAdapter implements UserWorkflowAdapter
     protected abstract void doRequestPasswordReset(User user);
 
     @Override
-    public void requestPasswordReset(final Long userKey) {
-        doRequestPasswordReset(userDAO.authFind(userKey));
+    public void requestPasswordReset(final Long key) {
+        doRequestPasswordReset(userDAO.authFind(key));
     }
 
     protected abstract void doConfirmPasswordReset(User user, String token, String password);
 
     @Override
-    public void confirmPasswordReset(final Long userKey, final String token, final String password) {
-        doConfirmPasswordReset(userDAO.authFind(userKey), token, password);
+    public void confirmPasswordReset(final Long key, final String token, final String password) {
+        doConfirmPasswordReset(userDAO.authFind(key), token, password);
     }
 
     protected abstract void doDelete(User user);
 
     @Override
-    public void delete(final Long userKey) {
-        doDelete(userDAO.authFind(userKey));
+    public void delete(final Long key) {
+        doDelete(userDAO.authFind(key));
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/AbstractCamelProvisioningManager.java
----------------------------------------------------------------------
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/AbstractCamelProvisioningManager.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/AbstractCamelProvisioningManager.java
index 516e798..1a6d7df 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/AbstractCamelProvisioningManager.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/AbstractCamelProvisioningManager.java
@@ -68,7 +68,7 @@ abstract class AbstractCamelProvisioningManager {
         template.send(uri, exchange);
     }
 
-    protected void sendMessage(final String uri, final Object obj, final Map<String, Object> properties) {
+    protected void sendMessage(final String uri, final Object body, final Map<String, Object> properties) {
         Exchange exchange = new DefaultExchange(getContext());
 
         for (Map.Entry<String, Object> property : properties.entrySet()) {
@@ -77,7 +77,7 @@ abstract class AbstractCamelProvisioningManager {
         }
 
         DefaultMessage message = new DefaultMessage();
-        message.setBody(obj);
+        message.setBody(body);
         exchange.setIn(message);
         ProducerTemplate template = getContext().createProducerTemplate();
         template.send(uri, exchange);

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
----------------------------------------------------------------------
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
index 8b5ad89..fa0b84b 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
@@ -34,7 +34,6 @@ import org.apache.syncope.common.lib.mod.UserMod;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.PropagationByResource;
-import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
 import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult;
@@ -319,13 +318,10 @@ public class CamelUserProvisioningManager extends AbstractCamelProvisioningManag
     }
 
     @Override
-    public void innerSuspend(final User user, final boolean propagate) {
-        PollingConsumer pollingConsumer = getConsumer("direct:innerSuspendUserPort");
+    public void internalSuspend(final Long key) {
+        PollingConsumer pollingConsumer = getConsumer("direct:internalSuspendUserPort");
 
-        Map<String, Object> props = new HashMap<>();
-        props.put("propagate", propagate);
-
-        sendMessage("direct:innerSuspendUser", user.getKey(), props);
+        sendMessage("direct:internalSuspendUser", key);
 
         Exchange exchange = pollingConsumer.receive();
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserInnerSuspendProcessor.java
----------------------------------------------------------------------
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserInnerSuspendProcessor.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserInnerSuspendProcessor.java
deleted file mode 100644
index be3e6ac..0000000
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserInnerSuspendProcessor.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.provisioning.camel.processor;
-
-import java.util.List;
-import org.apache.camel.Exchange;
-import org.apache.camel.Processor;
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
-import org.apache.syncope.common.lib.mod.UserMod;
-import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
-import org.apache.syncope.core.provisioning.api.WorkflowResult;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-@Component
-public class UserInnerSuspendProcessor implements Processor {
-
-    @Autowired
-    protected PropagationManager propagationManager;
-
-    @Autowired
-    protected PropagationTaskExecutor taskExecutor;
-
-    @Override
-    public void process(final Exchange exchange) {
-        @SuppressWarnings("unchecked")
-        WorkflowResult<Long> updated = (WorkflowResult) exchange.getIn().getBody();
-        Boolean propagate = exchange.getProperty("propagate", Boolean.class);
-
-        if (propagate) {
-            UserMod userMod = new UserMod();
-            userMod.setKey(updated.getResult());
-
-            List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(
-                    new WorkflowResult<Pair<UserMod, Boolean>>(
-                            new ImmutablePair<>(userMod, Boolean.FALSE),
-                            updated.getPropByRes(), updated.getPerformedTasks()));
-            taskExecutor.execute(tasks);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserInternalSuspendProcessor.java
----------------------------------------------------------------------
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserInternalSuspendProcessor.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserInternalSuspendProcessor.java
new file mode 100644
index 0000000..73e63ad
--- /dev/null
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserInternalSuspendProcessor.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.camel.processor;
+
+import java.util.List;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class UserInternalSuspendProcessor implements Processor {
+
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    @Autowired
+    protected PropagationTaskExecutor taskExecutor;
+
+    @Override
+    public void process(final Exchange exchange) {
+        @SuppressWarnings("unchecked")
+        Pair<WorkflowResult<Long>, Boolean> updated = (Pair) exchange.getIn().getBody();
+
+        // propagate suspension if and only if it is required by policy
+        if (updated != null && updated.getValue()) {
+            UserMod userMod = new UserMod();
+            userMod.setKey(updated.getKey().getResult());
+
+            List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(
+                    new WorkflowResult<Pair<UserMod, Boolean>>(
+                            new ImmutablePair<>(userMod, Boolean.FALSE),
+                            updated.getKey().getPropByRes(), updated.getKey().getPerformedTasks()));
+            taskExecutor.execute(tasks);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/ext/camel/provisioning-camel/src/main/resources/userRoutes.xml
----------------------------------------------------------------------
diff --git a/ext/camel/provisioning-camel/src/main/resources/userRoutes.xml b/ext/camel/provisioning-camel/src/main/resources/userRoutes.xml
index aca6a46..38e48f3 100644
--- a/ext/camel/provisioning-camel/src/main/resources/userRoutes.xml
+++ b/ext/camel/provisioning-camel/src/main/resources/userRoutes.xml
@@ -198,18 +198,18 @@ under the License.
     <to uri="direct:deprovisionPort"/>              
   </route>
     
-  <route id="innerSuspendUser">
-    <from uri="direct:innerSuspendUser"/>
+  <route id="internalSuspendUser">
+    <from uri="direct:internalSuspendUser"/>
     <doTry>
-      <bean ref="uwfAdapter" method="suspend(${body})"/>
-      <process ref="userInnerSuspendProcessor"/>
-      <to uri="direct:innerSuspendUserPort"/>
+      <bean ref="uwfAdapter" method="internalSuspend(${body})"/>
+      <process ref="userInternalSuspendProcessor"/>
+      <to uri="direct:internalSuspendUserPort"/>
       <doCatch>        
         <exception>java.lang.RuntimeException</exception>
         <handled>
           <constant>false</constant>
         </handled>
-        <to uri="direct:innerSuspendUserPort"/>
+        <to uri="direct:internalSuspendUserPort"/>
       </doCatch>
     </doTry>  
   </route>

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/AuthenticationITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/AuthenticationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/AuthenticationITCase.java
index 9d810b6..6378631 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/AuthenticationITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/AuthenticationITCase.java
@@ -258,7 +258,7 @@ public class AuthenticationITCase extends AbstractITCase {
         assertEquals(0, getFailedLogins(userService4, userId));
     }
 
-    //@Test
+    @Test
     public void checkUserSuspension() {
         UserTO userTO = UserITCase.getUniqueSampleTO("checkSuspension@syncope.apache.org");
         userTO.setRealm("/odd");
@@ -292,8 +292,9 @@ public class AuthenticationITCase extends AbstractITCase {
         assertReadFails(goodPwdClient);
 
         StatusMod reactivate = new StatusMod();
+        reactivate.setKey(userTO.getKey());
         reactivate.setType(StatusMod.ModType.REACTIVATE);
-        userTO = userService.status(userTO.getKey(), reactivate).readEntity(UserTO.class);
+        userTO = userService.status(reactivate).readEntity(UserTO.class);
         assertNotNull(userTO);
         assertEquals("active", userTO.getStatus());
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/5cf6aae8/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserITCase.java
index 4cecdf7..048259d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/UserITCase.java
@@ -818,9 +818,10 @@ public class UserITCase extends AbstractITCase {
         assertEquals("created", userTO.getStatus());
 
         StatusMod statusMod = new StatusMod();
+        statusMod.setKey(userTO.getKey());
         statusMod.setType(StatusMod.ModType.ACTIVATE);
         statusMod.setToken(userTO.getToken());
-        userTO = userService.status(userTO.getKey(), statusMod).readEntity(UserTO.class);
+        userTO = userService.status(statusMod).readEntity(UserTO.class);
 
         assertNotNull(userTO);
         assertNull(userTO.getToken());
@@ -844,14 +845,16 @@ public class UserITCase extends AbstractITCase {
                 : "created", userTO.getStatus());
 
         StatusMod statusMod = new StatusMod();
+        statusMod.setKey(userTO.getKey());
         statusMod.setType(StatusMod.ModType.SUSPEND);
-        userTO = userService.status(userTO.getKey(), statusMod).readEntity(UserTO.class);
+        userTO = userService.status(statusMod).readEntity(UserTO.class);
         assertNotNull(userTO);
         assertEquals("suspended", userTO.getStatus());
 
         statusMod = new StatusMod();
+        statusMod.setKey(userTO.getKey());
         statusMod.setType(StatusMod.ModType.REACTIVATE);
-        userTO = userService.status(userTO.getKey(), statusMod).readEntity(UserTO.class);
+        userTO = userService.status(statusMod).readEntity(UserTO.class);
         assertNotNull(userTO);
         assertEquals("active", userTO.getStatus());
     }
@@ -875,50 +878,53 @@ public class UserITCase extends AbstractITCase {
         assertEquals(ActivitiDetector.isActivitiEnabledForUsers(syncopeService)
                 ? "active"
                 : "created", userTO.getStatus());
-        long userId = userTO.getKey();
+        long userKey = userTO.getKey();
 
         // Suspend with effect on syncope, ldap and db => user should be suspended in syncope and all resources
         StatusMod statusMod = new StatusMod();
+        statusMod.setKey(userKey);
         statusMod.setType(StatusMod.ModType.SUSPEND);
         statusMod.setOnSyncope(true);
         statusMod.getResourceNames().add(RESOURCE_NAME_TESTDB);
         statusMod.getResourceNames().add(RESOURCE_NAME_LDAP);
-        userTO = userService.status(userId, statusMod).readEntity(UserTO.class);
+        userTO = userService.status(statusMod).readEntity(UserTO.class);
         assertNotNull(userTO);
         assertEquals("suspended", userTO.getStatus());
 
         ConnObjectTO connObjectTO =
-                resourceService.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userId);
+                resourceService.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userKey);
         assertFalse(getBooleanAttribute(connObjectTO, OperationalAttributes.ENABLE_NAME));
 
-        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userId);
+        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userKey);
         assertNotNull(connObjectTO);
 
         // Suspend and reactivate only on ldap => db and syncope should still show suspended
         statusMod = new StatusMod();
+        statusMod.setKey(userKey);
         statusMod.setType(StatusMod.ModType.SUSPEND);
         statusMod.setOnSyncope(false);
         statusMod.getResourceNames().add(RESOURCE_NAME_LDAP);
-        userService.status(userId, statusMod);
+        userService.status(statusMod);
         statusMod.setType(StatusMod.ModType.REACTIVATE);
-        userTO = userService.status(userId, statusMod).readEntity(UserTO.class);
+        userTO = userService.status(statusMod).readEntity(UserTO.class);
         assertNotNull(userTO);
         assertEquals("suspended", userTO.getStatus());
 
-        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userId);
+        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userKey);
         assertFalse(getBooleanAttribute(connObjectTO, OperationalAttributes.ENABLE_NAME));
 
         // Reactivate on syncope and db => syncope and db should show the user as active
         statusMod = new StatusMod();
+        statusMod.setKey(userKey);
         statusMod.setType(StatusMod.ModType.REACTIVATE);
         statusMod.setOnSyncope(true);
         statusMod.getResourceNames().add(RESOURCE_NAME_TESTDB);
 
-        userTO = userService.status(userId, statusMod).readEntity(UserTO.class);
+        userTO = userService.status(statusMod).readEntity(UserTO.class);
         assertNotNull(userTO);
         assertEquals("active", userTO.getStatus());
 
-        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userId);
+        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userKey);
         assertTrue(getBooleanAttribute(connObjectTO, OperationalAttributes.ENABLE_NAME));
     }
 
@@ -1795,7 +1801,8 @@ public class UserITCase extends AbstractITCase {
         final ResourceAssociationMod associationMod = new ResourceAssociationMod();
         associationMod.getTargetResources().addAll(CollectionWrapper.wrap(RESOURCE_NAME_CSV, ResourceKey.class));
 
-        assertNotNull(userService.associate(actual.getKey(), ResourceAssociationAction.LINK, associationMod).readEntity(BulkActionResult.class));
+        assertNotNull(userService.associate(actual.getKey(), ResourceAssociationAction.LINK, associationMod).readEntity(
+                BulkActionResult.class));
 
         actual = userService.read(actual.getKey());
         assertNotNull(actual);