You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2021/06/29 13:01:22 UTC

[syncope] branch master updated: [SYNCOPE-129] Delegation (#274) (#276)

This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 524fa23  [SYNCOPE-129] Delegation (#274) (#276)
524fa23 is described below

commit 524fa237cb52c195de2ddd1478215a83d1ea5ec4
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Tue Jun 29 15:01:14 2021 +0200

    [SYNCOPE-129] Delegation (#274) (#276)
---
 .../any}/MergeLinkedAccountsResourcesPanel.java    |   6 +-
 .../any}/MergeLinkedAccountsReviewPanel.java       |   6 +-
 .../any}/MergeLinkedAccountsSearchPanel.java       |  25 +-
 .../any/MergeLinkedAccountsWizardBuilder.java      |   3 -
 .../any}/MergeLinkedAccountsResourcesPanel.html    |   0
 .../MergeLinkedAccountsResourcesPanel.properties   |   0
 ...geLinkedAccountsResourcesPanel_fr_CA.properties |   0
 ...MergeLinkedAccountsResourcesPanel_it.properties |   0
 ...MergeLinkedAccountsResourcesPanel_ja.properties |   0
 ...geLinkedAccountsResourcesPanel_pt_BR.properties |   0
 ...MergeLinkedAccountsResourcesPanel_ru.properties |   0
 .../any}/MergeLinkedAccountsReviewPanel.html       |   0
 .../any}/MergeLinkedAccountsReviewPanel.properties |   0
 ...MergeLinkedAccountsReviewPanel_fr_CA.properties |   0
 .../MergeLinkedAccountsReviewPanel_it.properties   |   0
 .../MergeLinkedAccountsReviewPanel_ja.properties   |   0
 ...MergeLinkedAccountsReviewPanel_pt_BR.properties |   0
 .../MergeLinkedAccountsReviewPanel_ru.properties   |   0
 .../any}/MergeLinkedAccountsSearchPanel.html       |   0
 .../any}/MergeLinkedAccountsSearchPanel.properties |   0
 ...MergeLinkedAccountsSearchPanel_fr_CA.properties |   0
 .../MergeLinkedAccountsSearchPanel_it.properties   |   0
 .../MergeLinkedAccountsSearchPanel_ja.properties   |   0
 ...MergeLinkedAccountsSearchPanel_pt_BR.properties |   0
 .../MergeLinkedAccountsSearchPanel_ru.properties   |   0
 .../ui/commons/wizards/AjaxWizardMgtButtonBar.java |   2 +-
 .../client/console/SyncopeConsoleSession.java      |  27 +-
 .../client/console/commons/IdRepoConstants.java    |   2 +
 .../notifications/NotificationWizardBuilder.java   |   7 +-
 .../syncope/client/console/pages/BasePage.java     |  35 ++-
 .../syncope/client/console/pages/Security.java     |  55 +++--
 .../console/panels/ApplicationDirectoryPanel.java  |   1 -
 .../console/panels/DashboardOverviewPanel.java     |   2 +-
 .../console/panels/DelegationDirectoryPanel.java   | 223 +++++++++++++++++
 .../console/panels/DelegationSelectionPanel.java   |  57 +++++
 .../console/panels/SecurityQuestionsPanel.java     |   4 +-
 .../console/panels/search/SearchClausePanel.java   |   2 +-
 .../client/console/rest/ApplicationRestClient.java |   1 -
 ...onRestClient.java => DelegationRestClient.java} |  25 +-
 .../repeater/data/table/AjaxFallbackDataTable.java |   1 -
 .../markup/html/bootstrap/dialog/BaseModal.java    |   2 +-
 .../console/wizards/DelegationWizardBuilder.java   | 210 ++++++++++++++++
 .../console/wizards/UserSelectionWizardStep.java   |  95 +++++++
 .../client/console/wizards/any/Ownership.java      |   2 +-
 .../console/SyncopeWebApplication.properties       |   1 +
 .../console/SyncopeWebApplication_fr_CA.properties |   1 +
 .../console/SyncopeWebApplication_it.properties    |   1 +
 .../console/SyncopeWebApplication_ja.properties    |   1 +
 .../console/SyncopeWebApplication_pt_BR.properties |   1 +
 .../console/SyncopeWebApplication_ru.properties    |   1 +
 .../syncope/client/console/pages/BasePage.html     |  17 ++
 .../client/console/pages/BasePage.properties       |   2 +
 .../client/console/pages/BasePage_it.properties    |   2 +
 .../client/console/pages/BasePage_ja.properties    |   2 +
 .../client/console/pages/BasePage_pt_BR.properties |   2 +
 .../client/console/pages/BasePage_ru.properties    |   2 +
 .../client/console/pages/Security.properties       |   1 +
 .../client/console/pages/Security_it.properties    |   1 +
 .../client/console/pages/Security_ja.properties    |   1 +
 .../client/console/pages/Security_pt_BR.properties |   1 +
 .../client/console/pages/Security_ru.properties    |   1 +
 .../console/pages/pages/BasePage_fr_CA.properties  |   8 +-
 .../console/pages/pages/Security_fr_CA.properties  |   9 +-
 .../DelegationDirectoryPanel.properties}           |   8 +-
 .../DelegationDirectoryPanel_fr_CA.properties}     |   8 +-
 .../panels/DelegationDirectoryPanel_it.properties} |   8 +-
 .../DelegationDirectoryPanel_ja.properties}        |   8 +-
 .../DelegationDirectoryPanel_pt_BR.properties}     |   8 +-
 .../panels/DelegationDirectoryPanel_ru.properties} |  14 +-
 .../console/panels/DelegationSelectionPanel.html}  |   7 +-
 .../client/console/panels/MembersTogglePanel.html  |   2 +-
 .../tasks/ExecutionsDirectoryPanel_it.properties   |   4 +-
 .../markup/html/form/ActionsPanel.properties       |  10 +-
 .../markup/html/form/ActionsPanel_fr_CA.properties |  10 +-
 .../markup/html/form/ActionsPanel_it.properties    |  12 +-
 .../markup/html/form/ActionsPanel_ja.properties    |  10 +-
 .../markup/html/form/ActionsPanel_pt_BR.properties |  10 +-
 .../markup/html/form/ActionsPanel_ru.properties    |  10 +-
 .../wizards/DelegationWizardBuilder$Roles.html}    |   7 +-
 .../wizards/DelegationWizardBuilder$StartEnd.html} |  10 +-
 .../wizards/DelegationWizardBuilder$Users.html}    |  10 +-
 .../console/wizards/UserSelectionWizardStep.html}  |   8 +-
 .../syncope/client/console/AbstractTest.java       |   6 +-
 .../client/lib/AnonymousAuthenticationHandler.java |   1 -
 .../client/lib/BasicAuthenticationHandler.java     |   1 -
 .../apache/syncope/client/lib/SyncopeClient.java   |  26 +-
 .../apache/syncope/common/lib/to/DelegationTO.java | 121 +++++++++
 .../org/apache/syncope/common/lib/to/UserTO.java   |  22 ++
 .../common/lib/types/IdRepoEntitlement.java        |  10 +
 .../syncope/common/rest/api/RESTHeaders.java       |   4 +
 .../syncope/common/rest/api/beans/AuditQuery.java  |   4 +-
 .../common/rest/api/service/DelegationService.java | 122 +++++++++
 .../common/rest/api/service/UserSelfService.java   |   6 +-
 .../syncope/core/logic/ApplicationLogic.java       |   1 -
 .../apache/syncope/core/logic/DelegationLogic.java | 164 +++++++++++++
 .../syncope/core/logic/LogicInvocationHandler.java |  29 ++-
 .../org/apache/syncope/core/logic/UserLogic.java   |  14 +-
 .../core/rest/cxf/SyncopeOpenApiCustomizer.java    |  24 +-
 .../rest/cxf/service/ApplicationServiceImpl.java   |   1 -
 ...ServiceImpl.java => DelegationServiceImpl.java} |  27 +-
 .../core/rest/cxf/service/UserSelfServiceImpl.java |   7 +-
 .../core/persistence/api/dao/DelegationDAO.java    |  37 +--
 .../core/persistence/api/dao/LoggerDAO.java        |  15 +-
 .../syncope/core/persistence/api/dao/UserDAO.java  |   2 +
 .../core/persistence/api/entity/Delegation.java}   |  32 ++-
 .../core/persistence/jpa/dao/JPADelegationDAO.java | 118 +++++++++
 .../core/persistence/jpa/dao/JPALoggerDAO.java     |  28 ++-
 .../core/persistence/jpa/dao/JPARoleDAO.java       |   6 +
 .../core/persistence/jpa/dao/JPAUserDAO.java       |  28 +++
 .../core/persistence/jpa/entity/JPADelegation.java | 129 ++++++++++
 .../persistence/jpa/entity/JPAEntityFactory.java   |   3 +
 .../jpa/validation/entity/DelegationCheck.java     |  34 ++-
 .../jpa/validation/entity/DelegationValidator.java |  59 +++++
 .../core/persistence/jpa/inner/AnySearchTest.java  |   2 +-
 .../persistence/jpa/inner/ConnInstanceTest.java    |   2 +-
 .../persistence/jpa/inner/MultitenancyTest.java    |   2 +-
 .../core/persistence/jpa/inner/ResourceTest.java   |   2 +-
 .../persistence/jpa/outer/PlainSchemaTest.java     |   2 +-
 .../core/persistence/jpa/outer/RoleTest.java       |  34 +++
 .../core/persistence/jpa/outer/UserTest.java       |  39 +++
 .../api/data/DelegationDataBinder.java             |  17 +-
 .../java/data/DelegationDataBinderImpl.java        | 115 +++++++++
 .../provisioning/java/data/UserDataBinderImpl.java |  13 +-
 .../java/job/AbstractSchedTaskJobDelegate.java     |   6 +-
 .../DefaultNotificationJobDelegate.java            |   8 +-
 .../AbstractPropagationTaskExecutor.java           |   4 +-
 .../java/pushpull/AbstractPullResultHandler.java   |   4 +-
 .../java/pushpull/AbstractPushResultHandler.java   |   2 +-
 .../pushpull/DefaultRealmPullResultHandler.java    |   4 +-
 .../pushpull/DefaultRealmPushResultHandler.java    |   2 +-
 .../java/data/ResourceDataBinderTest.java          |   2 +-
 .../core/spring/security/AuthContextUtils.java     |  56 +++--
 .../core/spring/security/AuthDataAccessor.java     | 208 ++++++++++------
 .../security/SyncopeAuthenticationDetails.java     |  13 +-
 .../SyncopeAuthenticationDetailsSource.java        |   1 -
 .../UsernamePasswordAuthenticationProvider.java    |  61 +++--
 .../workflow/java/AbstractWorkflowAdapter.java     |   8 +-
 .../apache/syncope/core/logic/OIDCC4UILogic.java   |   2 +-
 .../apache/syncope/core/logic/SAML2SP4UILogic.java |   2 +-
 .../apache/syncope/core/logic/SCIMDataBinder.java  |  42 ++--
 ...sterUsernamePasswordAuthenticationProvider.java |   1 +
 .../fit/core/reference/CustomJWTSSOProvider.java   |   2 +-
 .../org/apache/syncope/fit/AbstractITCase.java     |  20 ++
 .../fit/console/SecurityQuestionsITCase.java       |   4 +-
 .../org/apache/syncope/fit/core/AuditITCase.java   |  48 ++--
 .../syncope/fit/core/AuthenticationITCase.java     |  13 +-
 .../apache/syncope/fit/core/DelegationITCase.java  | 272 +++++++++++++++++++++
 .../org/apache/syncope/fit/core/JWTITCase.java     |   5 +-
 .../apache/syncope/fit/core/PullTaskITCase.java    |   8 +-
 .../org/apache/syncope/fit/core/UserITCase.java    |   4 +-
 .../apache/syncope/fit/core/UserIssuesITCase.java  |   4 +-
 .../apache/syncope/fit/core/UserSelfITCase.java    |  10 +-
 .../asciidoc/reference-guide/concepts/roles.adoc   |  20 ++
 .../workingwithapachesyncope/restfulservices.adoc  |  10 +
 154 files changed, 2664 insertions(+), 503 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.java
similarity index 97%
rename from client/idm/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.java
rename to client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.java
index 509dc06..ce208f9 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.panels;
+package org.apache.syncope.client.console.wizards.any;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
 import java.util.ArrayList;
@@ -27,11 +27,11 @@ import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.commons.IdMConstants;
 import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
 import org.apache.syncope.client.console.pages.BasePage;
-import org.apache.syncope.client.console.panels.MergeLinkedAccountsResourcesPanel.ResourceSelectionDirectoryPanel.ResourcesDataProvider;
+import org.apache.syncope.client.console.panels.DirectoryPanel;
 import org.apache.syncope.client.console.rest.ResourceRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
-import org.apache.syncope.client.console.wizards.any.MergeLinkedAccountsWizardModel;
+import org.apache.syncope.client.console.wizards.any.MergeLinkedAccountsResourcesPanel.ResourceSelectionDirectoryPanel.ResourcesDataProvider;
 import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
 import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.types.IdMEntitlement;
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.java
similarity index 97%
rename from client/idm/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.java
rename to client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.java
index bbe97be..955ba56 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.panels;
+package org.apache.syncope.client.console.wizards.any;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
 import java.util.ArrayList;
@@ -25,11 +25,11 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
-import org.apache.syncope.client.console.panels.MergeLinkedAccountsReviewPanel.LinkedAccountsReviewDirectoryPanel.LinkedAccountsDataProvider;
+import org.apache.syncope.client.console.panels.DirectoryPanel;
 import org.apache.syncope.client.console.rest.ResourceRestClient;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
-import org.apache.syncope.client.console.wizards.any.MergeLinkedAccountsWizardModel;
+import org.apache.syncope.client.console.wizards.any.MergeLinkedAccountsReviewPanel.LinkedAccountsReviewDirectoryPanel.LinkedAccountsDataProvider;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
 import org.apache.syncope.common.lib.to.LinkedAccountTO;
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java
similarity index 85%
rename from client/idm/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.java
rename to client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java
index 5e8d55a..3f76ad6 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java
@@ -16,8 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.panels;
+package org.apache.syncope.client.console.wizards.any;
 
+import java.util.ArrayList;
 import org.apache.syncope.client.console.panels.search.AnySelectionDirectoryPanel;
 import org.apache.syncope.client.console.panels.search.SearchClausePanel;
 import org.apache.syncope.client.console.panels.search.SearchUtils;
@@ -26,7 +27,6 @@ import org.apache.syncope.client.console.panels.search.UserSelectionDirectoryPan
 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
 import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 import org.apache.syncope.client.console.rest.UserRestClient;
-import org.apache.syncope.client.console.wizards.any.MergeLinkedAccountsWizardModel;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AnyTypeTO;
@@ -42,9 +42,8 @@ import org.apache.wicket.model.Model;
 import org.apache.wicket.model.StringResourceModel;
 import org.apache.wicket.model.util.ListModel;
 
-import java.util.ArrayList;
-
 public class MergeLinkedAccountsSearchPanel extends WizardStep {
+
     private static final long serialVersionUID = 1221037007528732347L;
 
     private final WebMarkupContainer ownerContainer;
@@ -73,14 +72,14 @@ public class MergeLinkedAccountsSearchPanel extends WizardStep {
 
         userSearchFragment = new Fragment("search", "userSearchFragment", this);
         userSearchPanel = UserSearchPanel.class.cast(new UserSearchPanel.Builder(
-            new ListModel<>(new ArrayList<>())).required(false).enableSearch(MergeLinkedAccountsSearchPanel.this).
-            build("usersearch"));
+                new ListModel<>(new ArrayList<>())).required(false).enableSearch(MergeLinkedAccountsSearchPanel.this).
+                build("usersearch"));
         userSearchFragment.add(userSearchPanel);
 
         AnyTypeTO anyTypeTO = anyTypeRestClient.read(AnyTypeKind.USER.name());
         userDirectoryPanel = UserSelectionDirectoryPanel.class.cast(new UserSelectionDirectoryPanel.Builder(
-            anyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
-            build("searchResult"));
+                anyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
+                build("searchResult"));
 
         userSearchFragment.add(userDirectoryPanel);
         ownerContainer.add(userSearchFragment);
@@ -91,19 +90,19 @@ public class MergeLinkedAccountsSearchPanel extends WizardStep {
         if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
             final AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
             final String fiql = "username!~" + this.wizardModel.getBaseUser().getUsername() + ';'
-                + SearchUtils.buildFIQL(userSearchPanel.getModel().getObject(),
-                SyncopeClient.getUserSearchConditionBuilder());
+                    + SearchUtils.buildFIQL(userSearchPanel.getModel().getObject(),
+                            SyncopeClient.getUserSearchConditionBuilder());
             userDirectoryPanel.search(fiql, target);
         } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) {
             AnySelectionDirectoryPanel.ItemSelection payload =
-                (AnySelectionDirectoryPanel.ItemSelection) event.getPayload();
+                    (AnySelectionDirectoryPanel.ItemSelection) event.getPayload();
 
             final AnyTO sel = payload.getSelection();
             this.wizardModel.setMergingUser(new UserRestClient().read(sel.getKey()));
 
             String tableId = ((Component) event.getSource()).
-                get("container:content:searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable").
-                getMarkupId();
+                    get("container:content:searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable").
+                    getMarkupId();
             String js = "$('#" + tableId + " tr').removeClass('active');";
             js += "$('#" + tableId + " td[title=" + sel.getKey() + "]').parent().addClass('active');";
             payload.getTarget().prependJavaScript(js);
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java
index 7f1fa02..408e5ea 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java
@@ -28,9 +28,6 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.pages.BasePage;
-import org.apache.syncope.client.console.panels.MergeLinkedAccountsResourcesPanel;
-import org.apache.syncope.client.console.panels.MergeLinkedAccountsReviewPanel;
-import org.apache.syncope.client.console.panels.MergeLinkedAccountsSearchPanel;
 import org.apache.syncope.client.console.panels.UserDirectoryPanel;
 import org.apache.syncope.client.console.rest.ResourceRestClient;
 import org.apache.syncope.client.console.rest.UserRestClient;
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.html
similarity index 100%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.html
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_fr_CA.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_fr_CA.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_fr_CA.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_it.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_it.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_it.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_ja.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ja.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_ja.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_pt_BR.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_pt_BR.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_pt_BR.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_ru.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ru.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsResourcesPanel_ru.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.html
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.html
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.html
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.properties
similarity index 100%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.properties
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_fr_CA.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_fr_CA.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_fr_CA.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_it.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_it.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_it.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_ja.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ja.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_ja.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_pt_BR.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_pt_BR.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_pt_BR.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_ru.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ru.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsReviewPanel_ru.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.html
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.html
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.html
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.properties
similarity index 100%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_fr_CA.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_fr_CA.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_fr_CA.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_it.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_it.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_it.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_ja.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ja.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_ja.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_pt_BR.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_pt_BR.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_pt_BR.properties
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_ru.properties
similarity index 100%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ru.properties
rename to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel_ru.properties
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizardMgtButtonBar.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizardMgtButtonBar.java
index ce68be9..118822e 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizardMgtButtonBar.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizardMgtButtonBar.java
@@ -60,7 +60,7 @@ public class AjaxWizardMgtButtonBar<T extends Serializable> extends WizardButton
     private void ajaxify(final WizardButton button) {
         button.add(new AjaxFormSubmitBehavior("click") {
 
-            private static final long serialVersionUID = 1L;
+            private static final long serialVersionUID = 18163421824742L;
 
             @Override
             protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
index d18fc34..83de4f7 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
@@ -39,7 +39,7 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.commons.lang3.time.FastDateFormat;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.console.commons.RealmsUtils;
 import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
@@ -113,6 +113,10 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
 
     protected Map<String, Set<String>> auth;
 
+    protected List<String> delegations;
+
+    protected String delegatedBy;
+
     protected Roles roles;
 
     public static SyncopeConsoleSession get() {
@@ -259,6 +263,8 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
     public void cleanup() {
         client = null;
         auth = null;
+        delegations = null;
+        delegatedBy = null;
         selfTO = null;
         services.clear();
     }
@@ -343,10 +349,27 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
         return roles;
     }
 
+    public List<String> getDelegations() {
+        return delegations;
+    }
+
+    public String getDelegatedBy() {
+        return delegatedBy;
+    }
+
+    public void setDelegatedBy(final String delegatedBy) {
+        this.delegatedBy = delegatedBy;
+
+        this.client.delegatedBy(delegatedBy);
+
+        refreshAuth(null);
+    }
+
     public void refreshAuth(final String username) {
         try {
-            Pair<Map<String, Set<String>>, UserTO> self = client.self();
+            Triple<Map<String, Set<String>>, List<String>, UserTO> self = client.self();
             auth = self.getLeft();
+            delegations = self.getMiddle();
             selfTO = self.getRight();
             roles = null;
         } catch (ForbiddenException e) {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoConstants.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoConstants.java
index 566f7f0..ea418f7 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoConstants.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoConstants.java
@@ -102,6 +102,8 @@ public final class IdRepoConstants {
 
     public static final String PREF_TYPE_EXTENSIONS_PAGINATOR_ROWS = "typeextensions.paginator.rows";
 
+    public static final String PREF_DELEGATION_PAGINATOR_ROWS = "delegation.paginator.rows";
+
     public static final String PREF_TODO_PAGINATOR_ROWS = "todo.paginator.rows";
 
     public static final String PREF_REPORT_PAGINATOR_ROWS = "report.paginator.rows";
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
index 015e625..ae38a10 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
@@ -191,7 +191,7 @@ public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<Notificatio
             super(id, model);
             setOutputMarkupId(true);
 
-            final AjaxDropDownChoicePanel<String> type =
+            AjaxDropDownChoicePanel<String> type =
                     new AjaxDropDownChoicePanel<>("about", "anyType", new Model<String>() {
 
                         private static final long serialVersionUID = -2350296434572623272L;
@@ -211,7 +211,7 @@ public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<Notificatio
             type.addRequiredLabel();
             add(type);
 
-            final ListModel<SearchClause> clauseModel = new ListModel<SearchClause>() {
+            ListModel<SearchClause> clauseModel = new ListModel<SearchClause>() {
 
                 private static final long serialVersionUID = 3769540249683319782L;
 
@@ -224,7 +224,6 @@ public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<Notificatio
                 public void setObject(final List<SearchClause> object) {
                     model.getObject().setValue(object);
                 }
-
             };
 
             WebMarkupContainer searchContainer = new WebMarkupContainer("search");
@@ -275,7 +274,7 @@ public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<Notificatio
         public Abouts(final NotificationWrapper modelObject) {
             setTitleModel(new ResourceModel("about"));
 
-            final WebMarkupContainer aboutContainer = new WebMarkupContainer("about");
+            WebMarkupContainer aboutContainer = new WebMarkupContainer("about");
             aboutContainer.setOutputMarkupId(true);
             add(aboutContainer);
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index e67ef02..11a1cad 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -29,9 +29,11 @@ import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.annotations.AMPage;
 import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.console.annotations.IdMPage;
+import org.apache.syncope.client.console.panels.DelegationSelectionPanel;
 import org.apache.syncope.client.ui.commons.HttpResourceStream;
 import org.apache.syncope.client.console.rest.SyncopeRestClient;
 import org.apache.syncope.client.console.wicket.markup.head.MetaHeaderItem;
+import org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.syncope.client.console.widgets.ExtAlertWidget;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
@@ -63,6 +65,7 @@ import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.ContentDisposition;
@@ -87,7 +90,11 @@ public class BasePage extends BaseWebPage {
         add(body);
 
         // header, footer
-        body.add(new Label("username", SyncopeConsoleSession.get().getSelfTO().getUsername()));
+        String username = SyncopeConsoleSession.get().getSelfTO().getUsername();
+        if (SyncopeConsoleSession.get().getDelegatedBy() != null) {
+            username += " (" + SyncopeConsoleSession.get().getDelegatedBy() + ")";
+        }
+        body.add(new Label("username", username));
 
         // right sidebar
         PlatformInfo platformInfo = SyncopeConsoleSession.get().getPlatformInfo();
@@ -294,10 +301,6 @@ public class BasePage extends BaseWebPage {
         liContainer = new WebMarkupContainer(getLIContainerId("security"));
         confULContainer.add(liContainer);
         link = BookmarkablePageLinkBuilder.build("security", Security.class);
-        MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER,
-                String.format("%s,%s",
-                        IdRepoEntitlement.ROLE_LIST,
-                        IdRepoEntitlement.APPLICATION_LIST));
         liContainer.add(link);
 
         liContainer = new WebMarkupContainer(getLIContainerId("policies"));
@@ -327,6 +330,28 @@ public class BasePage extends BaseWebPage {
 
         body.add(new Label("domain", SyncopeConsoleSession.get().getDomain()));
 
+        WebMarkupContainer delegationsContainer = new WebMarkupContainer("delegationsContainer");
+        body.add(delegationsContainer.setOutputMarkupPlaceholderTag(true).
+                setVisible(!SyncopeConsoleSession.get().getDelegations().isEmpty()));
+        delegationsContainer.add(new Label("delegationsHeader", new ResourceModel("delegations")));
+        delegationsContainer.add(new ListView<String>("delegations", SyncopeConsoleSession.get().getDelegations()) {
+
+            @Override
+            protected void populateItem(final ListItem<String> item) {
+                item.add(new DelegationSelectionPanel("delegation", item.getModelObject()));
+            }
+        });
+
+        body.add(new IndicatingOnConfirmAjaxLink<String>("endDelegation", "confirmDelegation", true) {
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                SyncopeConsoleSession.get().setDelegatedBy(null);
+                setResponsePage(Dashboard.class);
+            }
+        }.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true).
+                setVisible(SyncopeConsoleSession.get().getDelegatedBy() != null));
+
         @SuppressWarnings("unchecked")
         Class<? extends WebPage> beforeLogout = (Class<? extends WebPage>) SyncopeConsoleSession.get().
                 getAttribute(Constants.BEFORE_LOGOUT_PAGE);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Security.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Security.java
index 56efb31..075eac0 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Security.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Security.java
@@ -22,12 +22,17 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbed
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.panels.ApplicationDirectoryPanel;
+import org.apache.syncope.client.console.panels.DelegationDirectoryPanel;
 import org.apache.syncope.client.console.panels.DynRealmDirectoryPanel;
 import org.apache.syncope.client.console.panels.RoleDirectoryPanel;
 import org.apache.syncope.client.console.panels.SecurityQuestionsPanel;
+import org.apache.syncope.client.console.wizards.DelegationWizardBuilder;
 import org.apache.syncope.client.console.wizards.role.RoleWizardBuilder;
+import org.apache.syncope.common.lib.to.DelegationTO;
 import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
 import org.apache.wicket.extensions.markup.html.tabs.ITab;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -51,19 +56,37 @@ public class Security extends BasePage {
     }
 
     private List<ITab> buildTabList() {
-        final List<ITab> tabs = new ArrayList<>();
+        List<ITab> tabs = new ArrayList<>();
 
-        tabs.add(new AbstractTab(new ResourceModel("roles")) {
+        if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.ROLE_LIST)) {
+            tabs.add(new AbstractTab(new ResourceModel("roles")) {
 
-            private static final long serialVersionUID = -6815067322125799251L;
+                private static final long serialVersionUID = -6815067322125799251L;
+
+                @Override
+                public Panel getPanel(final String panelId) {
+                    return new RoleDirectoryPanel.Builder(getPageReference()) {
+
+                        private static final long serialVersionUID = -5960765294082359003L;
+
+                    }.addNewItemPanelBuilder(
+                            new RoleWizardBuilder(new RoleTO(), getPageReference()), true).build(panelId);
+                }
+            });
+        }
+
+        tabs.add(new AbstractTab(new ResourceModel("delegations")) {
+
+            private static final long serialVersionUID = 29722178554609L;
 
             @Override
             public Panel getPanel(final String panelId) {
-                return new RoleDirectoryPanel.Builder(getPageReference()) {
+                return new DelegationDirectoryPanel.Builder(getPageReference()) {
 
-                    private static final long serialVersionUID = -5960765294082359003L;
+                    private static final long serialVersionUID = 30231721305047L;
 
-                }.addNewItemPanelBuilder(new RoleWizardBuilder(new RoleTO(), getPageReference()), true).build(panelId);
+                }.addNewItemPanelBuilder(
+                        new DelegationWizardBuilder(new DelegationTO(), getPageReference()), true).build(panelId);
             }
         });
 
@@ -81,19 +104,21 @@ public class Security extends BasePage {
             }
         });
 
-        tabs.add(new AbstractTab(new ResourceModel("applications")) {
+        if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.APPLICATION_LIST)) {
+            tabs.add(new AbstractTab(new ResourceModel("applications")) {
 
-            private static final long serialVersionUID = -6815067322125799251L;
+                private static final long serialVersionUID = -6815067322125799251L;
 
-            @Override
-            public Panel getPanel(final String panelId) {
-                return new ApplicationDirectoryPanel.Builder(getPageReference()) {
+                @Override
+                public Panel getPanel(final String panelId) {
+                    return new ApplicationDirectoryPanel.Builder(getPageReference()) {
 
-                    private static final long serialVersionUID = -5960765294082359003L;
+                        private static final long serialVersionUID = -5960765294082359003L;
 
-                }.build(panelId);
-            }
-        });
+                    }.build(panelId);
+                }
+            });
+        }
 
         tabs.add(new AbstractTab(new ResourceModel("securityQuestions")) {
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java
index b91d032..01ae391 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java
@@ -72,7 +72,6 @@ public class ApplicationDirectoryPanel extends
             super.onConfigure();
             setFooterVisible(false);
         }
-
     };
 
     protected ApplicationDirectoryPanel(final String id, final Builder builder) {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
index 2d49f0f..e503bcd 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
@@ -156,7 +156,7 @@ public class DashboardOverviewPanel extends Panel {
         if (numbers.getAnyType1() == null) {
             number = numbers.getTotalRoles();
             label = new ResourceModel("roles").getObject();
-            icon = "fa fa-users";
+            icon = "fas fa-users";
         } else {
             number = numbers.getTotalAny1();
             label = numbers.getAnyType1();
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java
new file mode 100644
index 0000000..a900fb0
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java
@@ -0,0 +1,223 @@
+/*
+ * 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.client.console.panels;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.IdRepoConstants;
+import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.panels.DelegationDirectoryPanel.DelegationDataProvider;
+import org.apache.syncope.client.console.rest.DelegationRestClient;
+import org.apache.syncope.client.console.rest.UserRestClient;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
+import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
+import org.apache.syncope.common.lib.to.DelegationTO;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
+
+public class DelegationDirectoryPanel extends
+        DirectoryPanel<DelegationTO, DelegationTO, DelegationDataProvider, DelegationRestClient> {
+
+    private static final long serialVersionUID = 28300423726398L;
+
+    private final UserRestClient userRestClient = new UserRestClient();
+
+    protected DelegationDirectoryPanel(final String id, final Builder builder) {
+        super(id, builder);
+
+        disableCheckBoxes();
+        setShowResultPage(true);
+
+        modal.size(Modal.Size.Large);
+        initResultTable();
+    }
+
+    @Override
+    protected DelegationDataProvider dataProvider() {
+        return new DelegationDataProvider(rows);
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return IdRepoConstants.PREF_DELEGATION_PAGINATOR_ROWS;
+    }
+
+    @Override
+    protected Collection<ActionLink.ActionType> getBatches() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    protected List<IColumn<DelegationTO, String>> getColumns() {
+        List<IColumn<DelegationTO, String>> columns = new ArrayList<>();
+
+        columns.add(new KeyPropertyColumn<>(
+                new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
+
+        columns.add(new AbstractColumn<DelegationTO, String>(new ResourceModel("delegating"), "delegating") {
+
+            @Override
+            public void populateItem(
+                    final Item<ICellPopulator<DelegationTO>> cellItem,
+                    final String componentId,
+                    final IModel<DelegationTO> rowModel) {
+
+                String delegating = rowModel.getObject().getDelegating();
+                if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.USER_READ)) {
+                    delegating = userRestClient.read(delegating).getUsername();
+                } else if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(delegating)) {
+                    delegating = SyncopeConsoleSession.get().getSelfTO().getUsername();
+                }
+
+                cellItem.add(new Label(componentId, delegating));
+            }
+        });
+
+        columns.add(new AbstractColumn<DelegationTO, String>(new ResourceModel("delegated"), "delegated") {
+
+            @Override
+            public void populateItem(
+                    final Item<ICellPopulator<DelegationTO>> cellItem,
+                    final String componentId,
+                    final IModel<DelegationTO> rowModel) {
+
+                String delegated = rowModel.getObject().getDelegated();
+                if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.USER_READ)) {
+                    delegated = userRestClient.read(delegated).getUsername();
+                } else if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(delegated)) {
+                    delegated = SyncopeConsoleSession.get().getSelfTO().getUsername();
+                }
+
+                cellItem.add(new Label(componentId, delegated));
+            }
+        });
+
+        columns.add(new DatePropertyColumn<>(new StringResourceModel("start", this), "start", "start"));
+
+        columns.add(new DatePropertyColumn<>(new StringResourceModel("end", this), "end", "end"));
+
+        columns.add(new PropertyColumn<>(new ResourceModel("roles"), null, "roles"));
+
+        return columns;
+    }
+
+    @Override
+    public ActionsPanel<DelegationTO> getActions(final IModel<DelegationTO> model) {
+        ActionsPanel<DelegationTO> panel = super.getActions(model);
+
+        panel.add(new ActionLink<DelegationTO>() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final DelegationTO ignore) {
+                send(DelegationDirectoryPanel.this, Broadcast.EXACT,
+                        new AjaxWizard.EditItemActionEvent<>(model.getObject(), target));
+            }
+        }, ActionLink.ActionType.EDIT, StringUtils.EMPTY);
+        panel.add(new ActionLink<DelegationTO>() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final DelegationTO ignore) {
+                try {
+                    DelegationRestClient.delete(model.getObject().getKey());
+                    SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+                    target.add(container);
+                } catch (Exception e) {
+                    LOG.error("While deleting {}", model.getObject().getKey(), e);
+                    SyncopeConsoleSession.get().onException(e);
+                }
+                ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+        }, ActionLink.ActionType.DELETE, StringUtils.EMPTY, true);
+
+        return panel;
+    }
+
+    public abstract static class Builder
+            extends DirectoryPanel.Builder<DelegationTO, DelegationTO, DelegationRestClient> {
+
+        private static final long serialVersionUID = 5530948153889495221L;
+
+        public Builder(final PageReference pageRef) {
+            super(new DelegationRestClient(), pageRef);
+        }
+
+        @Override
+        protected WizardMgtPanel<DelegationTO> newInstance(final String id, final boolean wizardInModal) {
+            return new DelegationDirectoryPanel(id, this);
+        }
+    }
+
+    protected static class DelegationDataProvider extends DirectoryDataProvider<DelegationTO> {
+
+        private static final long serialVersionUID = 28297380054779L;
+
+        private final SortableDataProviderComparator<DelegationTO> comparator;
+
+        public DelegationDataProvider(final int paginatorRows) {
+            super(paginatorRows);
+            this.comparator = new SortableDataProviderComparator<>(this);
+        }
+
+        @Override
+        public Iterator<DelegationTO> iterator(final long first, final long count) {
+            List<DelegationTO> result = DelegationRestClient.list();
+            Collections.sort(result, comparator);
+            return result.subList((int) first, (int) first + (int) count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return DelegationRestClient.list().size();
+        }
+
+        @Override
+        public IModel<DelegationTO> model(final DelegationTO object) {
+            return new CompoundPropertyModel<>(object);
+        }
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java
new file mode 100644
index 0000000..680d8fe
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java
@@ -0,0 +1,57 @@
+/*
+ * 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.client.console.panels;
+
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.pages.Dashboard;
+import org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+
+public class DelegationSelectionPanel extends Panel {
+
+    private static final long serialVersionUID = 5820439948762L;
+
+    public DelegationSelectionPanel(final String id, final String delegating) {
+        super(id);
+
+        IndicatingOnConfirmAjaxLink<String> link =
+                new IndicatingOnConfirmAjaxLink<String>("link", "confirmDelegation", true) {
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                SyncopeConsoleSession.get().setDelegatedBy(delegating);
+                setResponsePage(Dashboard.class);
+            }
+
+            @Override
+            protected void onComponentTag(final ComponentTag tag) {
+                super.onComponentTag(tag);
+                if (delegating.equals(SyncopeConsoleSession.get().getDelegatedBy())) {
+                    tag.append("class", "disabled", " ");
+                }
+            }
+        };
+        add(link);
+        link.setOutputMarkupId(true);
+        link.add(new Label("label", delegating));
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
index 64d93ce..a621bd3 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
@@ -122,7 +122,7 @@ public class SecurityQuestionsPanel extends DirectoryPanel<
 
     @Override
     public ActionsPanel<SecurityQuestionTO> getActions(final IModel<SecurityQuestionTO> model) {
-        final ActionsPanel<SecurityQuestionTO> panel = super.getActions(model);
+        ActionsPanel<SecurityQuestionTO> panel = super.getActions(model);
 
         panel.add(new ActionLink<SecurityQuestionTO>() {
 
@@ -150,7 +150,7 @@ public class SecurityQuestionsPanel extends DirectoryPanel<
                 }
                 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
             }
-        }, ActionLink.ActionType.DELETE, IdRepoEntitlement.TASK_DELETE, true);
+        }, ActionLink.ActionType.DELETE, IdRepoEntitlement.SECURITY_QUESTION_DELETE, true);
 
         return panel;
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
index 6356c61..d15188a 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
@@ -412,7 +412,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                 CheckBox checkBox = super.newCheckBox(id, model);
                 checkBox.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
 
-                    private static final long serialVersionUID = 1L;
+                    private static final long serialVersionUID = 18266219802290L;
 
                     @Override
                     protected void onUpdate(final AjaxRequestTarget target) {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java
index 25c5d14..4bab465 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java
@@ -45,5 +45,4 @@ public class ApplicationRestClient extends BaseRestClient {
     public static List<ApplicationTO> list() {
         return getService(ApplicationService.class).list();
     }
-
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/DelegationRestClient.java
similarity index 58%
copy from client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java
copy to client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/DelegationRestClient.java
index 25c5d14..a5bb730 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/ApplicationRestClient.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/DelegationRestClient.java
@@ -19,31 +19,30 @@
 package org.apache.syncope.client.console.rest;
 
 import java.util.List;
-import org.apache.syncope.common.lib.to.ApplicationTO;
-import org.apache.syncope.common.rest.api.service.ApplicationService;
+import org.apache.syncope.common.lib.to.DelegationTO;
+import org.apache.syncope.common.rest.api.service.DelegationService;
 
-public class ApplicationRestClient extends BaseRestClient {
+public class DelegationRestClient extends BaseRestClient {
 
     private static final long serialVersionUID = -381814125643246243L;
 
     public static void delete(final String key) {
-        getService(ApplicationService.class).delete(key);
+        getService(DelegationService.class).delete(key);
     }
 
-    public static ApplicationTO read(final String key) {
-        return getService(ApplicationService.class).read(key);
+    public static DelegationTO read(final String key) {
+        return getService(DelegationService.class).read(key);
     }
 
-    public static void update(final ApplicationTO applicationTO) {
-        getService(ApplicationService.class).update(applicationTO);
+    public static void update(final DelegationTO applicationTO) {
+        getService(DelegationService.class).update(applicationTO);
     }
 
-    public static void create(final ApplicationTO applicationTO) {
-        getService(ApplicationService.class).create(applicationTO);
+    public static void create(final DelegationTO applicationTO) {
+        getService(DelegationService.class).create(applicationTO);
     }
 
-    public static List<ApplicationTO> list() {
-        return getService(ApplicationService.class).list();
+    public static List<DelegationTO> list() {
+        return getService(DelegationService.class).list();
     }
-
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java
index 179ee12..09cf116 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java
@@ -104,7 +104,6 @@ public class AjaxFallbackDataTable<T extends Serializable, S> extends DataTable<
                     }
                 };
             }
-
         });
         addBottomToolbar(new AjaxDataNavigationToolbar(this, container));
         addBottomToolbar(new NoRecordsToolbar(this));
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
index c6bcf9e..861394e 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
@@ -90,7 +90,7 @@ public class BaseModal<T extends Serializable> extends Modal<T> {
 
         content = new AbstractModalPanel<T>(this, null) {
 
-            private static final long serialVersionUID = 1L;
+            private static final long serialVersionUID = -6142277554912316095L;
 
         };
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/DelegationWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/DelegationWizardBuilder.java
new file mode 100644
index 0000000..9148f64
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/DelegationWizardBuilder.java
@@ -0,0 +1,210 @@
+/*
+ * 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.client.console.wizards;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.FastDateFormat;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.rest.DelegationRestClient;
+import org.apache.syncope.client.console.rest.UserRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDateTimeFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.DelegationTO;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.extensions.wizard.WizardModel;
+import org.apache.wicket.extensions.wizard.WizardStep;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+
+public class DelegationWizardBuilder extends BaseAjaxWizardBuilder<DelegationTO> {
+
+    private static final long serialVersionUID = 16656970898539L;
+
+    private final UserRestClient userRestClient = new UserRestClient();
+
+    public DelegationWizardBuilder(final DelegationTO defaultItem, final PageReference pageRef) {
+        super(defaultItem, pageRef);
+    }
+
+    @Override
+    protected Serializable onApplyInternal(final DelegationTO modelObject) {
+        if (getOriginalItem() == null || StringUtils.isBlank(getOriginalItem().getKey())) {
+            DelegationRestClient.create(modelObject);
+        } else {
+            DelegationRestClient.update(modelObject);
+        }
+        return null;
+    }
+
+    @Override
+    protected WizardModel buildModelSteps(final DelegationTO modelObject, final WizardModel wizardModel) {
+        if (getOriginalItem() == null || StringUtils.isBlank(getOriginalItem().getKey())
+                && SyncopeConsoleSession.get().owns(IdRepoEntitlement.USER_SEARCH)
+                && SyncopeConsoleSession.get().owns(IdRepoEntitlement.DELEGATION_CREATE)) {
+
+            wizardModel.add(new UserSelectionWizardStep(
+                    new ResourceModel("delegating"), new PropertyModel<>(modelObject, "delegating"), pageRef));
+            wizardModel.add(new UserSelectionWizardStep(
+                    new ResourceModel("delegated"), new PropertyModel<>(modelObject, "delegated"), pageRef));
+        } else {
+            wizardModel.add(new Users(modelObject));
+        }
+
+        wizardModel.add(new StartEnd(modelObject));
+        wizardModel.add(new Roles(modelObject));
+
+        return wizardModel;
+    }
+
+    private class Users extends WizardStep {
+
+        private static final long serialVersionUID = 33859341441696L;
+
+        Users(final DelegationTO modelObject) {
+            super();
+
+            setTitleModel(new ResourceModel("users"));
+
+            IModel<String> delegating = new PropertyModel<>(modelObject, "delegating");
+            IModel<String> delegated = new PropertyModel<>(modelObject, "delegated");
+
+            boolean isNew = getOriginalItem() == null || StringUtils.isBlank(getOriginalItem().getKey());
+            if (!isNew) {
+                if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.USER_READ)) {
+                    delegating = Model.of(userRestClient.read(delegating.getObject()).getUsername());
+                    delegated = Model.of(userRestClient.read(delegated.getObject()).getUsername());
+                } else {
+                    if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(delegating.getObject())) {
+                        delegating = Model.of(SyncopeConsoleSession.get().getSelfTO().getUsername());
+                    }
+                    if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(delegated.getObject())) {
+                        delegated = Model.of(SyncopeConsoleSession.get().getSelfTO().getUsername());
+                    }
+                }
+            }
+
+            boolean isSelfOnly = !SyncopeConsoleSession.get().owns(IdRepoEntitlement.DELEGATION_CREATE);
+            if (isSelfOnly) {
+                modelObject.setDelegating(SyncopeConsoleSession.get().getSelfTO().getUsername());
+            }
+
+            add(new AjaxTextFieldPanel(
+                    "delegating",
+                    "delegating",
+                    delegating,
+                    false).addRequiredLabel().
+                    setEnabled(isNew && !isSelfOnly));
+            add(new AjaxTextFieldPanel(
+                    "delegated",
+                    "delegated",
+                    delegated,
+                    false).addRequiredLabel().
+                    setEnabled(isNew));
+        }
+    }
+
+    private static class StartEnd extends WizardStep {
+
+        private static final long serialVersionUID = 16957451737824L;
+
+        StartEnd(final DelegationTO modelObject) {
+            super();
+
+            setTitleModel(new ResourceModel("validity"));
+
+            add(new AjaxDateTimeFieldPanel(
+                    "start",
+                    "start",
+                    new PropertyModel<>(modelObject, "start"),
+                    FastDateFormat.getInstance(SyncopeConstants.DEFAULT_DATE_PATTERN)).
+                    addRequiredLabel());
+
+            add(new AjaxDateTimeFieldPanel(
+                    "end",
+                    "end",
+                    new PropertyModel<>(modelObject, "end"),
+                    FastDateFormat.getInstance(SyncopeConstants.DEFAULT_DATE_PATTERN)));
+        }
+    }
+
+    private class Roles extends WizardStep implements WizardModel.ICondition {
+
+        private static final long serialVersionUID = 16957451737824L;
+
+        private final List<String> allRoles = new ArrayList<>();
+
+        private final DelegationTO modelObject;
+
+        Roles(final DelegationTO modelObject) {
+            super();
+            this.modelObject = modelObject;
+
+            setTitleModel(new ResourceModel("roles"));
+
+            add(new AjaxPalettePanel.Builder<String>().
+                    withFilter().
+                    setAllowOrder(true).
+                    build("roles",
+                            new PropertyModel<>(modelObject, "roles"),
+                            new AjaxPalettePanel.Builder.Query<String>() {
+
+                        private static final long serialVersionUID = 3900199363626636719L;
+
+                        @Override
+                        public List<String> execute(final String filter) {
+                            if (StringUtils.isEmpty(filter) || "*".equals(filter)) {
+                                return allRoles.size() > Constants.MAX_ROLE_LIST_SIZE
+                                        ? allRoles.subList(0, Constants.MAX_ROLE_LIST_SIZE)
+                                        : allRoles;
+
+                            }
+                            return allRoles.stream().
+                                    filter(role -> StringUtils.containsIgnoreCase(role, filter)).
+                                    collect(Collectors.toList());
+                        }
+                    }).
+                    hideLabel().
+                    setOutputMarkupId(true));
+        }
+
+        @Override
+        public boolean evaluate() {
+            if (modelObject.getDelegating() != null) {
+                allRoles.clear();
+
+                if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.USER_READ)) {
+                    allRoles.addAll(userRestClient.read(modelObject.getDelegating()).getRoles());
+                } else if (SyncopeConsoleSession.get().getSelfTO().getKey().equals(modelObject.getDelegating())) {
+                    allRoles.addAll(SyncopeConsoleSession.get().getSelfTO().getRoles());
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java
new file mode 100644
index 0000000..c33dd8e
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.wizards;
+
+import java.util.ArrayList;
+import org.apache.syncope.client.console.panels.search.AnySelectionDirectoryPanel;
+import org.apache.syncope.client.console.panels.search.SearchClausePanel;
+import org.apache.syncope.client.console.panels.search.SearchUtils;
+import org.apache.syncope.client.console.panels.search.UserSearchPanel;
+import org.apache.syncope.client.console.panels.search.UserSelectionDirectoryPanel;
+import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
+import org.apache.syncope.client.console.rest.AnyTypeRestClient;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.to.AnyTypeTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.wicket.Component;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.wizard.WizardStep;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.util.ListModel;
+
+public class UserSelectionWizardStep extends WizardStep {
+
+    private static final long serialVersionUID = 36221031226727L;
+
+    private final IModel<String> model;
+
+    private final UserSearchPanel userSearchPanel;
+
+    private final UserSelectionDirectoryPanel userDirectoryPanel;
+
+    public UserSelectionWizardStep(
+            final IModel<String> title, final IModel<String> model, final PageReference pageRef) {
+
+        super();
+        setOutputMarkupId(true);
+
+        this.model = model;
+        setTitleModel(title);
+
+        userSearchPanel = UserSearchPanel.class.cast(new UserSearchPanel.Builder(
+                new ListModel<>(new ArrayList<>())).required(false).enableSearch(UserSelectionWizardStep.this).
+                build("usersearch"));
+        add(userSearchPanel);
+
+        AnyTypeTO anyTypeTO = AnyTypeRestClient.read(AnyTypeKind.USER.name());
+        userDirectoryPanel = UserSelectionDirectoryPanel.class.cast(new UserSelectionDirectoryPanel.Builder(
+                AnyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
+                build("searchResult"));
+        add(userDirectoryPanel);
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
+            AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
+            String fiql = SearchUtils.buildFIQL(
+                    userSearchPanel.getModel().getObject(), SyncopeClient.getUserSearchConditionBuilder());
+            userDirectoryPanel.search(fiql, target);
+        } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) {
+            @SuppressWarnings("unchecked")
+            AnySelectionDirectoryPanel.ItemSelection<UserTO> payload =
+                    (AnySelectionDirectoryPanel.ItemSelection<UserTO>) event.getPayload();
+
+            UserTO selected = payload.getSelection();
+            this.model.setObject(selected.getKey());
+
+            String tableId = ((Component) event.getSource()).
+                    get("container:content:searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable").
+                    getMarkupId();
+            String js = "$('#" + tableId + " tr').removeClass('active');";
+            js += "$('#" + tableId + " td[title=" + selected.getKey() + "]').parent().addClass('active');";
+            payload.getTarget().prependJavaScript(js);
+        }
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java
index 7a6d894..8de168d 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java
@@ -141,7 +141,7 @@ public class Ownership extends WizardStep implements ICondition {
                 final CheckBox checkBox = super.newCheckBox(id, model);
                 checkBox.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
 
-                    private static final long serialVersionUID = 1L;
+                    private static final long serialVersionUID = 18235445704320L;
 
                     @Override
                     protected void onUpdate(final AjaxRequestTarget target) {
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication.properties
index 3104d7e..966af0d 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication.properties
@@ -79,3 +79,4 @@ keymaster=Keymaster
 domains=Domains
 nomatch=No matches found
 tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). 
+confirmDelegation=Do you really want to switch user?
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_fr_CA.properties
index d9ebbf3..de28834 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_fr_CA.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_fr_CA.properties
@@ -78,3 +78,4 @@ keymaster=Keymaster
 domains=Domaines
 nomatch=Aucune correspondance trouv\u00e9e
 tooLargeFile=Fichier trop volumineux, la taille maximale autoris\u00e9e est de $ {maxUploadSizeB} octets ($ {maxUploadSizeMB} Mo).
+confirmDelegation=Do you really want to switch user?
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_it.properties
index c1c0ef8..59c56dc 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_it.properties
@@ -79,3 +79,4 @@ keymaster=Keymaster
 domains=Domini
 nomatch=Nessun risultato trovato
 tooLargeFile=File troppo grande, la dimensione massima ammessa \u00e8 ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). 
+confirmDelegation=Vuoi davvero cambiare utenza?
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_ja.properties
index 85f9f23..ca0e84e 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_ja.properties
@@ -77,3 +77,4 @@ keymaster=\u30ad\u30fc\u30de\u30b9\u30bf\u30fc
 domains=\u30c9\u30e1\u30a4\u30f3
 nomatch=No matches found
 tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). 
+confirmDelegation=Do you really want to switch user?
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_pt_BR.properties
index 112c58e..142ca0e 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_pt_BR.properties
@@ -79,3 +79,4 @@ keymaster=Keymaster
 domains=Dom\u00ednios
 nomatch=Nenhuma correspond\u00eancia encontrada
 tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). 
+confirmDelegation=Do you really want to switch user?
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_ru.properties
index d0855bb..9c58582 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/SyncopeWebApplication_ru.properties
@@ -78,3 +78,4 @@ keymaster=Keymaster
 domains=Domains
 nomatch=No matches found
 tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). 
+confirmDelegation=Do you really want to switch user?
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html
index 71a787e..c27b042 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html
@@ -77,8 +77,25 @@ under the License.
                   <wicket:message key="domain"/>: <label wicket:id="domain"/>
                 </div>
               </li>
+              <li class="user-body" wicket:id="delegationsContainer">
+                <div class="row">
+                  <div class="col-xs-12 text-center">
+                    <div class="box box-primary">
+                      <div class="box-header">
+                        <h3 class="box-title" wicket:id="delegationsHeader"/>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="col-xs-12 text-center">
+                    <span wicket:id="delegations">
+                      <span wicket:id="delegation"/>
+                    </span>
+                  </div>
+                </div>
+              </li>
               <!-- Menu Footer-->
               <li class="user-footer">
+                <a href="#" class="btn btn-default btn-flat float-left" wicket:id="endDelegation"><wicket:message key="endDelegation"/></a>
                 <a href="#" class="btn btn-default btn-flat float-right" wicket:id="logout"><wicket:message key="logout"/></a>
               </li>
             </ul>
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.properties
index 8fef47d..7b00c5e 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.properties
@@ -28,3 +28,5 @@ download=Download
 accessibility=Accessibility
 highContrast=Toggle high contrast mode
 fontSize=Change font size
+delegations=Delegations
+endDelegation=End Delegation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_it.properties
index e7e7fe2..024bd5c 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_it.properties
@@ -28,3 +28,5 @@ download=Scarica
 accessibility=Accessibilit\u00e0
 highContrast=(Dis)attiva alto contrasto
 fontSize=Dimensione carattere
+delegations=Deleghe
+endDelegation=Termina Delega
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ja.properties
index 1c7c981..39d0b4c 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ja.properties
@@ -28,3 +28,5 @@ download=Download
 accessibility=Accessibility
 highContrast=Toggle high contrast mode
 fontSize=Change font size
+delegations=Delegations
+endDelegation=End Delegation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_pt_BR.properties
index 93ed4ed..8d48680 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_pt_BR.properties
@@ -28,3 +28,5 @@ download=Download
 accessibility=Accessibility
 highContrast=Toggle high contrast mode
 fontSize=Change font size
+delegations=Delegations
+endDelegation=End Delegation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ru.properties
index 7c034db..2e19446 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage_ru.properties
@@ -28,3 +28,5 @@ download=Download
 accessibility=Accessibility
 highContrast=Toggle high contrast mode
 fontSize=Change font size
+delegations=Delegations
+endDelegation=End Delegation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security.properties
index 3bba56c..3132aad 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security.properties
@@ -19,3 +19,4 @@ dynRealms=Dynamic Realms
 privileges=Privileges
 applications=Applications
 securityQuestions=Security Questions
+delegations=Delegations
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_it.properties
index a8e66ca..1a116a3 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_it.properties
@@ -19,3 +19,4 @@ dynRealms=Realm dinamici
 privileges=Privilegi
 applications=Applicazioni
 securityQuestions=Domande di sicurezza
+delegations=Deleghe
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_ja.properties
index fbbf749..e9892b7 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_ja.properties
@@ -19,3 +19,4 @@ dynRealms=\u52d5\u7684\u30ec\u30eb\u30e0
 privileges=\u6a29\u9650
 applications=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3
 securityQuestions=\u79d8\u5bc6\u306e\u8cea\u554f
+delegations=Delegations
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_pt_BR.properties
index f6e211a..b0ff8d8 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_pt_BR.properties
@@ -19,3 +19,4 @@ dynRealms=Realm din\u00e2micos
 privileges=Privileges
 applications=Applications
 securityQuestions=Perguntas de seguran\u00e7a
+delegations=Delegations
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_ru.properties
index 6a5c308..7e98f88 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Security_ru.properties
@@ -20,3 +20,4 @@ dynRealms=Dynamic Realms
 privileges=Privileges
 applications=Applications
 securityQuestions=\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u044b\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b
+delegations=Delegations
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/pages/BasePage_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/pages/BasePage_fr_CA.properties
index 955b91c..a7d7e6b 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/pages/BasePage_fr_CA.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/pages/BasePage_fr_CA.properties
@@ -17,8 +17,10 @@
 home=Accueil
 version=Version
 domain=Domaine
-systemInfo=Information sur le syst�me
-hostname=Nom d'h�te
+systemInfo=Information sur le syst\u00e8me
+hostname=Nom d'h\u00f4te
 processors=Processeurs disponibles
-os=Syst�me d'exploitation
+os=Syst\u00e8me d'exploitation
 jvm=JVM
+delegations=Delegations
+endDelegation=End Delegation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/pages/Security_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/pages/Security_fr_CA.properties
index abc9e64..92d8be6 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/pages/Security_fr_CA.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/pages/Security_fr_CA.properties
@@ -14,8 +14,9 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-add=Cr�er nouveau r�le
-Domaines dynamiques=Domaines dynamiques
-privileges=Privil�ges
+add=Cr\u00e9er nouveau r\u00f4le
+dynRealms=Domaines dynamiques
+privileges=Privil\u00e8ges
 applications=Applications
-securityQuestions=Questions de s�curit�
+securityQuestions=Questions de s\u00e9curit\u00e9
+delegations=Delegations
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.properties
similarity index 87%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.properties
index dcc952b..4449fb4 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.properties
@@ -14,8 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+any.edit=Edit Delegation ${key}
+any.new=New Delegation
+delegating=Delegating
+delegated=Delegated
 start=Start
 end=End
-status=Stato
-executor=Esecutore
-execution.view=Risultato dello stato dell'esecuzione '${key}'
+validity=Validity
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_fr_CA.properties
similarity index 84%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_fr_CA.properties
index 5d25d9c..2c80111 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_fr_CA.properties
@@ -14,4 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-mergeLinkedAccounts.searchUser=Select user to merge
+any.edit=Edit Delegation ${key}
+any.new=New Delegation
+delegating=Delegating
+delegated=Delegated
+start=D\u00e9but
+end=Fin
+validity=Validity
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_it.properties
similarity index 85%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties
rename to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_it.properties
index 5d25d9c..89de8bf 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_it.properties
@@ -14,4 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-mergeLinkedAccounts.searchUser=Select user to merge
+any.edit=Modifica Delega ${key}
+any.new=Nuova Delega
+delegating=Delegante
+delegated=Delegato
+start=Inizio
+end=Fine
+validity=Validit\u00e0
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_ja.properties
similarity index 87%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_ja.properties
index dcc952b..4449fb4 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_ja.properties
@@ -14,8 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+any.edit=Edit Delegation ${key}
+any.new=New Delegation
+delegating=Delegating
+delegated=Delegated
 start=Start
 end=End
-status=Stato
-executor=Esecutore
-execution.view=Risultato dello stato dell'esecuzione '${key}'
+validity=Validity
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_pt_BR.properties
similarity index 87%
copy from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_pt_BR.properties
index dcc952b..4449fb4 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_pt_BR.properties
@@ -14,8 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+any.edit=Edit Delegation ${key}
+any.new=New Delegation
+delegating=Delegating
+delegated=Delegated
 start=Start
 end=End
-status=Stato
-executor=Esecutore
-execution.view=Risultato dello stato dell'esecuzione '${key}'
+validity=Validity
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_ru.properties
similarity index 78%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.properties
rename to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_ru.properties
index 3fd6639..2438784 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationDirectoryPanel_ru.properties
@@ -14,9 +14,11 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-mergeLinkedAccounts.reviewAccounts.title=Preview finalized linked accounts
-connObjectKeyValue=Connector Key
-resource=Resource
-key=Key
-username=Username
-suspended=Suspended
+#
+any.edit=Edit Delegation ${key}
+any.new=New Delegation
+delegating=Delegating
+delegated=Delegated
+start=\u041d\u0430\u0447\u0430\u043b\u043e
+end=\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0435
+validity=Validity
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationSelectionPanel.html
similarity index 86%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationSelectionPanel.html
index c8f3ec9..09083d7 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/DelegationSelectionPanel.html
@@ -17,8 +17,7 @@ specific language governing permissions and limitations
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-<wicket:panel>
-    <div wicket:id="resources">
-    </div>
-</wicket:panel>
+  <wicket:panel>
+    <a wicket:id="link" class="btn btn-app"><i class="fas fa-user"></i><span wicket:id="label"/></a>
+  </wicket:panel>
 </html>
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/MembersTogglePanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/MembersTogglePanel.html
index ae26507..00a11b9 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/MembersTogglePanel.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/MembersTogglePanel.html
@@ -23,7 +23,7 @@ under the License.
         <div class="input-group">
           <span wicket:id="type"/>
           <div class="input-group-append input-group-text">
-            <a wicket:id="changeit"><i class="fa fa-users" alt="members icon" title="Members"></i></a>
+            <a wicket:id="changeit"><i class="fas fa-users" alt="members icon" title="Members"></i></a>
           </div>
         </div>
       </form>
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties
index dcc952b..a4ad3eb 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel_it.properties
@@ -14,8 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-start=Start
-end=End
+start=Inizio
+end=Fine
 status=Stato
 executor=Esecutore
 execution.view=Risultato dello stato dell'esecuzione '${key}'
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
index f3b6a3b..b655b41 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
@@ -109,7 +109,7 @@ view_details.class=fa fa-info-circle
 view_details.title=view details
 view_details.alt=view details icon
 
-members.class=fa fa-users
+members.class=fas fa-users
 members.title=members
 members.alt=members icon
 
@@ -137,7 +137,7 @@ password_management.class=fas fa-shield-alt
 password_management.title=manage password
 password_management.alt=manage password icon
 
-request_password_reset.class=fa fa-user-secret
+request_password_reset.class=fas fa-user-secret
 request_password_reset.title=request password reset
 request_password_reset.alt=password reset icon
 
@@ -245,11 +245,11 @@ manage_resources.class=fa fa-sitemap
 manage_resources.title=manage resources
 manage_resources.alt=manage resources icon
 
-manage_users.class=fa fa-users
+manage_users.class=fas fa-users
 manage_users.title=manage users
 manage_users.alt=manage users icon
 
-manage_groups.class=fa fa-users
+manage_groups.class=fas fa-users
 manage_groups.title=manage groups
 manage_groups.alt=manage groups icon
 
@@ -269,7 +269,7 @@ zoom_out.class=fa fa-search-minus
 zoom_out.title=zoom-out
 zoom_out.alt=zoom-out icon
 
-manage_accounts.class=fa fa-users
+manage_accounts.class=fas fa-users
 manage_accounts.title=manage accounts
 manage_accounts.alt=manage accounts icon
 
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_fr_CA.properties
index 9805170..5b4c348 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_fr_CA.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_fr_CA.properties
@@ -86,7 +86,7 @@ view_executions.alt=ic\u00f4ne afficher ex\u00e9cutions
 view_details.class=fa fa-info-circle
 view_details.title=afficher d\u00e9tails
 view_details.alt=ic\u00f4ne afficher d\u00e9tails
-members.class=fa fa-users
+members.class=fas fa-users
 members.title=membres
 members.alt=ic\u00f4ne memnres
 search.class=fas fa-search
@@ -107,7 +107,7 @@ execute.alt=ic\u00f4ne ex\u00e9cuter
 password_management.class=fas fa-shield-alt
 password_management.title=gestion du mot de passe
 password_management.alt=ic\u00f4ne gestion du mot de passe
-request_password_reset.class=fa fa-user-secret
+request_password_reset.class=fas fa-user-secret
 request_password_reset.title=demande de r\u00e9initialisation du mot de passe
 request_password_reset.alt=ic\u00f4ne r\u00e9initialisation du mot de passe
 dryrun.class=fas fa-cogs
@@ -188,10 +188,10 @@ reconciliation_pull.alt=ic\u00f4ne pull r\u00e9conciliation
 manage_resources.class=fa fa-sitemap
 manage_resources.title=g\u00e9rer ressources
 manage_resources.alt=ic\u00f4ne g\u00e9rer ressources
-manage_users.class=fa fa-users
+manage_users.class=fas fa-users
 manage_users.title=g\u00e9rer utilisateurs
 manage_users.alt=ic\u00f4ne g\u00e9rer utilisateurs
-manage_groups.class=fa fa-users
+manage_groups.class=fas fa-users
 manage_groups.title=g\u00e9rer groupes
 manage_groups.alt=ic\u00f4ne g\u00e9rer groupes
 propagation_tasks.class=fa fa-arrow-right
@@ -206,7 +206,7 @@ zoom_in.alt=ic\u00f4ne zoom-in
 zoom_out.class=fa fa-search-minus
 zoom_out.title=zoom-out
 zoom_out.alt=ic\u00f4ne zoom-out
-manage_accounts.class=fa fa-users
+manage_accounts.class=fas fa-users
 manage_accounts.title=g\u00e9rer comptes
 manage_accounts.alt=ic\u00f4ne g\u00e9rer groupes
 view_audit_history.title=manage history
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties
index cd3d852..4800f58 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties
@@ -110,7 +110,7 @@ view_details.class=fa fa-info-circle
 view_details.title=vedi dettagli
 view_details.alt=view details icon
 
-members.class=fa fa-users
+members.class=fas fa-users
 members.title=membri
 members.alt=members icon
 
@@ -138,7 +138,7 @@ password_management.class=fas fa-shield-alt
 password_management.title=gestione password
 password_management.alt=manage password icon
 
-request_password_reset.class=fa fa-user-secret
+request_password_reset.class=fas fa-user-secret
 request_password_reset.title=richiedi password reset
 request_password_reset.alt=password reset icon
 
@@ -151,7 +151,7 @@ claim.title=claim
 claim.alt=claim icon
 
 select.class=fas fa-check
-select.title=select
+select.title=seleziona
 select.alt=select icon
 
 close.class=fas fa-sign-out-alt
@@ -234,11 +234,11 @@ manage_resources.class=fa fa-sitemap
 manage_resources.title=gestisci risorse
 manage_resources.alt=manage resources icon
 
-manage_users.class=fa fa-users
+manage_users.class=fas fa-users
 manage_users.title=gestisci utenti
 manage_users.alt=manage users icon
 
-manage_groups.class=fa fa-users
+manage_groups.class=fas fa-users
 manage_groups.title=gestisci gruppi
 manage_groups.alt=manage groups icon
 
@@ -265,7 +265,7 @@ reconciliation_pull.class=fa fa-chevron-circle-left
 reconciliation_pull.title=pull
 reconciliation_pull.alt=reconciliation pull icon
 
-manage_accounts.class=fa fa-users
+manage_accounts.class=fas fa-users
 manage_accounts.title=gestisci account
 manage_accounts.alt=manage accounts icon
 
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties
index 8f213b6..e0ec802 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties
@@ -110,7 +110,7 @@ view_details.class=fa fa-info-circle
 view_details.title=\u8a73\u7d30\u3092\u8868\u793a
 view_details.alt=\u8a73\u7d30\u3092\u8868\u793a
 
-members.class=fa fa-users
+members.class=fas fa-users
 members.title=\u30e1\u30f3\u30d0\u30fc
 members.alt=\u30e1\u30f3\u30d0\u30fc
 
@@ -138,7 +138,7 @@ password_management.class=fas fa-shield-alt
 password_management.title=\u30d1\u30b9\u30ef\u30fc\u30c9\u7ba1\u7406
 password_management.alt=\u30d1\u30b9\u30ef\u30fc\u30c9\u7ba1\u7406icon
 
-request_password_reset.class=fa fa-user-secret
+request_password_reset.class=fas fa-user-secret
 request_password_reset.title=\u30d1\u30b9\u30ef\u30fc\u30c9\u30ea\u30bb\u30c3\u30c8\u306e\u8981\u6c42
 request_password_reset.alt=\u30d1\u30b9\u30ef\u30fc\u30c9\u30ea\u30bb\u30c3\u30c8\u306e\u8981\u6c42 icon
 
@@ -234,11 +234,11 @@ manage_resources.class=fa fa-sitemap
 manage_resources.title=\u30ea\u30bd\u30fc\u30b9\u7ba1\u7406
 manage_resources.alt=\u30ea\u30bd\u30fc\u30b9\u7ba1\u7406
 
-manage_users.class=fa fa-users
+manage_users.class=fas fa-users
 manage_users.title=\u30e6\u30fc\u30b6\u2015\u7ba1\u7406
 manage_users.alt=\u30e6\u30fc\u30b6\u2015\u7ba1\u7406
 
-manage_groups.class=fa fa-users
+manage_groups.class=fas fa-users
 manage_groups.title=\u30b0\u30eb\u30fc\u30d7\u7ba1\u7406
 manage_groups.alt=\u30b0\u30eb\u30fc\u30d7\u7ba1\u7406
 
@@ -266,7 +266,7 @@ reconciliation_pull.class=fa fa-chevron-circle-left
 reconciliation_pull.title=\u30d7\u30eb
 reconciliation_pull.alt=\u7167\u5408\u30d7\u30eb icon
 
-manage_accounts.class=fa fa-users
+manage_accounts.class=fas fa-users
 manage_accounts.title=\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u7ba1\u7406\u3059\u308b
 manage_accounts.alt=\u30a2\u30ab\u30a6\u30f3\u30c8\u7ba1\u7406\u30a2\u30a4\u30b3\u30f3
 
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties
index 106d0ac..6002660 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties
@@ -109,7 +109,7 @@ view_details.class=fa fa-info-circle
 view_details.title=view details
 view_details.alt=view details icon
 
-members.class=fa fa-users
+members.class=fas fa-users
 members.title=members
 members.alt=members icon
 
@@ -137,7 +137,7 @@ password_management.class=fas fa-shield-alt
 password_management.title=manage password
 password_management.alt=manage password icon
 
-request_password_reset.class=fa fa-user-secret
+request_password_reset.class=fas fa-user-secret
 request_password_reset.title=request password reset
 request_password_reset.alt=password reset icon
 
@@ -245,11 +245,11 @@ manage_resources.class=fa fa-sitemap
 manage_resources.title=manage resources
 manage_resources.alt=manage resources icon
 
-manage_users.class=fa fa-users
+manage_users.class=fas fa-users
 manage_users.title=manage users
 manage_users.alt=manage users icon
 
-manage_groups.class=fa fa-users
+manage_groups.class=fas fa-users
 manage_groups.title=manage groups
 manage_groups.alt=manage groups icon
 
@@ -275,7 +275,7 @@ reconciliation_pull.class=fa-chevron-circle-left
 reconciliation_pull.title=pull
 reconciliation_pull.alt=reconciliation pull icon
 
-manage_accounts.class=fa fa-users
+manage_accounts.class=fas fa-users
 manage_accounts.title=manage accounts
 manage_accounts.alt=manage accounts icon
 
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties
index 34eb5a9..050558b 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties
@@ -110,7 +110,7 @@ view_details.class=fa fa-info-circle
 view_details.title=view details
 view_details.alt=view details icon
 
-members.class=fa fa-users
+members.class=fas fa-users
 members.title=members
 members.alt=members icon
 
@@ -138,7 +138,7 @@ password_management.class=fas fa-shield-alt
 password_management.title=manage password
 password_management.alt=manage password icon
 
-request_password_reset.class=fa fa-user-secret
+request_password_reset.class=fas fa-user-secret
 request_password_reset.title=request password reset
 request_password_reset.alt=password reset icon
 
@@ -234,11 +234,11 @@ manage_resources.class=fa fa-sitemap
 manage_resources.title=manage resources
 manage_resources.alt=manage resources icon
 
-manage_users.class=fa fa-users
+manage_users.class=fas fa-users
 manage_users.title=manage users
 manage_users.alt=manage users icon
 
-manage_groups.class=fa fa-users
+manage_groups.class=fas fa-users
 manage_groups.title=manage groups
 manage_groups.alt=manage groups icon
 
@@ -266,7 +266,7 @@ reconciliation_pull.class=fa fa-chevron-circle-left
 reconciliation_pull.title=pull
 reconciliation_pull.alt=reconciliation pull icon
 
-manage_accounts.class=fa fa-users
+manage_accounts.class=fas fa-users
 manage_accounts.title=manage accounts
 manage_accounts.alt=manage accounts icon
 
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/DelegationWizardBuilder$Roles.html
similarity index 90%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/DelegationWizardBuilder$Roles.html
index c8f3ec9..2926df2 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/DelegationWizardBuilder$Roles.html
@@ -17,8 +17,9 @@ specific language governing permissions and limitations
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-<wicket:panel>
-    <div wicket:id="resources">
+  <wicket:panel>
+    <div class="form-group">
+      <span wicket:id="roles"/>
     </div>
-</wicket:panel>
+  </wicket:panel>
 </html>
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/DelegationWizardBuilder$StartEnd.html
similarity index 83%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/DelegationWizardBuilder$StartEnd.html
index c8f3ec9..654eca0 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/DelegationWizardBuilder$StartEnd.html
@@ -17,8 +17,12 @@ specific language governing permissions and limitations
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-<wicket:panel>
-    <div wicket:id="resources">
+  <wicket:panel>
+    <div class="form-group">
+      <span wicket:id="start"/>
     </div>
-</wicket:panel>
+    <div class="form-group">
+      <span wicket:id="end"/>
+    </div>
+  </wicket:panel>
 </html>
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/DelegationWizardBuilder$Users.html
similarity index 83%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
copy to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/DelegationWizardBuilder$Users.html
index c8f3ec9..8a731d8 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/DelegationWizardBuilder$Users.html
@@ -17,8 +17,12 @@ specific language governing permissions and limitations
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-<wicket:panel>
-    <div wicket:id="resources">
+  <wicket:panel>
+    <div class="form-group">
+      <span wicket:id="delegating"/>
     </div>
-</wicket:panel>
+    <div class="form-group">
+      <span wicket:id="delegated"/>
+    </div>
+  </wicket:panel>
 </html>
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.html
similarity index 82%
rename from client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
rename to client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.html
index c8f3ec9..157d5d8 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.html
@@ -17,8 +17,10 @@ specific language governing permissions and limitations
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-<wicket:panel>
-    <div wicket:id="resources">
+  <wicket:panel>
+    <span wicket:id="usersearch">[USER SEARCH]</span>
+    <div class="searchResult">
+      <span wicket:id="searchResult">[USER SEARCH RESULT]</span>
     </div>
-</wicket:panel>
+  </wicket:panel>
 </html>
diff --git a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
index b28d761..6e12edb 100644
--- a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
+++ b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
@@ -35,7 +35,7 @@ import java.util.List;
 import java.util.Properties;
 import java.util.Set;
 import java.util.stream.Stream;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.jaxrs.client.Client;
 import org.apache.syncope.client.console.AbstractTest.TestSyncopeWebApplication.SyncopeServiceClient;
 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionLinksProvider;
@@ -254,7 +254,7 @@ public abstract class AbstractTest {
         public SyncopeClientFactoryBean newClientFactory() {
             SyncopeClient client = mock(SyncopeClient.class);
 
-            when(client.self()).thenReturn(Pair.of(new HashMap<>(), getUserTO()));
+            when(client.self()).thenReturn(Triple.of(new HashMap<>(), List.of(), getUserTO()));
 
             SyncopeService syncopeService = getSyncopeService();
             when(client.getService(SyncopeService.class)).thenReturn(syncopeService);
@@ -281,7 +281,7 @@ public abstract class AbstractTest {
     @BeforeAll
     public static void loadProps() throws IOException {
         PROPS = new Properties();
-        try ( InputStream is = AbstractTest.class.getResourceAsStream("/console.properties")) {
+        try (InputStream is = AbstractTest.class.getResourceAsStream("/console.properties")) {
             PROPS.load(is);
         }
     }
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
index b34be66..59345bd 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
+++ b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
@@ -26,5 +26,4 @@ public class AnonymousAuthenticationHandler extends BasicAuthenticationHandler i
     public AnonymousAuthenticationHandler(final String username, final String password) {
         super(username, password);
     }
-
 }
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
index ff1452e..94a1f6a 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
+++ b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
@@ -39,5 +39,4 @@ public class BasicAuthenticationHandler implements AuthenticationHandler {
     public String getPassword() {
         return password;
     }
-
 }
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
index 6192fd4..5d397e3 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
+++ b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
@@ -30,7 +30,7 @@ import javax.ws.rs.core.EntityTag;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.configuration.jsse.TLSClientParameters;
 import org.apache.cxf.jaxrs.client.Client;
 import org.apache.cxf.jaxrs.client.ClientConfiguration;
@@ -131,6 +131,7 @@ public class SyncopeClient {
 
     protected void cleanup() {
         restClientFactory.getHeaders().remove(HttpHeaders.AUTHORIZATION);
+        restClientFactory.getHeaders().remove(RESTHeaders.DELEGATED_BY);
         restClientFactory.setUsername(null);
         restClientFactory.setPassword(null);
     }
@@ -145,6 +146,21 @@ public class SyncopeClient {
     }
 
     /**
+     * Requests to invoke services as delegating user.
+     *
+     * @param delegating delegating username
+     * @return this instance, for fluent usage
+     */
+    public SyncopeClient delegatedBy(final String delegating) {
+        if (delegating == null) {
+            restClientFactory.getHeaders().remove(RESTHeaders.DELEGATED_BY);
+        } else {
+            restClientFactory.getHeaders().put(RESTHeaders.DELEGATED_BY, List.of(delegating));
+        }
+        return this;
+    }
+
+    /**
      * Attempts to extend the lifespan of the JWT currently in use.
      */
     public void refresh() {
@@ -280,7 +296,7 @@ public class SyncopeClient {
         }
     }
 
-    public Pair<Map<String, Set<String>>, UserTO> self() {
+    public Triple<Map<String, Set<String>>, List<String>, UserTO> self() {
         // Explicitly disable header value split because it interferes with JSON deserialization below
         UserSelfService service = getService(UserSelfService.class);
         WebClient.getConfig(WebClient.client(service)).getRequestContext().put(HEADER_SPLIT_PROPERTY, false);
@@ -294,11 +310,15 @@ public class SyncopeClient {
         }
 
         try {
-            return Pair.of(
+            return Triple.of(
                     OBJECT_MAPPER.readValue(
                             response.getHeaderString(RESTHeaders.OWNED_ENTITLEMENTS),
                             new TypeReference<Map<String, Set<String>>>() {
                     }),
+                    OBJECT_MAPPER.readValue(
+                            response.getHeaderString(RESTHeaders.DELEGATIONS),
+                            new TypeReference<List<String>>() {
+                    }),
                     response.readEntity(UserTO.class));
         } catch (IOException e) {
             throw new IllegalStateException(e);
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/DelegationTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/DelegationTO.java
new file mode 100644
index 0000000..3d567c2
--- /dev/null
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/DelegationTO.java
@@ -0,0 +1,121 @@
+/*
+ * 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.common.lib.to;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.PathParam;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+@Schema(allOf = { AbstractStartEndBean.class })
+public class DelegationTO extends AbstractStartEndBean implements EntityTO {
+
+    private static final long serialVersionUID = 18031949556054L;
+
+    private String key;
+
+    private String delegating;
+
+    private String delegated;
+
+    private final Set<String> roles = new HashSet<>();
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @PathParam("key")
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getDelegating() {
+        return delegating;
+    }
+
+    public void setDelegating(final String delegating) {
+        this.delegating = delegating;
+    }
+
+    public String getDelegated() {
+        return delegated;
+    }
+
+    public void setDelegated(final String delegated) {
+        this.delegated = delegated;
+    }
+
+    @JacksonXmlElementWrapper(localName = "roles")
+    @JacksonXmlProperty(localName = "role")
+    public Set<String> getRoles() {
+        return roles;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_WRITE)
+    @Override
+    public Date getStart() {
+        return super.getStart();
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_WRITE)
+    @Override
+    public Date getEnd() {
+        return super.getEnd();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().
+                appendSuper(super.hashCode()).
+                append(key).
+                append(delegating).
+                append(delegated).
+                append(roles).
+                build();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final DelegationTO other = (DelegationTO) obj;
+        return new EqualsBuilder().
+                appendSuper(super.equals(obj)).
+                append(key, other.key).
+                append(delegating, other.delegating).
+                append(delegated, other.delegated).
+                append(roles, other.roles).
+                build();
+    }
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
index 2711b80..6b46f70 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
@@ -74,6 +74,10 @@ public class UserTO extends AnyTO implements GroupableRelatableTO {
 
     private final List<LinkedAccountTO> linkedAccounts = new ArrayList<>();
 
+    private final List<String> delegatingDelegations = new ArrayList<>();
+
+    private final List<String> delegatedDelegations = new ArrayList<>();
+
     @JacksonXmlProperty(localName = "_class", isAttribute = true)
     @JsonProperty("_class")
     @Schema(name = "_class", required = true, example = "org.apache.syncope.common.lib.to.UserTO")
@@ -261,6 +265,20 @@ public class UserTO extends AnyTO implements GroupableRelatableTO {
         return linkedAccounts;
     }
 
+    @JacksonXmlElementWrapper(localName = "delegatingDelegations")
+    @JacksonXmlProperty(localName = "delegatingDelegation")
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public List<String> getDelegatingDelegations() {
+        return delegatingDelegations;
+    }
+
+    @JacksonXmlElementWrapper(localName = "getDelegatedDelegations")
+    @JacksonXmlProperty(localName = "getDelegatedDelegation")
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public List<String> getDelegatedDelegations() {
+        return delegatedDelegations;
+    }
+
     @Override
     public int hashCode() {
         return new HashCodeBuilder().
@@ -282,6 +300,8 @@ public class UserTO extends AnyTO implements GroupableRelatableTO {
                 append(memberships).
                 append(dynMemberships).
                 append(linkedAccounts).
+                append(delegatingDelegations).
+                append(delegatedDelegations).
                 build();
     }
 
@@ -316,6 +336,8 @@ public class UserTO extends AnyTO implements GroupableRelatableTO {
                 append(memberships, other.memberships).
                 append(dynMemberships, other.dynMemberships).
                 append(linkedAccounts, other.linkedAccounts).
+                append(delegatingDelegations, other.delegatingDelegations).
+                append(delegatedDelegations, other.delegatedDelegations).
                 build();
     }
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
index 2fdec6b..b88e4ad 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
@@ -240,6 +240,16 @@ public final class IdRepoEntitlement {
 
     public static final String IMPLEMENTATION_DELETE = "IMPLEMENTATION_DELETE";
 
+    public static final String DELEGATION_LIST = "DELEGATION_LIST";
+
+    public static final String DELEGATION_CREATE = "DELEGATION_CREATE";
+
+    public static final String DELEGATION_READ = "DELEGATION_READ";
+
+    public static final String DELEGATION_UPDATE = "DELEGATION_UPDATE";
+
+    public static final String DELEGATION_DELETE = "DELEGATION_DELETE";
+
     private static final Set<String> VALUES;
 
     static {
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
index cbe18cd..1164751 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
@@ -33,6 +33,10 @@ public final class RESTHeaders {
 
     public static final String OWNED_ENTITLEMENTS = "X-Syncope-Entitlements";
 
+    public static final String DELEGATED_BY = "X-Syncope-Delegated-By";
+
+    public static final String DELEGATIONS = "X-Syncope-Delegations";
+
     public static final String RESOURCE_KEY = "X-Syncope-Key";
 
     public static final String CONNOBJECT_KEY = "X-Syncope-ConnObject-Key";
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java
index 1acc4f9..f9f9402 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.common.rest.api.beans;
 
 import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.enums.ParameterIn;
 import io.swagger.v3.oas.annotations.media.ArraySchema;
 import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.ArrayList;
@@ -87,8 +86,7 @@ public class AuditQuery extends AbstractQuery {
 
     private AuditElements.Result result;
 
-    @Parameter(name = JAXRSService.PARAM_ENTITY_KEY, in = ParameterIn.QUERY,
-            description = "audit entity key to match", schema =
+    @Parameter(name = JAXRSService.PARAM_ENTITY_KEY, description = "audit entity key to match", schema =
             @Schema(implementation = String.class, example = "50592942-73ec-44c4-a377-e859524245e4"))
     public String getEntityKey() {
         return entityKey;
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/DelegationService.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/DelegationService.java
new file mode 100644
index 0000000..f1e1854
--- /dev/null
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/DelegationService.java
@@ -0,0 +1,122 @@
+/*
+ * 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.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.List;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.DelegationTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for delegations.
+ */
+@Tag(name = "Delegations")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer") })
+@Path("delegations")
+public interface DelegationService extends JAXRSService {
+
+    /**
+     * Returns a list of all delegations.
+     *
+     * @return list of all delegations.
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    List<DelegationTO> list();
+
+    /**
+     * Returns delegation with matching key.
+     *
+     * @param key delegation key to be read
+     * @return delegation with matching key
+     */
+    @GET
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    DelegationTO read(@NotNull @PathParam("key") String key);
+
+    /**
+     * Creates a new delegation.
+     *
+     * @param delegationTO delegation to be created
+     * @return Response object featuring Location header of created delegation
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "201",
+                    description = "Delegation successfully created", headers = {
+                @Header(name = RESTHeaders.RESOURCE_KEY, schema =
+                        @Schema(type = "string"),
+                        description = "Key value for the entity created"),
+                @Header(name = HttpHeaders.LOCATION, schema =
+                        @Schema(type = "string"),
+                        description = "URL of the entity created") }))
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    Response create(@NotNull DelegationTO delegationTO);
+
+    /**
+     * Updates the delegation matching the provided key.
+     *
+     * @param delegationTO delegation to be stored
+     */
+    @Parameter(name = "key", description = "Delegation's key", in = ParameterIn.PATH, schema =
+            @Schema(type = "string"))
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @PUT
+    @Path("{key}")
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void update(@NotNull DelegationTO delegationTO);
+
+    /**
+     * Deletes the delegation matching the provided key.
+     *
+     * @param key delegation key to be deleted
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @DELETE
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void delete(@NotNull @PathParam("key") String key);
+}
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
index 41cafeb..8dc4e54 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
@@ -58,14 +58,14 @@ public interface UserSelfService extends JAXRSService {
     /**
      * Returns the user making the service call.
      *
-     * @return calling user data, including own UUID and entitlements
+     * @return calling user data, including own UUID, entitlements and delegations
      */
     @Operation(security = {
         @SecurityRequirement(name = "BasicAuthentication"),
         @SecurityRequirement(name = "Bearer") })
     @ApiResponses(
-            @ApiResponse(responseCode = "200", description = "Calling user data, including own UUID and entitlements",
-                    content =
+            @ApiResponse(responseCode = "200",
+                    description = "Calling user data, including own UUID, entitlements and delegations", content =
                     @Content(schema =
                             @Schema(implementation = UserTO.class)), headers = {
                 @Header(name = RESTHeaders.RESOURCE_KEY, schema =
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
index 90f7249..eb558dc 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
@@ -133,5 +133,4 @@ public class ApplicationLogic extends AbstractTransactionalLogic<ApplicationTO>
 
         throw new UnresolvedReferenceException();
     }
-
 }
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/DelegationLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/DelegationLogic.java
new file mode 100644
index 0000000..bae38b3
--- /dev/null
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/DelegationLogic.java
@@ -0,0 +1,164 @@
+/*
+ * 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.logic;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.DelegationTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
+import org.apache.syncope.core.provisioning.api.data.DelegationDataBinder;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class DelegationLogic extends AbstractTransactionalLogic<DelegationTO> {
+
+    @Autowired
+    private DelegationDataBinder binder;
+
+    @Autowired
+    private DelegationDAO delegationDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    private void securityChecks(final String delegating, final String entitlement) {
+        if (!AuthContextUtils.getAuthorizations().keySet().contains(entitlement)
+                && (delegating == null || !delegating.equals(userDAO.findKey(AuthContextUtils.getUsername())))) {
+
+            throw new DelegatedAdministrationException(
+                    SyncopeConstants.ROOT_REALM, AnyTypeKind.USER.name(), delegating);
+        }
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
+    public DelegationTO read(final String key) {
+        Delegation delegation = delegationDAO.find(key);
+        if (delegation == null) {
+            LOG.error("Could not find delegation '" + key + "'");
+            throw new NotFoundException(key);
+        }
+
+        securityChecks(delegation.getDelegating().getKey(), IdRepoEntitlement.DELEGATION_READ);
+
+        return binder.getDelegationTO(delegation);
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
+    public List<DelegationTO> list() {
+        Stream<DelegationTO> delegations = delegationDAO.findAll().stream().map(binder::getDelegationTO);
+
+        if (!AuthContextUtils.getAuthorizations().keySet().contains(IdRepoEntitlement.DELEGATION_LIST)) {
+            String authUserKey = userDAO.findKey(AuthContextUtils.getUsername());
+            delegations = delegations.filter(delegation -> delegation.getDelegating().equals(authUserKey));
+        }
+
+        return delegations.collect(Collectors.toList());
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public DelegationTO create(final DelegationTO delegationTO) {
+        if (delegationTO.getDelegating() != null
+                && !SyncopeConstants.UUID_PATTERN.matcher(delegationTO.getDelegating()).matches()) {
+
+            delegationTO.setDelegating(userDAO.findKey(delegationTO.getDelegating()));
+        }
+        if (delegationTO.getDelegated() != null
+                && !SyncopeConstants.UUID_PATTERN.matcher(delegationTO.getDelegated()).matches()) {
+
+            delegationTO.setDelegated(userDAO.findKey(delegationTO.getDelegated()));
+        }
+
+        securityChecks(delegationTO.getDelegating(), IdRepoEntitlement.DELEGATION_CREATE);
+
+        return binder.getDelegationTO(delegationDAO.save(binder.create(delegationTO)));
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public DelegationTO update(final DelegationTO delegationTO) {
+        Delegation delegation = delegationDAO.find(delegationTO.getKey());
+        if (delegation == null) {
+            LOG.error("Could not find delegation '" + delegationTO.getKey() + "'");
+            throw new NotFoundException(delegationTO.getKey());
+        }
+
+        securityChecks(delegation.getDelegating().getKey(), IdRepoEntitlement.DELEGATION_UPDATE);
+
+        return binder.getDelegationTO(delegationDAO.save(binder.update(delegation, delegationTO)));
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public DelegationTO delete(final String key) {
+        Delegation delegation = delegationDAO.find(key);
+        if (delegation == null) {
+            LOG.error("Could not find delegation '" + key + "'");
+
+            throw new NotFoundException(key);
+        }
+
+        securityChecks(delegation.getDelegating().getKey(), IdRepoEntitlement.DELEGATION_DELETE);
+
+        DelegationTO deleted = binder.getDelegationTO(delegation);
+        delegationDAO.delete(key);
+        return deleted;
+    }
+
+    @Override
+    protected DelegationTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        String key = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; key == null && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    key = (String) args[i];
+                } else if (args[i] instanceof DelegationTO) {
+                    key = ((DelegationTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (key != null) {
+            try {
+                return binder.getDelegationTO(delegationDAO.find(key));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicInvocationHandler.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicInvocationHandler.java
index c3d6b38..25e0a8a 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicInvocationHandler.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicInvocationHandler.java
@@ -18,6 +18,9 @@
  */
 package org.apache.syncope.core.logic;
 
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.core.provisioning.api.AuditManager;
@@ -34,10 +37,6 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 
-import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.Map;
-
 @Aspect
 public class LogicInvocationHandler {
 
@@ -66,9 +65,9 @@ public class LogicInvocationHandler {
         String event = joinPoint.getSignature().getName();
 
         boolean notificationsAvailable = notificationManager.notificationsAvailable(
-            AuditElements.EventCategoryType.LOGIC, category, null, event);
+                AuditElements.EventCategoryType.LOGIC, category, null, event);
         boolean auditRequested = auditManager.auditRequested(
-            AuthContextUtils.getUsername(), AuditElements.EventCategoryType.LOGIC, category, null, event);
+                AuthContextUtils.getUsername(), AuditElements.EventCategoryType.LOGIC, category, null, event);
 
         AuditElements.Result condition = null;
         Object output = null;
@@ -101,15 +100,15 @@ public class LogicInvocationHandler {
             if (notificationsAvailable || auditRequested) {
                 Map<String, Object> jobMap = new HashMap<>();
                 jobMap.put(AfterHandlingEvent.JOBMAP_KEY, new AfterHandlingEvent(
-                    AuthContextUtils.getUsername(),
-                    AuditElements.EventCategoryType.LOGIC,
-                    category,
-                    null,
-                    event,
-                    condition,
-                    before,
-                    output,
-                    input));
+                        AuthContextUtils.getWho(),
+                        AuditElements.EventCategoryType.LOGIC,
+                        category,
+                        null,
+                        event,
+                        condition,
+                        before,
+                        output,
+                        input));
                 AfterHandlingJob.schedule(scheduler, jobMap);
             }
         }
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
index c2e0e91..8ce3ab2 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
@@ -26,6 +26,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.request.BooleanReplacePatchItem;
@@ -45,6 +46,7 @@ import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 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.UserDAO;
@@ -83,6 +85,9 @@ public class UserLogic extends AbstractAnyLogic<UserTO, UserCR, UserUR> {
     protected AccessTokenDAO accessTokenDAO;
 
     @Autowired
+    protected DelegationDAO delegationDAO;
+
+    @Autowired
     protected ConfParamOps confParamOps;
 
     @Autowired
@@ -96,10 +101,13 @@ public class UserLogic extends AbstractAnyLogic<UserTO, UserCR, UserUR> {
 
     @PreAuthorize("isAuthenticated() and not(hasRole('" + IdRepoEntitlement.MUST_CHANGE_PASSWORD + "'))")
     @Transactional(readOnly = true)
-    public Pair<String, UserTO> selfRead() {
-        return Pair.of(
+    public Triple<String, String, UserTO> selfRead() {
+        UserTO authenticatedUser = binder.getAuthenticatedUserTO();
+
+        return Triple.of(
                 POJOHelper.serialize(AuthContextUtils.getAuthorizations()),
-                binder.returnUserTO(binder.getAuthenticatedUserTO()));
+                POJOHelper.serialize(delegationDAO.findValidDelegating(authenticatedUser.getKey())),
+                binder.returnUserTO(authenticatedUser));
     }
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.USER_READ + "')")
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/SyncopeOpenApiCustomizer.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/SyncopeOpenApiCustomizer.java
index 6301769..4570944 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/SyncopeOpenApiCustomizer.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/SyncopeOpenApiCustomizer.java
@@ -117,8 +117,7 @@ public class SyncopeOpenApiCustomizer extends OpenApiCustomizer {
     @Override
     protected void addParameters(final List<Parameter> parameters) {
         Optional<Parameter> domainHeaderParameter = parameters.stream().filter(parameter
-                -> parameter instanceof HeaderParameter && RESTHeaders.DOMAIN.equals(parameter.getName())).
-                findFirst();
+                -> parameter instanceof HeaderParameter && RESTHeaders.DOMAIN.equals(parameter.getName())).findFirst();
         if (domainHeaderParameter.isEmpty()) {
             HeaderParameter parameter = new HeaderParameter();
             parameter.setName(RESTHeaders.DOMAIN);
@@ -126,7 +125,7 @@ public class SyncopeOpenApiCustomizer extends OpenApiCustomizer {
 
             ExternalDocumentation extDoc = new ExternalDocumentation();
             extDoc.setDescription("Apache Syncope Reference Guide");
-            extDoc.setUrl("http://syncope.apache.org/docs/2.1/reference-guide.html#domains");
+            extDoc.setUrl("http://syncope.apache.org/docs/3.0/reference-guide.html#domains");
 
             Schema<String> schema = new Schema<>();
             schema.setDescription("Domains are built to facilitate multitenancy.");
@@ -137,6 +136,25 @@ public class SyncopeOpenApiCustomizer extends OpenApiCustomizer {
 
             parameters.add(parameter);
         }
+
+        Optional<Parameter> delegatedByHeaderParameter = parameters.stream().
+                filter(p -> p instanceof HeaderParameter && RESTHeaders.DELEGATED_BY.equals(p.getName())).findFirst();
+        if (!delegatedByHeaderParameter.isPresent()) {
+            HeaderParameter parameter = new HeaderParameter();
+            parameter.setName(RESTHeaders.DELEGATED_BY);
+            parameter.setRequired(false);
+
+            ExternalDocumentation extDoc = new ExternalDocumentation();
+            extDoc.setDescription("Apache Syncope Reference Guide");
+            extDoc.setUrl("http://syncope.apache.org/docs/3.0/reference-guide.html#delegation");
+
+            Schema<String> schema = new Schema<>();
+            schema.setDescription("Acton behalf of someone else");
+            schema.setExternalDocs(extDoc);
+            parameter.setSchema(schema);
+
+            parameters.add(parameter);
+        }
     }
 
     @Override
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ApplicationServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ApplicationServiceImpl.java
index ed944da..a0354ee 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ApplicationServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ApplicationServiceImpl.java
@@ -68,5 +68,4 @@ public class ApplicationServiceImpl extends AbstractServiceImpl implements Appli
     public void delete(final String key) {
         logic.delete(key);
     }
-
 }
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ApplicationServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/DelegationServiceImpl.java
similarity index 68%
copy from core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ApplicationServiceImpl.java
copy to core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/DelegationServiceImpl.java
index ed944da..d6da13e 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ApplicationServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/DelegationServiceImpl.java
@@ -21,38 +21,32 @@ package org.apache.syncope.core.rest.cxf.service;
 import java.net.URI;
 import java.util.List;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.to.ApplicationTO;
-import org.apache.syncope.common.lib.to.PrivilegeTO;
+import org.apache.syncope.common.lib.to.DelegationTO;
 import org.apache.syncope.common.rest.api.RESTHeaders;
-import org.apache.syncope.common.rest.api.service.ApplicationService;
-import org.apache.syncope.core.logic.ApplicationLogic;
+import org.apache.syncope.common.rest.api.service.DelegationService;
+import org.apache.syncope.core.logic.DelegationLogic;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 @Service
-public class ApplicationServiceImpl extends AbstractServiceImpl implements ApplicationService {
+public class DelegationServiceImpl extends AbstractServiceImpl implements DelegationService {
 
     @Autowired
-    private ApplicationLogic logic;
+    private DelegationLogic logic;
 
     @Override
-    public List<ApplicationTO> list() {
+    public List<DelegationTO> list() {
         return logic.list();
     }
 
     @Override
-    public ApplicationTO read(final String key) {
+    public DelegationTO read(final String key) {
         return logic.read(key);
     }
 
     @Override
-    public PrivilegeTO readPrivilege(final String key) {
-        return logic.readPrivilege(key);
-    }
-
-    @Override
-    public Response create(final ApplicationTO applicationTO) {
-        ApplicationTO created = logic.create(applicationTO);
+    public Response create(final DelegationTO applicationTO) {
+        DelegationTO created = logic.create(applicationTO);
         URI location = uriInfo.getAbsolutePathBuilder().path(created.getKey()).build();
         return Response.created(location).
                 header(RESTHeaders.RESOURCE_KEY, created.getKey()).
@@ -60,7 +54,7 @@ public class ApplicationServiceImpl extends AbstractServiceImpl implements Appli
     }
 
     @Override
-    public void update(final ApplicationTO applicationTO) {
+    public void update(final DelegationTO applicationTO) {
         logic.update(applicationTO);
     }
 
@@ -68,5 +62,4 @@ public class ApplicationServiceImpl extends AbstractServiceImpl implements Appli
     public void delete(final String key) {
         logic.delete(key);
     }
-
 }
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserSelfServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserSelfServiceImpl.java
index 2a0e2d3..bd4176a 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserSelfServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserSelfServiceImpl.java
@@ -19,7 +19,7 @@
 package org.apache.syncope.core.rest.cxf.service;
 
 import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.common.lib.AnyOperations;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.request.StatusR;
@@ -58,10 +58,11 @@ public class UserSelfServiceImpl extends AbstractServiceImpl implements UserSelf
 
     @Override
     public Response read() {
-        Pair<String, UserTO> self = logic.selfRead();
+        Triple<String, String, UserTO> self = logic.selfRead();
         return Response.ok().
                 header(RESTHeaders.RESOURCE_KEY, self.getRight().getKey()).
                 header(RESTHeaders.OWNED_ENTITLEMENTS, self.getLeft()).
+                header(RESTHeaders.DELEGATIONS, self.getMiddle()).
                 entity(self.getRight()).
                 build();
     }
@@ -74,7 +75,7 @@ public class UserSelfServiceImpl extends AbstractServiceImpl implements UserSelf
 
     @Override
     public Response update(final UserTO user) {
-        Pair<String, UserTO> self = logic.selfRead();
+        Triple<String, String, UserTO> self = logic.selfRead();
         return update(AnyOperations.diff(user, self.getRight(), false));
     }
 
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/DelegationDAO.java
similarity index 51%
copy from client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/DelegationDAO.java
index ff1452e..09ac349 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/DelegationDAO.java
@@ -16,28 +16,31 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.lib;
+package org.apache.syncope.core.persistence.api.dao;
 
-/**
- * Implementation providing Basic Authentication capability.
- */
-public class BasicAuthenticationHandler implements AuthenticationHandler {
+import java.util.List;
+import java.util.Optional;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
+import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+
+public interface DelegationDAO extends DAO<Delegation> {
+
+    Delegation find(String key);
+
+    Optional<String> findValidFor(String delegating, String delegated);
+
+    List<String> findValidDelegating(String delegated);
 
-    private final String username;
+    List<Delegation> findByDelegating(User user);
 
-    private final String password;
+    List<Delegation> findByDelegated(User user);
 
-    public BasicAuthenticationHandler(final String username, final String password) {
-        this.username = username;
-        this.password = password;
-    }
+    List<Delegation> findByRole(Role role);
 
-    public String getUsername() {
-        return username;
-    }
+    List<Delegation> findAll();
 
-    public String getPassword() {
-        return password;
-    }
+    Delegation save(Delegation delegation);
 
+    void delete(String key);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/LoggerDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/LoggerDAO.java
index e7e1b43..0539ef3 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/LoggerDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/LoggerDAO.java
@@ -41,6 +41,14 @@ public interface LoggerDAO extends DAO<Logger> {
 
     void delete(Logger logger);
 
+    int countAuditEntries(
+            String entityKey,
+            AuditElements.EventCategoryType type,
+            String category,
+            String subcategory,
+            List<String> events,
+            AuditElements.Result result);
+
     List<AuditEntry> findAuditEntries(
             String entityKey,
             int page,
@@ -51,11 +59,4 @@ public interface LoggerDAO extends DAO<Logger> {
             List<String> events,
             AuditElements.Result result,
             List<OrderByClause> orderByClauses);
-
-    int countAuditEntries(String entityKey,
-                          AuditElements.EventCategoryType type,
-                          String category,
-                          String subcategory,
-                          List<String> events,
-                          AuditElements.Result result);
 }
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 471182d..98302bf 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
@@ -46,6 +46,8 @@ public interface UserDAO extends AnyDAO<User> {
      */
     void securityChecks(Set<String> authRealms, String key, String realm, Collection<String> groups);
 
+    Optional<String> findUsername(String key);
+
     Map<String, Integer> countByRealm();
 
     Map<String, Integer> countByStatus();
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Delegation.java
similarity index 61%
copy from core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Delegation.java
index 0b2b27c..07c34c5 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Delegation.java
@@ -16,17 +16,31 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.spring.security;
+package org.apache.syncope.core.persistence.api.entity;
 
-import javax.servlet.http.HttpServletRequest;
-import org.springframework.security.authentication.AuthenticationDetailsSource;
+import java.util.Date;
+import java.util.Set;
+import org.apache.syncope.core.persistence.api.entity.user.User;
 
-public class SyncopeAuthenticationDetailsSource
-        implements AuthenticationDetailsSource<HttpServletRequest, SyncopeAuthenticationDetails> {
+public interface Delegation extends Entity {
 
-    @Override
-    public SyncopeAuthenticationDetails buildDetails(final HttpServletRequest context) {
-        return new SyncopeAuthenticationDetails(context);
-    }
+    User getDelegating();
 
+    void setDelegating(User delegating);
+
+    User getDelegated();
+
+    void setDelegated(User delegated);
+
+    void setStart(Date start);
+
+    Date getStart();
+
+    void setEnd(Date end);
+
+    Date getEnd();
+
+    boolean add(Role role);
+
+    Set<? extends Role> getRoles();
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADelegationDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADelegationDAO.java
new file mode 100644
index 0000000..d22842a
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADelegationDAO.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.dao;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
+import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.jpa.entity.JPADelegation;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPADelegationDAO extends AbstractDAO<Delegation> implements DelegationDAO {
+
+    @Override
+    public Delegation find(final String key) {
+        return entityManager().find(JPADelegation.class, key);
+    }
+
+    @Override
+    public Optional<String> findValidFor(final String delegating, final String delegated) {
+        TypedQuery<Delegation> query = entityManager().createQuery(
+                "SELECT e FROM " + JPADelegation.class.getSimpleName() + " e "
+                + "WHERE e.delegating.id=:delegating AND e.delegated.id=:delegated "
+                + "AND e.start <= :now AND (e.end IS NULL OR e.end >= :now)", Delegation.class);
+        query.setParameter("delegating", delegating);
+        query.setParameter("delegated", delegated);
+        query.setParameter("now", new Date());
+        query.setMaxResults(1);
+
+        List<Delegation> raw = query.getResultList();
+        return raw.isEmpty() ? Optional.empty() : Optional.of(raw.get(0).getKey());
+    }
+
+    @Override
+    public List<String> findValidDelegating(final String delegated) {
+        TypedQuery<Delegation> query = entityManager().createQuery(
+                "SELECT e FROM " + JPADelegation.class.getSimpleName() + " e "
+                + "WHERE e.delegated.id=:delegated "
+                + "AND e.start <= :now AND (e.end IS NULL OR e.end >= :now)", Delegation.class);
+        query.setParameter("delegated", delegated);
+        query.setParameter("now", new Date());
+
+        return query.getResultList().stream().
+                map(delegation -> delegation.getDelegating().getUsername()).
+                collect(Collectors.toList());
+    }
+
+    @Override
+    public List<Delegation> findByDelegating(final User user) {
+        TypedQuery<Delegation> query = entityManager().createQuery(
+                "SELECT e FROM " + JPADelegation.class.getSimpleName() + " e "
+                + "WHERE e.delegating=:user", Delegation.class);
+        query.setParameter("user", user);
+        return query.getResultList();
+    }
+
+    @Override
+    public List<Delegation> findByDelegated(final User user) {
+        TypedQuery<Delegation> query = entityManager().createQuery(
+                "SELECT e FROM " + JPADelegation.class.getSimpleName() + " e "
+                + "WHERE e.delegated=:user", Delegation.class);
+        query.setParameter("user", user);
+        return query.getResultList();
+    }
+
+    @Override
+    public List<Delegation> findByRole(final Role role) {
+        TypedQuery<Delegation> query = entityManager().createQuery(
+                "SELECT e FROM " + JPADelegation.class.getSimpleName() + " e "
+                + "WHERE :role MEMBER OF e.roles", Delegation.class);
+        query.setParameter("role", role);
+        return query.getResultList();
+    }
+
+    @Override
+    public List<Delegation> findAll() {
+        TypedQuery<Delegation> query = entityManager().createQuery(
+                "SELECT e FROM " + JPADelegation.class.getSimpleName() + " e ", Delegation.class);
+        return query.getResultList();
+    }
+
+    @Override
+    public Delegation save(final Delegation delegation) {
+        return entityManager().merge(delegation);
+    }
+
+    @Override
+    public void delete(final String key) {
+        Delegation delegation = find(key);
+        if (delegation == null) {
+            return;
+        }
+
+        entityManager().remove(delegation);
+    }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPALoggerDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPALoggerDAO.java
index 0927fdb..a2f44dd 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPALoggerDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPALoggerDAO.java
@@ -43,18 +43,21 @@ public class JPALoggerDAO extends AbstractDAO<Logger> implements LoggerDAO {
 
         protected final StringBuilder query = new StringBuilder();
 
+        protected String andIfNeeded() {
+            return query.length() == 0 ? " " : " AND ";
+        }
+
         protected MessageCriteriaBuilder entityKey(final String entityKey) {
-            if (entityKey == null) {
-                query.append(" 1=1");
-            } else {
-                query.append(' ').append(AUDIT_MESSAGE_COLUMN).append(" LIKE '%key%").append(entityKey).append("%'");
+            if (entityKey != null) {
+                query.append(andIfNeeded()).append(AUDIT_MESSAGE_COLUMN).
+                        append(" LIKE '%key%").append(entityKey).append("%'");
             }
             return this;
         }
 
         public MessageCriteriaBuilder type(final AuditElements.EventCategoryType type) {
             if (type != null) {
-                query.append(" AND ").append(AUDIT_MESSAGE_COLUMN).
+                query.append(andIfNeeded()).append(AUDIT_MESSAGE_COLUMN).
                         append(" LIKE '%\"type\":\"").append(type.name()).append("\"%'");
             }
             return this;
@@ -62,7 +65,7 @@ public class JPALoggerDAO extends AbstractDAO<Logger> implements LoggerDAO {
 
         public MessageCriteriaBuilder category(final String category) {
             if (StringUtils.isNotBlank(category)) {
-                query.append(" AND ").append(AUDIT_MESSAGE_COLUMN).
+                query.append(andIfNeeded()).append(AUDIT_MESSAGE_COLUMN).
                         append(" LIKE '%\"category\":\"").append(category).append("\"%'");
             }
             return this;
@@ -70,7 +73,7 @@ public class JPALoggerDAO extends AbstractDAO<Logger> implements LoggerDAO {
 
         public MessageCriteriaBuilder subcategory(final String subcategory) {
             if (StringUtils.isNotBlank(subcategory)) {
-                query.append(" AND ").append(AUDIT_MESSAGE_COLUMN).
+                query.append(andIfNeeded()).append(AUDIT_MESSAGE_COLUMN).
                         append(" LIKE '%\"subcategory\":\"").append(subcategory).append("\"%'");
             }
             return this;
@@ -78,7 +81,7 @@ public class JPALoggerDAO extends AbstractDAO<Logger> implements LoggerDAO {
 
         public MessageCriteriaBuilder events(final List<String> events) {
             if (!events.isEmpty()) {
-                query.append(" AND ( ").
+                query.append(andIfNeeded()).append("( ").
                         append(events.stream().
                                 map(event -> AUDIT_MESSAGE_COLUMN + " LIKE '%\"event\":\"" + event + "\"%'").
                                 collect(Collectors.joining(" OR "))).
@@ -89,7 +92,7 @@ public class JPALoggerDAO extends AbstractDAO<Logger> implements LoggerDAO {
 
         public MessageCriteriaBuilder result(final AuditElements.Result result) {
             if (result != null) {
-                query.append(" AND ").append(AUDIT_MESSAGE_COLUMN).
+                query.append(andIfNeeded()).append(AUDIT_MESSAGE_COLUMN).
                         append(" LIKE '%\"result\":\"").append(result.name()).append("\"%' ");
             }
             return this;
@@ -142,13 +145,16 @@ public class JPALoggerDAO extends AbstractDAO<Logger> implements LoggerDAO {
     }
 
     @Override
-    public int countAuditEntries(final String entityKey,
+    public int countAuditEntries(
+            final String entityKey,
             final AuditElements.EventCategoryType type,
             final String category,
             final String subcategory,
             final List<String> events,
             final AuditElements.Result result) {
-        String queryString = "SELECT COUNT(0) FROM " + AUDIT_TABLE
+
+        String queryString = "SELECT COUNT(0)"
+                + " FROM " + AUDIT_TABLE
                 + " WHERE " + messageCriteriaBuilder(entityKey).
                         type(type).
                         category(category).
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
index 27928cd..9ebb0ec 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
@@ -27,6 +27,7 @@ import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
 import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.Role;
@@ -56,6 +57,9 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
     private AnySearchDAO searchDAO;
 
     @Autowired
+    private DelegationDAO delegationDAO;
+
+    @Autowired
     private SearchCondVisitor searchCondVisitor;
 
     @Override
@@ -136,6 +140,8 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
 
         clearDynMembers(role);
 
+        delegationDAO.findByRole(role).forEach(delegation -> delegation.getRoles().remove(role));
+
         entityManager().remove(role);
     }
 
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 0d6ee22..e3f8675 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
@@ -44,12 +44,14 @@ import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
@@ -95,6 +97,9 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
     @Autowired
     protected GroupDAO groupDAO;
 
+    @Autowired
+    protected DelegationDAO delegationDAO;
+
     @Resource(name = "adminUser")
     protected String adminUser;
 
@@ -118,6 +123,22 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         return findLastChange(key, JPAUser.TABLE);
     }
 
+    @Transactional(readOnly = true)
+    @Override
+    public Optional<String> findUsername(final String key) {
+        Query query = entityManager().createNativeQuery("SELECT username FROM " + JPAUser.TABLE + " WHERE id=?");
+        query.setParameter(1, key);
+
+        String username = null;
+        for (Object resultKey : query.getResultList()) {
+            username = resultKey instanceof Object[]
+                    ? (String) ((Object[]) resultKey)[0]
+                    : ((String) resultKey);
+        }
+
+        return Optional.ofNullable(username);
+    }
+
     @Override
     public int count() {
         Query query = entityManager().createQuery(
@@ -456,6 +477,13 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         groupDAO.removeDynMemberships(user);
         dynRealmDAO.removeDynMemberships(user.getKey());
 
+        Set<String> delegations = delegationDAO.findByDelegating(user).stream().
+                map(Delegation::getKey).collect(Collectors.toSet());
+        delegations.forEach(delegationDAO::delete);
+        delegations = delegationDAO.findByDelegated(user).stream().
+                map(Delegation::getKey).collect(Collectors.toSet());
+        delegations.forEach(delegationDAO::delete);
+
         AccessToken accessToken = accessTokenDAO.findByOwner(user.getUsername());
         if (accessToken != null) {
             accessTokenDAO.delete(accessToken);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPADelegation.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPADelegation.java
new file mode 100644
index 0000000..e49325f
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPADelegation.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.entity;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import javax.persistence.Cacheable;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+import javax.validation.constraints.NotNull;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
+import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
+import org.apache.syncope.core.persistence.jpa.validation.entity.DelegationCheck;
+
+@Entity
+@Table(name = JPADelegation.TABLE, uniqueConstraints =
+        @UniqueConstraint(columnNames = { "delegating_id", "delegated_id" }))
+@Cacheable
+@DelegationCheck
+public class JPADelegation extends AbstractGeneratedKeyEntity implements Delegation {
+
+    public static final String TABLE = "Delegation";
+
+    private static final long serialVersionUID = 17988340419552L;
+
+    @ManyToOne(optional = false)
+    private JPAUser delegating;
+
+    @ManyToOne(optional = false)
+    private JPAUser delegated;
+
+    @NotNull
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "startDate")
+    private Date start;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "endDate")
+    private Date end;
+
+    @OneToMany
+    private Set<JPARole> roles = new HashSet<>();
+
+    @Override
+    public User getDelegating() {
+        return delegating;
+    }
+
+    @Override
+    public void setDelegating(final User delegating) {
+        checkType(delegating, JPAUser.class);
+        this.delegating = (JPAUser) delegating;
+    }
+
+    @Override
+    public User getDelegated() {
+        return delegated;
+    }
+
+    @Override
+    public void setDelegated(final User delegated) {
+        checkType(delegated, JPAUser.class);
+        this.delegated = (JPAUser) delegated;
+    }
+
+    @Override
+    public Date getStart() {
+        return start == null
+                ? null
+                : new Date(start.getTime());
+    }
+
+    @Override
+    public void setStart(final Date start) {
+        this.start = start == null
+                ? null
+                : new Date(start.getTime());
+    }
+
+    @Override
+    public Date getEnd() {
+        return end == null
+                ? null
+                : new Date(end.getTime());
+    }
+
+    @Override
+    public void setEnd(final Date end) {
+        this.end = end == null
+                ? null
+                : new Date(end.getTime());
+    }
+
+    @Override
+    public boolean add(final Role role) {
+        checkType(role, JPARole.class);
+        return roles.add((JPARole) role);
+    }
+
+    @Override
+    public Set<? extends Role> getRoles() {
+        return roles;
+    }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index 88f0484..c15bdb2 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -28,6 +28,7 @@ import org.apache.syncope.core.persistence.api.entity.Application;
 import org.apache.syncope.core.persistence.api.entity.Batch;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.ConnPoolConf;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
 import org.apache.syncope.core.persistence.api.entity.DynRealm;
 import org.apache.syncope.core.persistence.api.entity.DynRealmMembership;
@@ -317,6 +318,8 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPARemediation();
         } else if (reference.equals(Batch.class)) {
             result = (E) new JPABatch();
+        } else if (reference.equals(Delegation.class)) {
+            result = (E) new JPADelegation();
         } else if (reference.equals(SRARoute.class)) {
             result = (E) new JPASRARoute();
         } else if (reference.equals(AuthModule.class)) {
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/DelegationCheck.java
similarity index 54%
copy from client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
copy to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/DelegationCheck.java
index ff1452e..ffb8207 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/BasicAuthenticationHandler.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/DelegationCheck.java
@@ -16,28 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.lib;
+package org.apache.syncope.core.persistence.jpa.validation.entity;
 
-/**
- * Implementation providing Basic Authentication capability.
- */
-public class BasicAuthenticationHandler implements AuthenticationHandler {
-
-    private final String username;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
-    private final String password;
+import javax.validation.Constraint;
+import javax.validation.Payload;
 
-    public BasicAuthenticationHandler(final String username, final String password) {
-        this.username = username;
-        this.password = password;
-    }
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = DelegationValidator.class)
+@Documented
+public @interface DelegationCheck {
 
-    public String getUsername() {
-        return username;
-    }
+    String message() default "{org.apache.syncope.core.persistence.validation.delegation}";
 
-    public String getPassword() {
-        return password;
-    }
+    Class<?>[] groups() default {};
 
+    Class<? extends Payload>[] payload() default {};
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/DelegationValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/DelegationValidator.java
new file mode 100644
index 0000000..c90eec3
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/DelegationValidator.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.validation.entity;
+
+import javax.validation.ConstraintValidatorContext;
+import org.apache.syncope.common.lib.types.EntityViolationType;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
+
+public class DelegationValidator extends AbstractValidator<DelegationCheck, Delegation> {
+
+    @Override
+    public boolean isValid(final Delegation delegation, final ConstraintValidatorContext context) {
+        context.disableDefaultConstraintViolation();
+
+        boolean isValid = true;
+
+        if (delegation.getDelegating().equals(delegation.getDelegated())) {
+            context.buildConstraintViolationWithTemplate(
+                    getTemplate(EntityViolationType.Standard, "delegating must be different from delegated")).
+                    addPropertyNode("delegating").addConstraintViolation();
+
+            isValid = false;
+        }
+
+        if (isValid && delegation.getEnd() != null && !delegation.getEnd().after(delegation.getStart())) {
+            context.buildConstraintViolationWithTemplate(
+                    getTemplate(EntityViolationType.Standard, "when end is provided it must to be after start")).
+                    addPropertyNode("end").addConstraintViolation();
+
+            isValid = false;
+        }
+
+        if (isValid && !delegation.getDelegating().getRoles().containsAll(delegation.getRoles())) {
+            context.buildConstraintViolationWithTemplate(
+                    getTemplate(EntityViolationType.Standard, "only Roles assigned to delegating User can be granted")).
+                    addPropertyNode("roles").addConstraintViolation();
+
+            isValid = false;
+        }
+
+        return isValid;
+    }
+}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java
index c545764..52d0313 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java
@@ -624,7 +624,7 @@ public class AnySearchTest extends AbstractTest {
         UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                 new org.springframework.security.core.userdetails.User(
                         "poorGroupOwner", "FAKE_PASSWORD", authorities), "FAKE_PASSWORD", authorities);
-        auth.setDetails(new SyncopeAuthenticationDetails(SyncopeConstants.MASTER_DOMAIN));
+        auth.setDetails(new SyncopeAuthenticationDetails(SyncopeConstants.MASTER_DOMAIN, null));
 
         SecurityContextHolder.getContext().setAuthentication(auth);
         try {
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ConnInstanceTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ConnInstanceTest.java
index 896c43f..4d1b103 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ConnInstanceTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ConnInstanceTest.java
@@ -62,7 +62,7 @@ public class ConnInstanceTest extends AbstractTest {
         UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                 new org.springframework.security.core.userdetails.User(
                         "admin", "FAKE_PASSWORD", authorities), "FAKE_PASSWORD", authorities);
-        auth.setDetails(new SyncopeAuthenticationDetails("Master"));
+        auth.setDetails(new SyncopeAuthenticationDetails(SyncopeConstants.MASTER_DOMAIN, null));
         SecurityContextHolder.getContext().setAuthentication(auth);
 
         try {
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
index 95df68f..0fc1ea8 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
@@ -67,7 +67,7 @@ public class MultitenancyTest extends AbstractTest {
         UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                 new org.springframework.security.core.userdetails.User(
                         "admin", "FAKE_PASSWORD", authorities), "FAKE_PASSWORD", authorities);
-        auth.setDetails(new SyncopeAuthenticationDetails("Two"));
+        auth.setDetails(new SyncopeAuthenticationDetails("Two", null));
         SecurityContextHolder.getContext().setAuthentication(auth);
     }
 
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ResourceTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ResourceTest.java
index 116704f..9d213b7 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ResourceTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ResourceTest.java
@@ -101,7 +101,7 @@ public class ResourceTest extends AbstractTest {
         UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                 new org.springframework.security.core.userdetails.User(
                         "admin", "FAKE_PASSWORD", authorities), "FAKE_PASSWORD", authorities);
-        auth.setDetails(new SyncopeAuthenticationDetails("Master"));
+        auth.setDetails(new SyncopeAuthenticationDetails(SyncopeConstants.MASTER_DOMAIN, null));
         SecurityContextHolder.getContext().setAuthentication(auth);
 
         try {
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/PlainSchemaTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/PlainSchemaTest.java
index 02de590..6c65f91 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/PlainSchemaTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/PlainSchemaTest.java
@@ -83,7 +83,7 @@ public class PlainSchemaTest extends AbstractTest {
         UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                 new org.springframework.security.core.userdetails.User(
                         "admin", "FAKE_PASSWORD", authorities), "FAKE_PASSWORD", authorities);
-        auth.setDetails(new SyncopeAuthenticationDetails("Master"));
+        auth.setDetails(new SyncopeAuthenticationDetails(SyncopeConstants.MASTER_DOMAIN, null));
         SecurityContextHolder.getContext().setAuthentication(auth);
     }
 
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java
index b69c5b9..617bbf4 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java
@@ -25,6 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -33,10 +35,12 @@ import javax.persistence.Query;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
 import org.apache.syncope.core.persistence.api.entity.user.DynRoleMembership;
 import org.apache.syncope.core.persistence.api.entity.Role;
 import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr;
@@ -66,6 +70,9 @@ public class RoleTest extends AbstractTest {
     @Autowired
     private AnyTypeClassDAO anyTypeClassDAO;
 
+    @Autowired
+    private DelegationDAO delegationDAO;
+
     /**
      * Static copy of {@link org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO} method with same signature:
      * required for avoiding creating new transaction - good for general use case but bad for the way how
@@ -202,4 +209,31 @@ public class RoleTest extends AbstractTest {
         assertNotNull(user);
         assertTrue(user.getRoles().isEmpty());
     }
+
+    @Test
+    public void deleteCascadeOnDelegations() {
+        User bellini = userDAO.findByUsername("bellini");
+        User rossini = userDAO.findByUsername("rossini");
+
+        Role reviewer = roleDAO.find("User reviewer");
+
+        Delegation delegation = entityFactory.newEntity(Delegation.class);
+        delegation.setDelegating(bellini);
+        delegation.setDelegated(rossini);
+        delegation.setStart(new Date());
+        delegation.add(reviewer);
+        delegation = delegationDAO.save(delegation);
+
+        entityManager().flush();
+
+        delegation = delegationDAO.find(delegation.getKey());
+
+        assertEquals(Collections.singletonList(delegation), delegationDAO.findByRole(reviewer));
+
+        roleDAO.delete(reviewer.getKey());
+
+        entityManager().flush();
+
+        assertTrue(delegationDAO.find(delegation.getKey()).getRoles().isEmpty());
+    }
 }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
index f639503..c0b1d9b 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import java.util.Date;
 import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
@@ -33,15 +34,19 @@ import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.ApplicationDAO;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.Role;
 import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttr;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
@@ -85,6 +90,12 @@ public class UserTest extends AbstractTest {
     @Autowired
     private ApplicationDAO applicationDAO;
 
+    @Autowired
+    private DelegationDAO delegationDAO;
+
+    @Autowired
+    private RoleDAO roleDAO;
+
     @Test
     public void delete() {
         List<UMembership> memberships = groupDAO.findUMemberships(groupDAO.findByName("managingDirector"));
@@ -318,6 +329,34 @@ public class UserTest extends AbstractTest {
         assertNull(entityManager().find(JPALinkedAccount.class, account.getKey()));
     }
 
+    @Test
+    public void deleteCascadeOnDelegations() {
+        User bellini = userDAO.findByUsername("bellini");
+        User rossini = userDAO.findByUsername("rossini");
+
+        Role reviewer = roleDAO.find("User reviewer");
+
+        Delegation delegation = entityFactory.newEntity(Delegation.class);
+        delegation.setDelegating(bellini);
+        delegation.setDelegated(rossini);
+        delegation.setStart(new Date());
+        delegation.add(reviewer);
+        delegation = delegationDAO.save(delegation);
+
+        entityManager().flush();
+
+        delegation = delegationDAO.find(delegation.getKey());
+
+        assertEquals(List.of(delegation), delegationDAO.findByDelegating(bellini));
+        assertEquals(List.of(delegation), delegationDAO.findByDelegated(rossini));
+
+        userDAO.delete(rossini.getKey());
+
+        entityManager().flush();
+
+        assertNull(delegationDAO.find(delegation.getKey()));
+    }
+
     /**
      * Search by derived attribute.
      */
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/DelegationDataBinder.java
similarity index 67%
copy from client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
copy to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/DelegationDataBinder.java
index b34be66..ece45d3 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/AnonymousAuthenticationHandler.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/DelegationDataBinder.java
@@ -16,15 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.lib;
+package org.apache.syncope.core.provisioning.api.data;
 
-/**
- * Implementation providing Basic Authentication capability for the special {@code anonymous} user.
- */
-public class AnonymousAuthenticationHandler extends BasicAuthenticationHandler implements AuthenticationHandler {
+import org.apache.syncope.common.lib.to.DelegationTO;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
+
+public interface DelegationDataBinder {
+
+    Delegation create(DelegationTO delegationTO);
 
-    public AnonymousAuthenticationHandler(final String username, final String password) {
-        super(username, password);
-    }
+    Delegation update(Delegation delegation, DelegationTO delegationTO);
 
+    DelegationTO getDelegationTO(Delegation delegation);
 }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/DelegationDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/DelegationDataBinderImpl.java
new file mode 100644
index 0000000..60be552
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/DelegationDataBinderImpl.java
@@ -0,0 +1,115 @@
+/*
+ * 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.data;
+
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.DelegationTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.data.DelegationDataBinder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DelegationDataBinderImpl implements DelegationDataBinder {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DelegationDataBinder.class);
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private RoleDAO roleDAO;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Override
+    public Delegation create(final DelegationTO delegationTO) {
+        Delegation delegation = entityFactory.newEntity(Delegation.class);
+
+        User delegating = Optional.ofNullable(userDAO.find(delegationTO.getDelegating())).
+                orElseThrow(() -> new NotFoundException("Delegating User " + delegationTO.getDelegating()));
+        delegation.setDelegating(delegating);
+
+        User delegated = Optional.ofNullable(userDAO.find(delegationTO.getDelegated())).
+                orElseThrow(() -> new NotFoundException("Delegated User " + delegationTO.getDelegated()));
+        delegation.setDelegated(delegated);
+
+        return update(delegation, delegationTO);
+    }
+
+    @Override
+    public Delegation update(final Delegation delegation, final DelegationTO delegationTO) {
+        delegation.setStart(delegationTO.getStart());
+        delegation.setEnd(delegationTO.getEnd());
+
+        // 1. add or update all (valid) roles from TO
+        delegationTO.getRoles().forEach(roleTO -> {
+            if (roleTO == null) {
+                LOG.error("Null {}", RoleTO.class.getSimpleName());
+            } else {
+                Role role = roleDAO.find(roleTO);
+                if (role == null) {
+                    SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidRole);
+                    sce.getElements().add("Role " + roleTO + " not found");
+                    throw sce;
+                }
+
+                delegation.add(role);
+            }
+        });
+
+        // 2. remove all roles not contained in the TO
+        for (Iterator<? extends Role> itor = delegation.getRoles().iterator(); itor.hasNext();) {
+            Role role = itor.next();
+            if (!delegationTO.getRoles().stream().anyMatch(roleKey -> roleKey.equals(role.getKey()))) {
+                itor.remove();
+            }
+        }
+
+        return delegation;
+    }
+
+    @Override
+    public DelegationTO getDelegationTO(final Delegation delegation) {
+        DelegationTO delegationTO = new DelegationTO();
+
+        delegationTO.setKey(delegation.getKey());
+        delegationTO.setDelegating(delegation.getDelegating().getKey());
+        delegationTO.setDelegated(delegation.getDelegated().getKey());
+        delegationTO.setStart(delegation.getStart());
+        delegationTO.setEnd(delegation.getEnd());
+        delegationTO.getRoles().addAll(delegation.getRoles().stream().map(Role::getKey).collect(Collectors.toSet()));
+
+        return delegationTO;
+    }
+}
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 31a605d..cf29706 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
@@ -57,9 +57,11 @@ import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.ApplicationDAO;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.entity.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
 import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Privilege;
@@ -97,6 +99,9 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
     private AccessTokenDAO accessTokenDAO;
 
     @Autowired
+    private DelegationDAO delegationDAO;
+
+    @Autowired
     private ConfParamOps confParamOps;
 
     @Resource(name = "adminUser")
@@ -118,7 +123,7 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
     @Transactional(readOnly = true)
     @Override
     public UserTO getAuthenticatedUserTO() {
-        final UserTO authUserTO;
+        UserTO authUserTO;
 
         String authUsername = AuthContextUtils.getUsername();
         if (anonymousUser.equals(authUsername)) {
@@ -770,6 +775,12 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
             // linked accounts
             userTO.getLinkedAccounts().addAll(
                     user.getLinkedAccounts().stream().map(this::getLinkedAccountTO).collect(Collectors.toList()));
+
+            // delegations
+            userTO.getDelegatingDelegations().addAll(
+                    delegationDAO.findByDelegating(user).stream().map(Delegation::getKey).collect(Collectors.toList()));
+            userTO.getDelegatedDelegations().addAll(
+                    delegationDAO.findByDelegated(user).stream().map(Delegation::getKey).collect(Collectors.toList()));
         }
 
         return userTO;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
index 08017a7..df88571 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractSchedTaskJobDelegate.java
@@ -148,7 +148,7 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega
         status.set("Done");
 
         notificationManager.createTasks(
-                AuthContextUtils.getUsername(),
+                AuthContextUtils.getWho(),
                 AuditElements.EventCategoryType.TASK,
                 this.getClass().getSimpleName(),
                 null,
@@ -158,14 +158,14 @@ public abstract class AbstractSchedTaskJobDelegate implements SchedTaskJobDelega
                 execution);
 
         auditManager.audit(
-                AuthContextUtils.getUsername(),
+                AuthContextUtils.getWho(),
                 AuditElements.EventCategoryType.TASK,
                 task.getClass().getSimpleName(),
                 null,
                 null, // searching for before object is too much expensive ...
                 result,
                 task,
-                (Object[]) null);
+                null);
     }
 
     /**
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java
index 13a700b..d2d73a4 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/DefaultNotificationJobDelegate.java
@@ -159,7 +159,7 @@ public class DefaultNotificationJobDelegate implements NotificationJobDelegate {
                     }
 
                     notificationManager.createTasks(
-                            AuthContextUtils.getUsername(),
+                            AuthContextUtils.getWho(),
                             AuditElements.EventCategoryType.TASK,
                             "notification",
                             null,
@@ -178,7 +178,7 @@ public class DefaultNotificationJobDelegate implements NotificationJobDelegate {
                     }
 
                     notificationManager.createTasks(
-                            AuthContextUtils.getUsername(),
+                            AuthContextUtils.getWho(),
                             AuditElements.EventCategoryType.TASK,
                             "notification",
                             null,
@@ -250,7 +250,7 @@ public class DefaultNotificationJobDelegate implements NotificationJobDelegate {
             notificationManager.setTaskExecuted(execution.getTask().getKey(), false);
 
             auditManager.audit(
-                    AuthContextUtils.getUsername(),
+                    AuthContextUtils.getWho(),
                     AuditElements.EventCategoryType.TASK,
                     "notification",
                     null,
@@ -264,7 +264,7 @@ public class DefaultNotificationJobDelegate implements NotificationJobDelegate {
             LOG.error("Maximum number of retries reached for task {} - giving up", execution.getTask());
 
             auditManager.audit(
-                    AuthContextUtils.getUsername(),
+                    AuthContextUtils.getWho(),
                     AuditElements.EventCategoryType.TASK,
                     "notification",
                     null,
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 3dc37d3..048b1e4 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -482,7 +482,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         if (notificationsAvailable || auditRequested) {
             ExecTO execTO = taskDataBinder.getExecTO(execution);
             notificationManager.createTasks(
-                    AuthContextUtils.getUsername(),
+                    AuthContextUtils.getWho(),
                     AuditElements.EventCategoryType.PROPAGATION,
                     anyTypeKind,
                     task.getResource().getKey(),
@@ -493,7 +493,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                     taskInfo);
 
             auditManager.audit(
-                    AuthContextUtils.getUsername(),
+                    AuthContextUtils.getWho(),
                     AuditElements.EventCategoryType.PROPAGATION,
                     anyTypeKind,
                     task.getResource().getKey(),
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
index 22b8bb9..94cb8af 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
@@ -923,7 +923,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
         }
 
         notificationManager.createTasks(
-                AuthContextUtils.getUsername(),
+                AuthContextUtils.getWho(),
                 AuditElements.EventCategoryType.PULL,
                 anyTypeKind.name().toLowerCase(),
                 profile.getTask().getResource().getKey(),
@@ -935,7 +935,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
                 furtherInput);
 
         auditManager.audit(
-                AuthContextUtils.getUsername(),
+                AuthContextUtils.getWho(),
                 AuditElements.EventCategoryType.PULL,
                 anyTypeKind.name().toLowerCase(),
                 profile.getTask().getResource().getKey(),
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
index 3c0e13b..70e229c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
@@ -478,7 +478,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
                 if (notificationsAvailable || auditRequested) {
                     Map<String, Object> jobMap = new HashMap<>();
                     jobMap.put(AfterHandlingEvent.JOBMAP_KEY, new AfterHandlingEvent(
-                            AuthContextUtils.getUsername(),
+                            AuthContextUtils.getWho(),
                             AuditElements.EventCategoryType.PUSH,
                             any.getType().getKind().name().toLowerCase(),
                             profile.getTask().getResource().getKey(),
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
index 33fb80f..aa1382b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
@@ -734,7 +734,7 @@ public class DefaultRealmPullResultHandler
         }
 
         notificationManager.createTasks(
-                AuthContextUtils.getUsername(),
+                AuthContextUtils.getWho(),
                 AuditElements.EventCategoryType.PULL,
                 SyncopeConstants.REALM_ANYTYPE.toLowerCase(),
                 profile.getTask().getResource().getKey(),
@@ -745,7 +745,7 @@ public class DefaultRealmPullResultHandler
                 delta);
 
         auditManager.audit(
-                AuthContextUtils.getUsername(),
+                AuthContextUtils.getWho(),
                 AuditElements.EventCategoryType.PULL,
                 SyncopeConstants.REALM_ANYTYPE.toLowerCase(),
                 profile.getTask().getResource().getKey(),
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPushResultHandler.java
index 4d3bf03..b89e630 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPushResultHandler.java
@@ -427,7 +427,7 @@ public class DefaultRealmPushResultHandler
                 if (notificationsAvailable || auditRequested) {
                     Map<String, Object> jobMap = new HashMap<>();
                     jobMap.put(AfterHandlingEvent.JOBMAP_KEY, new AfterHandlingEvent(
-                            AuthContextUtils.getUsername(),
+                            AuthContextUtils.getWho(),
                             AuditElements.EventCategoryType.PUSH,
                             SyncopeConstants.REALM_ANYTYPE.toLowerCase(),
                             profile.getTask().getResource().getKey(),
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java
index bf71aa8..68d381b 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java
@@ -77,7 +77,7 @@ public class ResourceDataBinderTest extends AbstractTest {
         UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                 new org.springframework.security.core.userdetails.User(
                         "admin", "FAKE_PASSWORD", authorities), "FAKE_PASSWORD", authorities);
-        auth.setDetails(new SyncopeAuthenticationDetails("Master"));
+        auth.setDetails(new SyncopeAuthenticationDetails(SyncopeConstants.MASTER_DOMAIN, null));
         SecurityContextHolder.getContext().setAuthentication(auth);
     }
 
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthContextUtils.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthContextUtils.java
index a6f217f..0728f08 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthContextUtils.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthContextUtils.java
@@ -19,22 +19,22 @@
 package org.apache.syncope.core.spring.security;
 
 import java.util.Collection;
-import org.apache.syncope.common.lib.types.EntitlementsHolder;
-
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.EntitlementsHolder;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.User;
 
@@ -59,29 +59,39 @@ public final class AuthContextUtils {
         SecurityContextHolder.getContext().setAuthentication(newAuth);
     }
 
-    public static Set<SyncopeGrantedAuthority> getAuthorities() {
-        SecurityContext ctx = SecurityContextHolder.getContext();
-        if (ctx != null && ctx.getAuthentication() != null && ctx.getAuthentication().getAuthorities() != null) {
-            return ctx.getAuthentication().getAuthorities().stream().
-                    filter(SyncopeGrantedAuthority.class::isInstance).
-                    map(SyncopeGrantedAuthority.class::cast).
-                    collect(Collectors.toSet());
-        }
+    public static Optional<String> getDelegatedBy() {
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
 
-        return Set.of();
+        return auth != null && auth.getDetails() instanceof SyncopeAuthenticationDetails
+                ? Optional.ofNullable(SyncopeAuthenticationDetails.class.cast(auth.getDetails()).getDelegatedBy())
+                : Optional.empty();
     }
 
-    public static Map<String, Set<String>> getAuthorizations() {
-        SecurityContext ctx = SecurityContextHolder.getContext();
-        if (ctx != null && ctx.getAuthentication() != null && ctx.getAuthentication().getAuthorities() != null) {
-            return ctx.getAuthentication().getAuthorities().stream().
-                    filter(SyncopeGrantedAuthority.class::isInstance).
-                    map(SyncopeGrantedAuthority.class::cast).
-                    collect(Collectors.toMap(
-                            SyncopeGrantedAuthority::getAuthority, SyncopeGrantedAuthority::getRealms));
-        }
+    public static String getWho() {
+        return getUsername() + getDelegatedBy().map(d -> {
+            String delegatedBy = callAsAdmin(getDomain(),
+                    () -> ApplicationContextProvider.getApplicationContext().getBean(UserDAO.class).findUsername(d)).
+                    orElse(d);
+            return " [delegated by " + delegatedBy + "]";
+        }).orElse(StringUtils.EMPTY);
+    }
+
+    public static Set<SyncopeGrantedAuthority> getAuthorities() {
+        return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()).
+                map(authentication -> authentication.getAuthorities().stream().
+                filter(SyncopeGrantedAuthority.class::isInstance).
+                map(SyncopeGrantedAuthority.class::cast).
+                collect(Collectors.toSet())).
+                orElse(Set.of());
+    }
 
-        return Map.of();
+    public static Map<String, Set<String>> getAuthorizations() {
+        return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()).
+                map(authentication -> authentication.getAuthorities().stream().
+                filter(SyncopeGrantedAuthority.class::isInstance).
+                map(SyncopeGrantedAuthority.class::cast).
+                collect(Collectors.toMap(SyncopeGrantedAuthority::getAuthority, SyncopeGrantedAuthority::getRealms))).
+                orElse(Map.of());
     }
 
     public static String getDomain() {
@@ -125,7 +135,7 @@ public final class AuthContextUtils {
                 collect(Collectors.toList());
         UsernamePasswordAuthenticationToken fakeAuth = new UsernamePasswordAuthenticationToken(
                 new User(username, FAKE_PASSWORD, authorities), FAKE_PASSWORD, authorities);
-        fakeAuth.setDetails(new SyncopeAuthenticationDetails(domain));
+        fakeAuth.setDetails(new SyncopeAuthenticationDetails(domain, getDelegatedBy().orElse(null)));
 
         return call(domain, fakeAuth, callable);
     }
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
index 75284ae..d7bc51e 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
@@ -31,7 +31,9 @@ import javax.annotation.Resource;
 import javax.security.auth.login.AccountNotFoundException;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -41,7 +43,11 @@ import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
+import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
@@ -49,17 +55,15 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.AccessToken;
-import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.Delegation;
 import org.apache.syncope.core.persistence.api.entity.DynRealm;
 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.resource.ExternalResource;
-import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.AuditManager;
 import org.apache.syncope.core.provisioning.api.ConnectorFactory;
 import org.apache.syncope.core.provisioning.api.MappingManager;
-import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.identityconnectors.framework.common.objects.Uid;
 import org.slf4j.Logger;
@@ -69,6 +73,8 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
 import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
 import org.springframework.security.authentication.DisabledException;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.web.authentication.session.SessionAuthenticationException;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -87,6 +93,9 @@ public class AuthDataAccessor {
     protected static final Set<SyncopeGrantedAuthority> ANONYMOUS_AUTHORITIES =
             Set.of(new SyncopeGrantedAuthority(IdRepoEntitlement.ANONYMOUS));
 
+    protected static final Set<SyncopeGrantedAuthority> MUST_CHANGE_PASSWORD_AUTHORITIES =
+            Set.of(new SyncopeGrantedAuthority(IdRepoEntitlement.MUST_CHANGE_PASSWORD));
+
     @Resource(name = "adminUser")
     protected String adminUser;
 
@@ -112,12 +121,15 @@ public class AuthDataAccessor {
     protected AccessTokenDAO accessTokenDAO;
 
     @Autowired
-    private ConfParamOps confParamOps;
+    protected ConfParamOps confParamOps;
 
     @Autowired
     protected RoleDAO roleDAO;
 
     @Autowired
+    protected DelegationDAO delegationDAO;
+
+    @Autowired
     protected ConnectorFactory connFactory;
 
     @Autowired
@@ -155,6 +167,20 @@ public class AuthDataAccessor {
         return provider;
     }
 
+    protected String getDelegationKey(final SyncopeAuthenticationDetails details, final String delegatedKey) {
+        return Optional.ofNullable(details.getDelegatedBy()).
+                map(delegatingKey -> SyncopeConstants.UUID_PATTERN.matcher(delegatingKey).matches()
+                ? delegatingKey
+                : userDAO.findKey(delegatingKey)).map(delegatingKey -> {
+
+            LOG.debug("Delegation request: delegating:{}, delegated:{}", delegatingKey, delegatedKey);
+
+            return delegationDAO.findValidFor(delegatingKey, delegatedKey).
+                    orElseThrow(() -> new SessionAuthenticationException(
+                    "Delegation by " + delegatingKey + " was requested but none found"));
+        }).orElse(null);
+    }
+
     /**
      * Attempts to authenticate the given credentials against internal storage and pass-through resources (if
      * configured): the first succeeding causes global success.
@@ -164,7 +190,7 @@ public class AuthDataAccessor {
      * @return {@code null} if no matching user was found, authentication result otherwise
      */
     @Transactional(noRollbackFor = DisabledException.class)
-    public Pair<User, Boolean> authenticate(final String domain, final Authentication authentication) {
+    public Triple<User, Boolean, String> authenticate(final String domain, final Authentication authentication) {
         User user = null;
 
         List<String> authAttrValues = List.of(confParamOps.get(domain,
@@ -187,6 +213,7 @@ public class AuthDataAccessor {
         }
 
         Boolean authenticated = null;
+        String delegationKey = null;
         if (user != null) {
             authenticated = false;
 
@@ -201,8 +228,11 @@ public class AuthDataAccessor {
             }
 
             boolean userModified = false;
-            authenticated = AuthDataAccessor.this.authenticate(user, authentication.getCredentials().toString());
+            authenticated = authenticate(user, authentication.getCredentials().toString());
             if (authenticated) {
+                delegationKey = getDelegationKey(
+                        SyncopeAuthenticationDetails.class.cast(authentication.getDetails()), user.getKey());
+
                 if (confParamOps.get(domain, "log.lastlogindate", true, Boolean.class)) {
                     user.setLastLoginDate(new Date());
                     userModified = true;
@@ -212,7 +242,6 @@ public class AuthDataAccessor {
                     user.setFailedLogins(0);
                     userModified = true;
                 }
-
             } else {
                 user.setFailedLogins(user.getFailedLogins() + 1);
                 userModified = true;
@@ -223,7 +252,7 @@ public class AuthDataAccessor {
             }
         }
 
-        return Pair.of(user, authenticated);
+        return Triple.of(user, authenticated, delegationKey);
     }
 
     protected boolean authenticate(final User user, final String password) {
@@ -285,84 +314,114 @@ public class AuthDataAccessor {
         return result == null ? Set.of() : result;
     }
 
-    protected static Set<SyncopeGrantedAuthority> getAdminAuthorities() {
+    protected Set<SyncopeGrantedAuthority> getAdminAuthorities() {
         return EntitlementsHolder.getInstance().getValues().stream().
                 map(entitlement -> new SyncopeGrantedAuthority(entitlement, SyncopeConstants.ROOT_REALM)).
                 collect(Collectors.toSet());
     }
 
-    protected Set<SyncopeGrantedAuthority> getUserAuthorities(final User user) {
+    protected Set<SyncopeGrantedAuthority> buildAuthorities(final Map<String, Set<String>> entForRealms) {
         Set<SyncopeGrantedAuthority> authorities = new HashSet<>();
 
+        entForRealms.forEach((entitlement, realms) -> {
+            Pair<Set<String>, Set<String>> normalized = RealmUtils.normalize(realms);
+
+            SyncopeGrantedAuthority authority = new SyncopeGrantedAuthority(entitlement);
+            authority.addRealms(normalized.getLeft());
+            authority.addRealms(normalized.getRight());
+            authorities.add(authority);
+        });
+
+        return authorities;
+    }
+
+    protected Set<SyncopeGrantedAuthority> getUserAuthorities(final User user) {
         if (user.isMustChangePassword()) {
-            authorities.add(new SyncopeGrantedAuthority(IdRepoEntitlement.MUST_CHANGE_PASSWORD));
-        } else {
-            Map<String, Set<String>> entForRealms = new HashMap<>();
-
-            // Give entitlements as assigned by roles (with static or dynamic realms, where applicable) - assigned
-            // either statically and dynamically
-            userDAO.findAllRoles(user).stream().
-                    filter(role -> !SyncopeConstants.GROUP_OWNER_ROLE.equals(role.getKey())).
-                    forEach(role -> role.getEntitlements().forEach(entitlement -> {
-                Set<String> realms = Optional.ofNullable(entForRealms.get(entitlement)).orElseGet(() -> {
-                    Set<String> r = new HashSet<>();
-                    entForRealms.put(entitlement, r);
-                    return r;
-                });
+            return MUST_CHANGE_PASSWORD_AUTHORITIES;
+        }
 
-                realms.addAll(role.getRealms().stream().map(Realm::getFullPath).collect(Collectors.toSet()));
-                if (!entitlement.endsWith("_CREATE") && !entitlement.endsWith("_DELETE")) {
-                    realms.addAll(role.getDynRealms().stream().map(DynRealm::getKey).collect(Collectors.toList()));
-                }
-            }));
+        Map<String, Set<String>> entForRealms = new HashMap<>();
+
+        // Give entitlements as assigned by roles (with static or dynamic realms, where applicable) - assigned
+        // either statically and dynamically
+        userDAO.findAllRoles(user).stream().
+                filter(role -> !SyncopeConstants.GROUP_OWNER_ROLE.equals(role.getKey())).
+                forEach(role -> role.getEntitlements().forEach(entitlement -> {
+            Set<String> realms = Optional.ofNullable(entForRealms.get(entitlement)).orElseGet(() -> {
+                Set<String> r = new HashSet<>();
+                entForRealms.put(entitlement, r);
+                return r;
+            });
 
-            // Give group entitlements for owned groups
-            groupDAO.findOwnedByUser(user.getKey()).forEach(group -> {
-                Role groupOwnerRole = roleDAO.find(SyncopeConstants.GROUP_OWNER_ROLE);
-                if (groupOwnerRole == null) {
-                    LOG.warn("Role {} was not found", SyncopeConstants.GROUP_OWNER_ROLE);
-                } else {
-                    groupOwnerRole.getEntitlements().forEach(entitlement -> {
-                        Set<String> realms = Optional.ofNullable(entForRealms.get(entitlement)).orElseGet(() -> {
-                            HashSet<String> r = new HashSet<>();
-                            entForRealms.put(entitlement, r);
-                            return r;
-                        });
-
-                        realms.add(RealmUtils.getGroupOwnerRealm(group.getRealm().getFullPath(), group.getKey()));
+            realms.addAll(role.getRealms().stream().map(Realm::getFullPath).collect(Collectors.toSet()));
+            if (!entitlement.endsWith("_CREATE") && !entitlement.endsWith("_DELETE")) {
+                realms.addAll(role.getDynRealms().stream().map(DynRealm::getKey).collect(Collectors.toList()));
+            }
+        }));
+
+        // Give group entitlements for owned groups
+        groupDAO.findOwnedByUser(user.getKey()).forEach(group -> {
+            Role groupOwnerRole = roleDAO.find(SyncopeConstants.GROUP_OWNER_ROLE);
+            if (groupOwnerRole == null) {
+                LOG.warn("Role {} was not found", SyncopeConstants.GROUP_OWNER_ROLE);
+            } else {
+                groupOwnerRole.getEntitlements().forEach(entitlement -> {
+                    Set<String> realms = Optional.ofNullable(entForRealms.get(entitlement)).orElseGet(() -> {
+                        HashSet<String> r = new HashSet<>();
+                        entForRealms.put(entitlement, r);
+                        return r;
                     });
-                }
-            });
 
-            // Finally normalize realms for each given entitlement and generate authorities
-            entForRealms.forEach((key, value) -> {
-                Pair<Set<String>, Set<String>> normalized = RealmUtils.normalize(value);
+                    realms.add(RealmUtils.getGroupOwnerRealm(group.getRealm().getFullPath(), group.getKey()));
+                });
+            }
+        });
+
+        return buildAuthorities(entForRealms);
+    }
+
+    protected Set<SyncopeGrantedAuthority> getDelegatedAuthorities(final Delegation delegation) {
+        Map<String, Set<String>> entForRealms = new HashMap<>();
 
-                SyncopeGrantedAuthority authority = new SyncopeGrantedAuthority(key);
-                authority.addRealms(normalized.getLeft());
-                authority.addRealms(normalized.getRight());
-                authorities.add(authority);
+        delegation.getRoles().stream().filter(role -> !SyncopeConstants.GROUP_OWNER_ROLE.equals(role.getKey())).
+                forEach(role -> role.getEntitlements().forEach(entitlement -> {
+            Set<String> realms = Optional.ofNullable(entForRealms.get(entitlement)).orElseGet(() -> {
+                HashSet<String> r = new HashSet<>();
+                entForRealms.put(entitlement, r);
+                return r;
             });
-        }
 
-        return authorities;
+            realms.addAll(role.getRealms().stream().map(Realm::getFullPath).collect(Collectors.toSet()));
+            if (!entitlement.endsWith("_CREATE") && !entitlement.endsWith("_DELETE")) {
+                realms.addAll(role.getDynRealms().stream().map(DynRealm::getKey).collect(Collectors.toList()));
+            }
+        }));
+
+        return buildAuthorities(entForRealms);
     }
 
     @Transactional
-    public Set<SyncopeGrantedAuthority> getAuthorities(final String username) {
+    public Set<SyncopeGrantedAuthority> getAuthorities(final String username, final String delegationKey) {
         Set<SyncopeGrantedAuthority> authorities;
 
         if (anonymousUser.equals(username)) {
             authorities = ANONYMOUS_AUTHORITIES;
         } else if (adminUser.equals(username)) {
             authorities = getAdminAuthorities();
+        } else if (delegationKey != null) {
+            Delegation delegation = Optional.ofNullable(delegationDAO.find(delegationKey)).
+                    orElseThrow(() -> new UsernameNotFoundException(
+                    "Could not find delegation " + delegationKey));
+
+            authorities = delegation.getRoles().isEmpty()
+                    ? getUserAuthorities(delegation.getDelegating())
+                    : getDelegatedAuthorities(delegation);
         } else {
-            User user = userDAO.findByUsername(username);
-            if (user == null) {
-                authorities = Set.of();
-            } else {
-                authorities = getUserAuthorities(user);
-            }
+            User user = Optional.ofNullable(userDAO.findByUsername(username)).
+                    orElseThrow(() -> new UsernameNotFoundException(
+                    "Could not find any user with username " + username));
+
+            authorities = getUserAuthorities(user);
         }
 
         return authorities;
@@ -392,12 +451,19 @@ public class AuthDataAccessor {
             }
 
             User user = resolved.getLeft();
+            String delegationKey = getDelegationKey(authentication.getDetails(), user.getKey());
             username = user.getUsername();
-            authorities = resolved.getRight() == null ? Set.of() : resolved.getRight();
+            authorities = resolved.getRight() == null
+                    ? Set.of()
+                    : delegationKey == null
+                            ? resolved.getRight()
+                            : getAuthorities(username, delegationKey);
             LOG.debug("JWT {} issued by {} resolved to User {} with authorities {}",
                     authentication.getClaims().getJWTID(),
                     authentication.getClaims().getIssuer(),
-                    username, authorities);
+                    username + Optional.ofNullable(delegationKey).
+                            map(d -> " [under delegation " + delegationKey + "]").orElse(StringUtils.EMPTY),
+                    authorities);
 
             if (BooleanUtils.isTrue(user.isSuspended())) {
                 throw new DisabledException("User " + username + " is suspended");
@@ -411,7 +477,7 @@ public class AuthDataAccessor {
 
             if (BooleanUtils.isTrue(user.isMustChangePassword())) {
                 LOG.debug("User {} must change password, resetting authorities", username);
-                authorities = Set.of(new SyncopeGrantedAuthority(IdRepoEntitlement.MUST_CHANGE_PASSWORD));
+                authorities = MUST_CHANGE_PASSWORD_AUTHORITIES;
             }
         }
 
@@ -425,16 +491,16 @@ public class AuthDataAccessor {
 
     @Transactional(readOnly = true)
     public void audit(
-            final String who,
-            final AuditElements.EventCategoryType type,
-            final String category,
-            final String subcategory,
-            final String event,
+            final String username,
+            final String delegationKey,
             final AuditElements.Result result,
-            final Object before,
             final Object output,
             final Object... input) {
 
-        auditManager.audit(who, type, category, subcategory, event, result, before, output, input);
+        auditManager.audit(
+                username + Optional.ofNullable(delegationKey).
+                        map(d -> " [under delegation " + delegationKey + "]").orElse(StringUtils.EMPTY),
+                AuditElements.EventCategoryType.LOGIC, AuditElements.AUTHENTICATION_CATEGORY, null,
+                AuditElements.LOGIN_EVENT, result, null, output, input);
     }
 }
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
index de86f94..b6d80e7 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetails.java
@@ -33,12 +33,16 @@ public class SyncopeAuthenticationDetails implements Serializable {
 
     private final String domain;
 
+    private final String delegatedBy;
+
     public SyncopeAuthenticationDetails(final HttpServletRequest request) {
         this.domain = request.getHeader(RESTHeaders.DOMAIN);
+        this.delegatedBy = request.getHeader(RESTHeaders.DELEGATED_BY);
     }
 
-    public SyncopeAuthenticationDetails(final String domain) {
+    public SyncopeAuthenticationDetails(final String domain, final String delegatedBy) {
         this.domain = domain;
+        this.delegatedBy = delegatedBy;
     }
 
     public String getDomain() {
@@ -47,10 +51,15 @@ public class SyncopeAuthenticationDetails implements Serializable {
                 : domain;
     }
 
+    public String getDelegatedBy() {
+        return delegatedBy;
+    }
+
     @Override
     public int hashCode() {
         return new HashCodeBuilder().
                 append(domain).
+                append(delegatedBy).
                 build();
     }
 
@@ -68,6 +77,7 @@ public class SyncopeAuthenticationDetails implements Serializable {
         final SyncopeAuthenticationDetails other = (SyncopeAuthenticationDetails) obj;
         return new EqualsBuilder().
                 append(domain, other.domain).
+                append(delegatedBy, other.delegatedBy).
                 build();
     }
 
@@ -75,6 +85,7 @@ public class SyncopeAuthenticationDetails implements Serializable {
     public String toString() {
         return new ToStringBuilder(this).
                 append(domain).
+                append(delegatedBy).
                 build();
     }
 }
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java
index 0b2b27c..5f82832 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeAuthenticationDetailsSource.java
@@ -28,5 +28,4 @@ public class SyncopeAuthenticationDetailsSource
     public SyncopeAuthenticationDetails buildDetails(final HttpServletRequest context) {
         return new SyncopeAuthenticationDetails(context);
     }
-
 }
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
index 76313ec..a8728fd 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
@@ -18,14 +18,14 @@
  */
 package org.apache.syncope.core.spring.security;
 
+import java.util.concurrent.atomic.AtomicReference;
 import javax.annotation.Resource;
 import org.apache.commons.lang3.BooleanUtils;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.common.keymaster.client.api.DomainOps;
 import org.apache.syncope.common.keymaster.client.api.KeymasterException;
 import org.apache.syncope.common.keymaster.client.api.model.Domain;
 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.NotFoundException;
@@ -112,15 +112,16 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
             }
         }
 
-        String[] username = new String[1];
-        boolean authenticated;
+        AtomicReference<String> username = new AtomicReference<>();
+        Boolean authenticated;
+        AtomicReference<String> delegationKey = new AtomicReference<>();
 
         if (anonymousUser.equals(authentication.getName())) {
-            username[0] = anonymousUser;
+            username.set(anonymousUser);
             credentialChecker.checkIsDefaultAnonymousKeyInUse();
             authenticated = authentication.getCredentials().toString().equals(anonymousKey);
         } else if (adminUser.equals(authentication.getName())) {
-            username[0] = adminUser;
+            username.set(adminUser);
             if (SyncopeConstants.MASTER_DOMAIN.equals(domain.getKey())) {
                 credentialChecker.checkIsDefaultAdminPasswordInUse();
                 authenticated = ENCRYPTOR.verify(
@@ -134,13 +135,13 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
                         domain.getAdminPassword());
             }
         } else {
-            Pair<User, Boolean> authResult = AuthContextUtils.callAsAdmin(domain.getKey(),
+            Triple<User, Boolean, String> authResult = AuthContextUtils.callAsAdmin(domain.getKey(),
                     () -> dataAccessor.authenticate(domain.getKey(), authentication));
-            authenticated = BooleanUtils.toBoolean(authResult.getRight());
-            if (authResult.getLeft() != null && authResult.getRight() != null) {
-                username[0] = authResult.getLeft().getUsername();
+            authenticated = authResult.getMiddle();
+            if (authResult.getLeft() != null && authResult.getMiddle() != null) {
+                username.set(authResult.getLeft().getUsername());
 
-                if (!authResult.getRight()) {
+                if (!authenticated) {
                     AuthContextUtils.callAsAdmin(domain.getKey(), () -> {
                         provisioningManager.internalSuspend(
                                 authResult.getLeft().getKey(), adminUser, "Failed authentication");
@@ -148,57 +149,51 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
                     });
                 }
             }
+            delegationKey.set(authResult.getRight());
         }
-        if (username[0] == null) {
-            username[0] = authentication.getPrincipal().toString();
+        if (username.get() == null) {
+            username.set(authentication.getPrincipal().toString());
         }
 
-        return finalizeAuthentication(authenticated, domain.getKey(), username[0], authentication);
+        return finalizeAuthentication(
+                authenticated, domain.getKey(), username.get(), delegationKey.get(), authentication);
     }
 
     protected Authentication finalizeAuthentication(
-            final boolean authenticated,
+            final Boolean authenticated,
             final String domain,
             final String username,
+            final String delegationKey,
             final Authentication authentication) {
 
         UsernamePasswordAuthenticationToken token;
-        if (authenticated) {
+        if (BooleanUtils.isTrue(authenticated)) {
             token = AuthContextUtils.callAsAdmin(domain, () -> {
                 UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken(
                         username,
                         null,
-                        dataAccessor.getAuthorities(username));
+                        dataAccessor.getAuthorities(username, delegationKey));
                 upat.setDetails(authentication.getDetails());
                 dataAccessor.audit(
                         username,
-                        AuditElements.EventCategoryType.LOGIC,
-                        AuditElements.AUTHENTICATION_CATEGORY,
-                        null,
-                        AuditElements.LOGIN_EVENT,
+                        delegationKey,
                         Result.SUCCESS,
-                        null,
-                        authenticated,
+                        true,
                         authentication,
-                        "User " + username + " successfully authenticated with entitlements: " + upat.getAuthorities());
+                        "Successfully authenticated, with entitlements: " + upat.getAuthorities());
                 return upat;
             });
 
-            LOG.debug("User {} successfully authenticated, with entitlements {}",
-                    username, token.getAuthorities());
+            LOG.debug("User {} successfully authenticated, with entitlements {}", username, token.getAuthorities());
         } else {
             AuthContextUtils.callAsAdmin(domain, () -> {
                 dataAccessor.audit(
                         username,
-                        AuditElements.EventCategoryType.LOGIC,
-                        AuditElements.AUTHENTICATION_CATEGORY,
-                        null,
-                        AuditElements.LOGIN_EVENT,
+                        delegationKey,
                         Result.FAILURE,
-                        null,
-                        authenticated,
+                        false,
                         authentication,
-                        "User " + username + " not authenticated");
+                        "Not authenticated");
                 return null;
             });
 
diff --git a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractWorkflowAdapter.java b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractWorkflowAdapter.java
index 422ec1f..1f9ef76 100644
--- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractWorkflowAdapter.java
+++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractWorkflowAdapter.java
@@ -19,20 +19,24 @@
 package org.apache.syncope.core.workflow.java;
 
 import java.util.Date;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 
 public abstract class AbstractWorkflowAdapter {
 
     protected void metadata(final Any<?> any, final String username, final String context) {
+        String who = username
+                + AuthContextUtils.getDelegatedBy().map(d -> " [delegated by " + d + "]").orElse(StringUtils.EMPTY);
         Date now = new Date();
 
         if (any.getCreationDate() == null) {
             any.setCreationDate(now);
-            any.setCreator(username);
+            any.setCreator(who);
             any.setCreationContext(context);
         }
 
-        any.setLastModifier(username);
+        any.setLastModifier(who);
         any.setLastChangeDate(now);
         any.setLastChangeContext(context);
     }
diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java
index 5e5d25a..548d227 100644
--- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java
+++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java
@@ -229,7 +229,7 @@ public class OIDCC4UILogic extends AbstractTransactionalLogic<EntityTO> {
         byte[] authorities = null;
         try {
             authorities = ENCRYPTOR.encode(POJOHelper.serialize(
-                    authDataAccessor.getAuthorities(loginResponse.getUsername())), CipherAlgorithm.AES).
+                    authDataAccessor.getAuthorities(loginResponse.getUsername(), null)), CipherAlgorithm.AES).
                     getBytes();
         } catch (Exception e) {
             LOG.error("Could not fetch authorities", e);
diff --git a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java
index c523b85..a69e0d0 100644
--- a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java
+++ b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java
@@ -407,7 +407,7 @@ public class SAML2SP4UILogic extends AbstractTransactionalLogic<EntityTO> {
         byte[] authorities = null;
         try {
             authorities = ENCRYPTOR.encode(POJOHelper.serialize(
-                    authDataAccessor.getAuthorities(loginResp.getUsername())), CipherAlgorithm.AES).getBytes();
+                    authDataAccessor.getAuthorities(loginResp.getUsername(), null)), CipherAlgorithm.AES).getBytes();
         } catch (Exception e) {
             LOG.error("Could not fetch authorities", e);
         }
diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
index e5f837f..55ebc01 100644
--- a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
+++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
@@ -82,9 +82,9 @@ public class SCIMDataBinder {
     private AuthDataAccessor authDataAccessor;
 
     private static <E extends Enum<?>> void fill(
-        final Map<String, Attr> attrs,
-        final List<SCIMComplexConf<E>> confs,
-        final List<SCIMComplexValue> values) {
+            final Map<String, Attr> attrs,
+            final List<SCIMComplexConf<E>> confs,
+            final List<SCIMComplexValue> values) {
 
         confs.forEach(conf -> {
             SCIMComplexValue value = new SCIMComplexValue();
@@ -109,19 +109,19 @@ public class SCIMDataBinder {
     }
 
     private static boolean output(
-        final List<String> attributes,
-        final List<String> excludedAttributes,
-        final String schema) {
+            final List<String> attributes,
+            final List<String> excludedAttributes,
+            final String schema) {
 
         return (attributes.isEmpty() || attributes.contains(schema))
                 && (excludedAttributes.isEmpty() || !excludedAttributes.contains(schema));
     }
 
     private static <T> T output(
-        final List<String> attributes,
-        final List<String> excludedAttributes,
-        final String schema,
-        final T value) {
+            final List<String> attributes,
+            final List<String> excludedAttributes,
+            final String schema,
+            final T value) {
 
         return output(attributes, excludedAttributes, schema)
                 ? value
@@ -399,7 +399,7 @@ public class SCIMDataBinder {
             }
 
             if (output(attributes, excludedAttributes, "entitlements")) {
-                authDataAccessor.getAuthorities(userTO.getUsername()).forEach(authority -> user.getEntitlements().
+                authDataAccessor.getAuthorities(userTO.getUsername(), null).forEach(authority -> user.getEntitlements().
                         add(new Value(authority.getAuthority() + " on Realm(s) " + authority.getRealms())));
             }
 
@@ -412,18 +412,14 @@ public class SCIMDataBinder {
     }
 
     private static <E extends Enum<?>> void fill(
-        final Set<Attr> attrs,
-        final List<SCIMComplexConf<E>> confs,
-        final List<SCIMComplexValue> values) {
-
-        values.forEach(value -> {
-            if (value.getType() != null) {
-                confs.stream().
-                        filter(object -> value.getType().equals(object.getType().name())).findFirst().
-                        ifPresent(conf -> attrs.add(
-                        new Attr.Builder(conf.getValue()).value(value.getValue()).build()));
-            }
-        });
+            final Set<Attr> attrs,
+            final List<SCIMComplexConf<E>> confs,
+            final List<SCIMComplexValue> values) {
+
+        values.stream().filter(value -> value.getType() != null).forEach(value -> confs.stream().
+                filter(object -> value.getType().equals(object.getType().name())).findFirst().
+                ifPresent(conf -> attrs.add(
+                new Attr.Builder(conf.getValue()).value(value.getValue()).build())));
     }
 
     public UserTO toUserTO(final SCIMUser user) {
diff --git a/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/security/SelfKeymasterUsernamePasswordAuthenticationProvider.java b/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/security/SelfKeymasterUsernamePasswordAuthenticationProvider.java
index 2068326..68b63db 100644
--- a/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/security/SelfKeymasterUsernamePasswordAuthenticationProvider.java
+++ b/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/security/SelfKeymasterUsernamePasswordAuthenticationProvider.java
@@ -40,6 +40,7 @@ public class SelfKeymasterUsernamePasswordAuthenticationProvider extends Usernam
                     authentication.getCredentials().toString().equals(keymasterPassword),
                     SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain(),
                     keymasterUsername,
+                    null,
                     authentication);
         }
 
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
index 8b2137a..891cd63 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
@@ -101,7 +101,7 @@ public class CustomJWTSSOProvider implements JWTSSOProvider {
         List<User> matching = searchDAO.search(SearchCond.getLeaf(userIdCond), AnyTypeKind.USER);
         if (matching.size() == 1) {
             User user = matching.get(0);
-            Set<SyncopeGrantedAuthority> authorities = authDataAccessor.getAuthorities(user.getUsername());
+            Set<SyncopeGrantedAuthority> authorities = authDataAccessor.getAuthorities(user.getUsername(), null);
 
             return Pair.of(user, authorities);
         }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index da72e84..ef208bc 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -63,6 +63,7 @@ import org.apache.syncope.common.lib.request.AnyObjectUR;
 import org.apache.syncope.common.lib.request.AttrPatch;
 import org.apache.syncope.common.lib.request.GroupUR;
 import org.apache.syncope.common.lib.request.UserUR;
+import org.apache.syncope.common.lib.log.AuditEntry;
 import org.apache.syncope.common.lib.policy.PolicyTO;
 import org.apache.syncope.common.lib.request.AnyObjectCR;
 import org.apache.syncope.common.lib.request.GroupCR;
@@ -102,6 +103,7 @@ import org.apache.syncope.common.lib.types.TraceLevel;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.batch.BatchPayloadParser;
 import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
+import org.apache.syncope.common.rest.api.beans.AuditQuery;
 import org.apache.syncope.common.rest.api.service.AnyObjectService;
 import org.apache.syncope.common.rest.api.service.AnyTypeClassService;
 import org.apache.syncope.common.rest.api.service.AnyTypeService;
@@ -145,6 +147,7 @@ import org.apache.syncope.common.rest.api.service.SAML2SP4UIIdPService;
 import org.apache.syncope.common.rest.api.service.SAML2SP4UIService;
 import org.apache.syncope.common.rest.api.service.SAML2SPEntityService;
 import org.apache.syncope.common.rest.api.service.SRARouteService;
+import org.apache.syncope.common.rest.api.service.DelegationService;
 import org.apache.syncope.common.rest.api.service.UserWorkflowTaskService;
 import org.apache.syncope.common.rest.api.service.wa.ImpersonationService;
 import org.apache.syncope.common.rest.api.service.wa.U2FRegistrationService;
@@ -312,6 +315,8 @@ public abstract class AbstractITCase {
 
     protected static RemediationService remediationService;
 
+    protected static DelegationService delegationService;
+
     protected static SRARouteService sraRouteService;
 
     protected static CamelRouteService camelRouteService;
@@ -410,6 +415,7 @@ public abstract class AbstractITCase {
         securityQuestionService = adminClient.getService(SecurityQuestionService.class);
         implementationService = adminClient.getService(ImplementationService.class);
         remediationService = adminClient.getService(RemediationService.class);
+        delegationService = adminClient.getService(DelegationService.class);
         sraRouteService = adminClient.getService(SRARouteService.class);
         camelRouteService = adminClient.getService(CamelRouteService.class);
         saml2SP4UIService = adminClient.getService(SAML2SP4UIService.class);
@@ -876,4 +882,18 @@ public abstract class AbstractITCase {
 
         return policy;
     }
+
+    protected static List<AuditEntry> query(final AuditQuery query, final int maxWaitSeconds) {
+        int i = 0;
+        List<AuditEntry> results = List.of();
+        do {
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+            }
+            results = loggerService.search(query).getResult();
+            i++;
+        } while (results.isEmpty() && i < maxWaitSeconds);
+        return results;
+    }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/SecurityQuestionsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/SecurityQuestionsITCase.java
index 329369e..c8c0d25 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/SecurityQuestionsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/SecurityQuestionsITCase.java
@@ -39,7 +39,7 @@ public class SecurityQuestionsITCase extends AbstractConsoleITCase {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
         TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security");
         TESTER.assertRenderedPage(Security.class);
-        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:3:link");
+        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:4:link");
     }
 
     private static void createSecurityQuestion(final String name) {
@@ -60,7 +60,7 @@ public class SecurityQuestionsITCase extends AbstractConsoleITCase {
         TESTER.cleanupFeedbackMessages();
 
         TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security");
-        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:3:link");
+        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:4:link");
     }
 
     @Test
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
index 31304d2..c7a3a17 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.fit.core;
 
-import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.fail;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -29,8 +28,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -45,35 +42,22 @@ import org.apache.syncope.common.lib.types.ConnConfProperty;
 import org.apache.syncope.common.lib.types.ConnectorCapability;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.beans.AuditQuery;
+import org.apache.syncope.core.logic.ConnectorLogic;
+import org.apache.syncope.core.logic.UserLogic;
 import org.apache.syncope.fit.AbstractITCase;
 import org.junit.jupiter.api.Test;
 
 public class AuditITCase extends AbstractITCase {
 
-    private static AuditEntry query(final AuditQuery query, final int maxWaitSeconds, final boolean failIfEmpty) {
+    private static AuditEntry queryWithFailure(final AuditQuery query, final int maxWaitSeconds) {
         List<AuditEntry> results = query(query, maxWaitSeconds);
         if (results.isEmpty()) {
-            if (failIfEmpty) {
-                fail("Timeout when executing query for key " + query.getEntityKey());
-            }
+            fail("Timeout when executing query for key " + query.getEntityKey());
             return null;
         }
         return results.get(0);
     }
 
-    private static List<AuditEntry> query(final AuditQuery query, final int maxWaitSeconds) {
-        AtomicReference<List<AuditEntry>> holder = new AtomicReference<>();
-        await().atMost(maxWaitSeconds, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> {
-            try {
-                holder.set(loggerService.search(query).getResult());
-                return !holder.get().isEmpty();
-            } catch (Exception e) {
-                return false;
-            }
-        });
-        return holder.get();
-    }
-
     @Test
     public void userReadAndSearchYieldsNoAudit() {
         UserTO userTO = createUser(UserITCase.getUniqueSample("audit@syncope.org")).getEntity();
@@ -102,7 +86,7 @@ public class AuditITCase extends AbstractITCase {
 
         AuditQuery query = new AuditQuery.Builder().entityKey(userTO.getKey()).orderBy("event_date desc").
                 page(1).size(1).build();
-        AuditEntry entry = query(query, MAX_WAIT_SECONDS, true);
+        AuditEntry entry = queryWithFailure(query, MAX_WAIT_SECONDS);
         assertNotNull(entry);
         userService.delete(userTO.getKey());
     }
@@ -118,11 +102,11 @@ public class AuditITCase extends AbstractITCase {
                 page(1).
                 size(1).
                 type(AuditElements.EventCategoryType.LOGIC).
-                category("UserLogic").
+                category(UserLogic.class.getSimpleName()).
                 event("create").
                 result(AuditElements.Result.SUCCESS).
                 build();
-        AuditEntry entry = query(query, MAX_WAIT_SECONDS, true);
+        AuditEntry entry = queryWithFailure(query, MAX_WAIT_SECONDS);
         assertNotNull(entry);
         userService.delete(userTO.getKey());
     }
@@ -134,7 +118,7 @@ public class AuditITCase extends AbstractITCase {
 
         AuditQuery query = new AuditQuery.Builder().entityKey(groupTO.getKey()).orderBy("event_date desc").
                 page(1).size(1).build();
-        AuditEntry entry = query(query, MAX_WAIT_SECONDS, true);
+        AuditEntry entry = queryWithFailure(query, MAX_WAIT_SECONDS);
         assertNotNull(entry);
         groupService.delete(groupTO.getKey());
     }
@@ -148,11 +132,9 @@ public class AuditITCase extends AbstractITCase {
         List<AuditEntry> entries = query(query, MAX_WAIT_SECONDS);
         assertEquals(1, entries.size());
 
-        PagedResult<GroupTO> groups = groupService.search(
-                new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
-                        fiql(SyncopeClient.getGroupSearchConditionBuilder().
-                                is("name").equalTo(groupTO.getName()).query()).
-                        build());
+        PagedResult<GroupTO> groups = groupService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
+                fiql(SyncopeClient.getGroupSearchConditionBuilder().is("name").equalTo(groupTO.getName()).query()).
+                build());
         assertNotNull(groups);
         assertFalse(groups.getResult().isEmpty());
 
@@ -164,9 +146,9 @@ public class AuditITCase extends AbstractITCase {
     public void findByAnyObject() {
         AnyObjectTO anyObjectTO = createAnyObject(AnyObjectITCase.getSample("Italy")).getEntity();
         assertNotNull(anyObjectTO.getKey());
-        AuditQuery query = new AuditQuery.Builder().entityKey(anyObjectTO.getKey()).orderBy("event_date desc").
-                page(1).size(1).build();
-        AuditEntry entry = query(query, MAX_WAIT_SECONDS, true);
+        AuditQuery query = new AuditQuery.Builder().entityKey(anyObjectTO.getKey()).
+                orderBy("event_date desc").page(1).size(1).build();
+        AuditEntry entry = queryWithFailure(query, MAX_WAIT_SECONDS);
         assertNotNull(entry);
         anyObjectService.delete(anyObjectTO.getKey());
     }
@@ -199,7 +181,7 @@ public class AuditITCase extends AbstractITCase {
                 entityKey(connectorKey).
                 orderBy("event_date desc").
                 type(AuditElements.EventCategoryType.LOGIC).
-                category("ConnectorLogic").
+                category(ConnectorLogic.class.getSimpleName()).
                 event("update").
                 result(AuditElements.Result.SUCCESS).
                 build();
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
index 1e7ca0b..9b65273 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
@@ -26,13 +26,14 @@ import static org.junit.jupiter.api.Assertions.fail;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 import java.security.AccessControlException;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import javax.ws.rs.ForbiddenException;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
 import org.apache.syncope.client.lib.BasicAuthenticationHandler;
 import org.apache.syncope.client.lib.SyncopeClient;
@@ -101,22 +102,25 @@ public class AuthenticationITCase extends AbstractITCase {
         }
 
         // 2. as anonymous
-        Pair<Map<String, Set<String>>, UserTO> self = clientFactory.create(
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self = clientFactory.create(
                 new AnonymousAuthenticationHandler(ANONYMOUS_UNAME, ANONYMOUS_KEY)).self();
         assertEquals(1, self.getLeft().size());
         assertTrue(self.getLeft().keySet().contains(IdRepoEntitlement.ANONYMOUS));
+        assertEquals(List.of(), self.getMiddle());
         assertEquals(ANONYMOUS_UNAME, self.getRight().getUsername());
 
         // 3. as admin
         self = adminClient.self();
         assertEquals(syncopeService.platform().getEntitlements().size(), self.getLeft().size());
         assertFalse(self.getLeft().keySet().contains(IdRepoEntitlement.ANONYMOUS));
+        assertEquals(List.of(), self.getMiddle());
         assertEquals(ADMIN_UNAME, self.getRight().getUsername());
 
         // 4. as user
         self = clientFactory.create("bellini", ADMIN_PWD).self();
         assertFalse(self.getLeft().isEmpty());
         assertFalse(self.getLeft().keySet().contains(IdRepoEntitlement.ANONYMOUS));
+        assertEquals(List.of(), self.getMiddle());
         assertEquals("bellini", self.getRight().getUsername());
     }
 
@@ -606,10 +610,11 @@ public class AuthenticationITCase extends AbstractITCase {
         assertEquals("active", userTO.getStatus());
 
         // 4. try to authenticate again: success
-        Pair<Map<String, Set<String>>, UserTO> self =
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self =
                 clientFactory.create(userTO.getUsername(), "password123").self();
         assertNotNull(self);
         assertNotNull(self.getLeft());
+        assertEquals(List.of(), self.getMiddle());
         assertNotNull(self.getRight());
     }
 
@@ -642,7 +647,7 @@ public class AuthenticationITCase extends AbstractITCase {
         assertEquals(Encryptor.getInstance().encode("password123", CipherAlgorithm.SHA1), value.toUpperCase());
 
         // 5. successfully authenticate with old (on db resource) and new (on internal storage) password values
-        Pair<Map<String, Set<String>>, UserTO> self =
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self =
                 clientFactory.create(user.getUsername(), "password123").self();
         assertNotNull(self);
         self = clientFactory.create(user.getUsername(), "password234").self();
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DelegationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DelegationITCase.java
new file mode 100644
index 0000000..02a65eb
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DelegationITCase.java
@@ -0,0 +1,272 @@
+/*
+ * 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.fit.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.security.AccessControlException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.ws.rs.ForbiddenException;
+import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.log.AuditEntry;
+import org.apache.syncope.common.lib.log.LoggerTO;
+import org.apache.syncope.common.lib.request.UserCR;
+import org.apache.syncope.common.lib.to.DelegationTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.LoggerLevel;
+import org.apache.syncope.common.lib.types.LoggerType;
+import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.beans.AuditQuery;
+import org.apache.syncope.common.rest.api.service.DelegationService;
+import org.apache.syncope.common.rest.api.service.UserService;
+import org.apache.syncope.core.logic.UserLogic;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.Test;
+
+public class DelegationITCase extends AbstractITCase {
+
+    private DelegationTO create(final DelegationService ds, final DelegationTO delegation) {
+        Response response = ds.create(delegation);
+        if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
+            Exception ex = clientFactory.getExceptionMapper().fromResponse(response);
+            if (ex != null) {
+                throw (RuntimeException) ex;
+            }
+        }
+        return getObject(response.getLocation(), DelegationService.class, DelegationTO.class);
+    }
+
+    @Test
+    public void crudAsAdmin() {
+        // 1. create users        
+        UserCR delegatingCR = UserITCase.getUniqueSample("delegating@syncope.apache.org");
+        delegatingCR.getRoles().add("User reviewer");
+        UserTO delegating = createUser(delegatingCR).getEntity();
+        assertNotNull(delegating.getKey());
+
+        UserCR delegatedCR = UserITCase.getUniqueSample("delegated@syncope.apache.org");
+        UserTO delegated = createUser(delegatedCR).getEntity();
+        assertNotNull(delegated.getKey());
+
+        DelegationTO delegation = new DelegationTO();
+        delegation.setDelegating(delegating.getKey());
+        delegation.setDelegated(delegated.getKey());
+
+        // no dates set -> FAIL
+        try {
+            delegationService.create(delegation);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.InvalidEntity, e.getType());
+        }
+
+        delegation.setStart(new Date());
+        delegation.setEnd(new Date(System.currentTimeMillis() - 1000));
+
+        // end before start -> FAIL
+        try {
+            delegationService.create(delegation);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.InvalidEntity, e.getType());
+        }
+
+        delegation.setEnd(new Date());
+
+        // 2. create delegation
+        delegation = create(delegationService, delegation);
+        assertNotNull(delegation.getKey());
+        assertNotNull(delegation.getEnd());
+
+        // 3. verify delegation is reported for users
+        delegating = userService.read(delegating.getKey());
+        assertEquals(List.of(delegation.getKey()), delegating.getDelegatingDelegations());
+        assertEquals(List.of(), delegating.getDelegatedDelegations());
+
+        delegated = userService.read(delegated.getKey());
+        assertEquals(List.of(), delegated.getDelegatingDelegations());
+        assertEquals(List.of(delegation.getKey()), delegated.getDelegatedDelegations());
+
+        // 4. update and read delegation
+        delegation.setEnd(null);
+        delegationService.update(delegation);
+
+        delegation = delegationService.read(delegation.getKey());
+        assertNull(delegation.getEnd());
+
+        // 5. delete delegation
+        delegationService.delete(delegation.getKey());
+
+        try {
+            delegationService.read(delegation.getKey());
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+        }
+
+        // 6. verify delegation is not reported for users
+        delegating = userService.read(delegating.getKey());
+        assertEquals(List.of(), delegating.getDelegatingDelegations());
+        assertEquals(List.of(), delegating.getDelegatedDelegations());
+
+        delegated = userService.read(delegated.getKey());
+        assertEquals(List.of(), delegated.getDelegatingDelegations());
+        assertEquals(List.of(), delegated.getDelegatedDelegations());
+    }
+
+    @Test
+    public void crudAsUser() {
+        // 1. create users        
+        UserCR delegatingCR = UserITCase.getUniqueSample("delegating@syncope.apache.org");
+        delegatingCR.getRoles().add("User reviewer");
+        UserTO delegating = createUser(delegatingCR).getEntity();
+        assertNotNull(delegating.getKey());
+
+        UserCR delegatedCR = UserITCase.getUniqueSample("delegated@syncope.apache.org");
+        UserTO delegated = createUser(delegatedCR).getEntity();
+        assertNotNull(delegated.getKey());
+
+        DelegationTO delegation = new DelegationTO();
+        delegation.setDelegating("c9b2dec2-00a7-4855-97c0-d854842b4b24");
+        delegation.setDelegated(delegated.getKey());
+        delegation.setStart(new Date());
+
+        DelegationService uds = clientFactory.create(delegating.getUsername(), "password123").
+                getService(DelegationService.class);
+
+        // delegating user is not requesting user -> FAIL
+        try {
+            create(uds, delegation);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.DelegatedAdministration, e.getType());
+        }
+
+        // 2. create delegation
+        delegation.setDelegating(delegating.getKey());
+
+        delegation = create(uds, delegation);
+        assertNotNull(delegation.getKey());
+        assertNull(delegation.getEnd());
+
+        // 3. update and read delegation
+        delegation.setEnd(new Date());
+        uds.update(delegation);
+
+        delegation = uds.read(delegation.getKey());
+        assertNotNull(delegation.getEnd());
+
+        // 4. delete delegation
+        uds.delete(delegation.getKey());
+
+        try {
+            uds.read(delegation.getKey());
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+        }
+    }
+
+    @Test
+    public void operations() {
+        // 0. enable audit
+        AuditLoggerName authLoginSuccess = new AuditLoggerName(
+                AuditElements.EventCategoryType.LOGIC,
+                UserLogic.class.getSimpleName(),
+                null,
+                "search",
+                AuditElements.Result.SUCCESS);
+        LoggerTO authLogin = new LoggerTO();
+        authLogin.setKey(authLoginSuccess.toLoggerName());
+        authLogin.setLevel(LoggerLevel.DEBUG);
+        loggerService.update(LoggerType.AUDIT, authLogin);
+
+        // 1. bellini delegates rossini
+        DelegationTO delegation = new DelegationTO();
+        delegation.setDelegating("bellini");
+        delegation.setDelegated("rossini");
+        delegation.setStart(new Date());
+        delegation = create(delegationService, delegation);
+        assertNotNull(delegation.getKey());
+
+        // 2. search users as bellini
+        SyncopeClient bellini = clientFactory.create("bellini", "password");
+        int forBellini = bellini.getService(UserService.class).search(
+                new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).build()).getTotalCount();
+
+        SyncopeClient rossini = clientFactory.create("rossini", "password");
+
+        // 3. search users as rossini
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self = rossini.self();
+        assertEquals(List.of("bellini"), self.getMiddle());
+
+        // 3a. search users as rossini without delegation -> FAIL
+        try {
+            rossini.getService(UserService.class).search(
+                    new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).build());
+            fail();
+        } catch (ForbiddenException e) {
+            assertNotNull(e);
+        }
+
+        // 3b. search users as rossini with delegation -> SUCCESS
+        int forRossini = rossini.delegatedBy("bellini").getService(UserService.class).search(
+                new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).build()).getTotalCount();
+        assertEquals(forBellini, forRossini);
+
+        // 4. delete delegation: searching users as rossini does not work, even with delegation
+        delegationService.delete(delegation.getKey());
+
+        try {
+            rossini.getService(UserService.class).search(
+                    new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).build());
+            fail();
+        } catch (AccessControlException e) {
+            assertNotNull(e);
+        }
+
+        // 5. query audit entries
+        AuditQuery query = new AuditQuery.Builder().
+                type(authLoginSuccess.getType()).
+                category(authLoginSuccess.getCategory()).
+                event(authLoginSuccess.getEvent()).
+                result(authLoginSuccess.getResult()).
+                build();
+        List<AuditEntry> entries = query(query, MAX_WAIT_SECONDS);
+        assertTrue(entries.stream().anyMatch(entry -> "rossini [delegated by bellini]".equals(entry.getWho())));
+
+        // 6. disable audit
+        authLogin.setLevel(LoggerLevel.OFF);
+        loggerService.update(LoggerType.AUDIT, authLogin);
+    }
+}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
index 1ebefd7..741442d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java
@@ -42,12 +42,13 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.RandomStringUtils;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.request.UserCR;
@@ -375,7 +376,7 @@ public class JWTITCase extends AbstractITCase {
 
         SyncopeClient jwtClient = clientFactory.create(signed);
 
-        Pair<Map<String, Set<String>>, UserTO> self = jwtClient.self();
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self = jwtClient.self();
         assertFalse(self.getLeft().isEmpty());
         assertEquals("puccini", self.getRight().getUsername());
     }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
index ee05318..843fa42 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
@@ -36,6 +36,7 @@ import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.Date;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
@@ -47,7 +48,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import javax.ws.rs.core.Response;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.SerializationUtils;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -1176,7 +1177,8 @@ public class PullTaskITCase extends AbstractTaskITCase {
         assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
 
         // 5. Test the pulled user
-        Pair<Map<String, Set<String>>, UserTO> self = clientFactory.create(user.getUsername(), newCleanPassword).self();
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self =
+                clientFactory.create(user.getUsername(), newCleanPassword).self();
         assertNotNull(self);
 
         // 6. Delete PullTask + user
@@ -1211,7 +1213,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
             user = updateUser(userUR).getEntity();
 
             // 3. Check that the Syncope user now has the changed password
-            Pair<Map<String, Set<String>>, UserTO> self =
+            Triple<Map<String, Set<String>>, List<String>, UserTO> self =
                     clientFactory.create(user.getUsername(), newCleanPassword).self();
             assertNotNull(self);
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
index 8e965ef..d2f3cca 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
@@ -41,7 +41,7 @@ import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.time.DateFormatUtils;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.client.lib.batch.BatchRequest;
@@ -336,7 +336,7 @@ public class UserITCase extends AbstractITCase {
 
         // 3. verify password
         try {
-            Pair<Map<String, Set<String>>, UserTO> self =
+            Triple<Map<String, Set<String>>, List<String>, UserTO> self =
                     clientFactory.create(userTO.getUsername(), "password123").self();
             assertNotNull(self);
         } catch (AccessControlException e) {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
index 4467344..f104ff0 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
@@ -38,7 +38,7 @@ import java.util.Set;
 import javax.naming.NamingException;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.SyncopeClientException;
@@ -1142,7 +1142,7 @@ public class UserIssuesITCase extends AbstractITCase {
         assertEquals(
                 "passwordTESTNULL1",
                 connObjectTO.getAttr(OperationalAttributes.PASSWORD_NAME).get().getValues().get(0));
-        Pair<Map<String, Set<String>>, UserTO> self =
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self =
                 clientFactory.create(userTO.getUsername(), "passwordTESTNULL1").self();
         assertNotNull(self);
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
index 3fb1544..040c11d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
@@ -35,7 +35,7 @@ import javax.ws.rs.ForbiddenException;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -198,7 +198,8 @@ public class UserSelfITCase extends AbstractITCase {
             assertNotNull(e);
         }
 
-        Pair<Map<String, Set<String>>, UserTO> self = clientFactory.create(user.getUsername(), "password123").self();
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self =
+                clientFactory.create(user.getUsername(), "password123").self();
         assertEquals(user.getUsername(), self.getRight().getUsername());
     }
 
@@ -209,7 +210,7 @@ public class UserSelfITCase extends AbstractITCase {
         String userId = rossini.getPlainAttr("userId").get().getValues().get(0);
         assertNotNull(userId);
 
-        Pair<Map<String, Set<String>>, UserTO> self = clientFactory.create(userId, ADMIN_PWD).self();
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self = clientFactory.create(userId, ADMIN_PWD).self();
         assertEquals(rossini.getUsername(), self.getRight().getUsername());
     }
 
@@ -454,7 +455,8 @@ public class UserSelfITCase extends AbstractITCase {
         vivaldiClient.getService(UserSelfService.class).mustChangePassword("password123");
 
         // 4. verify it worked
-        Pair<Map<String, Set<String>>, UserTO> self = clientFactory.create("vivaldi", "password123").self();
+        Triple<Map<String, Set<String>>, List<String>, UserTO> self =
+                clientFactory.create("vivaldi", "password123").self();
         assertFalse(self.getRight().isMustChangePassword());
     }
 
diff --git a/src/main/asciidoc/reference-guide/concepts/roles.adoc b/src/main/asciidoc/reference-guide/concepts/roles.adoc
index 9f5073b..216de6c 100644
--- a/src/main/asciidoc/reference-guide/concepts/roles.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/roles.adoc
@@ -127,3 +127,23 @@ The actual Entitlements are assigned through the predefined `GROUP_OWNER` Role:
 . `GROUP_READ`
 . `GROUP_UPDATE`
 . `GROUP_DELETE`
+
+The `GROUP_OWNER` Role can be updated to adjust the set of assigned Entitlements.
+
+==== Delegation
+
+With Delegation, any user can delegate other users to perform operations on their behalf.
+
+In order to set up a Delegation, the following information shall be provided:
+
+* delegating User (mandatory) - administrators granted with `DELEGATION_CREATE` Entitlement can create Delegations for
+all defined Users; otherwise, the only accepted value is the User itself;
+* delegated User (mandatory) - any User defined, distinct from delegating;
+* start (mandatory) - initial timestamp from which the Delegation is considered effective;
+* end (optional) - final timestamp after which the Delegation is not considered effective: when not provided, Delegation
+will remain valid unless deleted;
+* roles (optional) - set of Roles granted by delegating to delegated User: only Roles owned by delegating can be
+granted, when not provided all owned Roles are considered as part of the Delegation.
+
+[NOTE]
+<<Audit>> entries generated when operating under Delegation will report both delegating and delegated users.
diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/restfulservices.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/restfulservices.adoc
index a70fecf..8d9600c 100644
--- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/restfulservices.adoc
+++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/restfulservices.adoc
@@ -156,6 +156,11 @@ https://github.com/apache/syncope/blob/master/common/lib/src/main/java/org/apach
 endif::[]
 and `X-Application-Error-Info` might be optionally populated with more details, if available.
 
+===== X-Syncope-Delegated-By
+
+When requesting an operation under <<delegation,Delegation>>, this header must be provided to indicate the delegating
+User, either by their username or key.
+
 ===== X-Syncope-Null-Priority-Async
 
 When set to `true`, this request header instructs the <<propagation,propagation process>> not to wait for completion
@@ -201,6 +206,11 @@ Groups and Any Objects operations.
 When invoking the REST endpoint `/users/self` in `GET`, the `X-Syncope-Entitlements` response header will list all
 the <<entitlements,entitlements>> owned by the requesting user.
 
+===== X-Syncope-Delegations
+
+When invoking the REST endpoint `/users/self` in `GET`, the `X-Syncope-Delegations` response header will list all
+delegating users for each <<delegation,Delegation>> for which the requesting user is delegated.
+
 ===== X-Syncope-Privileges
 
 When invoking the REST endpoint `/users/self` in `GET`, the `X-Syncope-Privileges` response header will list all