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