You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by mm...@apache.org on 2020/02/06 11:20:38 UTC
[syncope] branch 2_1_X updated: SYNCOPE-1506: Merge Accounts (#153)
This is an automated email from the ASF dual-hosted git repository.
mmoayyed pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/2_1_X by this push:
new 112acd6 SYNCOPE-1506: Merge Accounts (#153)
112acd6 is described below
commit 112acd62d3f0d0a637de204f9d999f28122432f0
Author: Misagh Moayyed <mm...@gmail.com>
AuthorDate: Thu Feb 6 15:20:30 2020 +0400
SYNCOPE-1506: Merge Accounts (#153)
* initial pass at merging users/accounts
* clean up config; working on wizard and user selection
* clean up config; working on wizard and user selection
* polish
* working on account review wizard page
* working on batch items
* working on batch requests
* testing batch ops
* fix tests
* fix test
* fix formatting issues
* apply changes based on review suggestions
* working on ITs for merge-accounts feature
* updated test
* adjustments to query filter
* tweaks to UI
* cntd with wicket changes and merging functionality
* working on merge wizard. added panels to show resources, and preview
* add panel for preview
* polish
* polish
* cntd with tests - wip
* addressing feedback after review - tests WIP.
* fix issues after review
* highlight item when selected in data table
* work out events with item selection after the merge
* fix tests - let ci run
* fix checkstyle
---
.../panels/MergeLinkedAccountsResourcesPanel.java | 182 +++++++++++++++++++
.../panels/MergeLinkedAccountsReviewPanel.java | 177 ++++++++++++++++++
.../panels/MergeLinkedAccountsSearchPanel.java | 112 ++++++++++++
.../client/console/panels/UserDirectoryPanel.java | 20 +++
.../client/console/rest/ResourceRestClient.java | 15 ++
.../repeater/data/table/AjaxFallbackDataTable.java | 1 +
.../wicket/markup/html/form/ActionLink.java | 1 +
.../syncope/client/console/wizards/AjaxWizard.java | 9 +-
.../any/MergeLinkedAccountsWizardBuilder.java | 200 +++++++++++++++++++++
.../any/MergeLinkedAccountsWizardModel.java | 58 ++++++
.../panels/MergeLinkedAccountsResourcesPanel.html | 24 +++
...> MergeLinkedAccountsResourcesPanel.properties} | 7 +-
...eLinkedAccountsResourcesPanel_fr_CA.properties} | 7 +-
...ergeLinkedAccountsResourcesPanel_it.properties} | 7 +-
...ergeLinkedAccountsResourcesPanel_ja.properties} | 7 +-
...eLinkedAccountsResourcesPanel_pt_BR.properties} | 7 +-
...ergeLinkedAccountsResourcesPanel_ru.properties} | 7 +-
.../panels/MergeLinkedAccountsReviewPanel.html | 24 +++
...s => MergeLinkedAccountsReviewPanel.properties} | 10 +-
...ergeLinkedAccountsReviewPanel_fr_CA.properties} | 10 +-
...> MergeLinkedAccountsReviewPanel_it.properties} | 10 +-
...> MergeLinkedAccountsReviewPanel_ja.properties} | 10 +-
...ergeLinkedAccountsReviewPanel_pt_BR.properties} | 10 +-
...> MergeLinkedAccountsReviewPanel_ru.properties} | 10 +-
.../panels/MergeLinkedAccountsSearchPanel.html | 31 ++++
...s => MergeLinkedAccountsSearchPanel.properties} | 5 +-
...ergeLinkedAccountsSearchPanel_fr_CA.properties} | 5 +-
...> MergeLinkedAccountsSearchPanel_it.properties} | 5 +-
...> MergeLinkedAccountsSearchPanel_ja.properties} | 5 +-
...ergeLinkedAccountsSearchPanel_pt_BR.properties} | 5 +-
...> MergeLinkedAccountsSearchPanel_ru.properties} | 5 +-
.../console/panels/UserDirectoryPanel.properties | 1 +
.../panels/UserDirectoryPanel_fr_CA.properties | 7 +-
.../panels/UserDirectoryPanel_it.properties | 1 +
.../panels/UserDirectoryPanel_ja.properties | 1 +
.../panels/UserDirectoryPanel_pt_BR.properties | 1 +
.../panels/UserDirectoryPanel_ru.properties | 1 +
.../markup/html/form/ActionsPanel.properties | 4 +
.../syncope/common/rest/api/RESTHeaders.java | 2 +
.../common/rest/api/service/ResourceService.java | 25 +++
.../apache/syncope/core/logic/ResourceLogic.java | 24 +++
.../core/rest/cxf/service/ResourceServiceImpl.java | 6 +
.../syncope/fit/console/LinkedAccountsITCase.java | 197 ++++++++++++++++++++
.../apache/syncope/fit/console/PoliciesITCase.java | 4 +-
.../apache/syncope/fit/console/UsersITCase.java | 4 +-
45 files changed, 1184 insertions(+), 80 deletions(-)
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.java
new file mode 100644
index 0000000..628b1a6
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.java
@@ -0,0 +1,182 @@
+/*
+ * 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 org.apache.syncope.client.console.SyncopeConsoleApplication;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.DirectoryDataProvider;
+import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.pages.BasePage;
+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.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+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.extensions.wizard.WizardModel.ICondition;
+import org.apache.wicket.extensions.wizard.WizardStep;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+public class MergeLinkedAccountsResourcesPanel extends WizardStep implements ICondition {
+ private static final long serialVersionUID = 1221037007528732347L;
+
+ private final MergeLinkedAccountsWizardModel wizardModel;
+
+ public MergeLinkedAccountsResourcesPanel(final MergeLinkedAccountsWizardModel wizardModel,
+ final PageReference pageReference) {
+ super();
+ setOutputMarkupId(true);
+ this.wizardModel = wizardModel;
+ add(new ResourceSelectionDirectoryPanel("resources", pageReference));
+ }
+
+ @Override
+ public boolean evaluate() {
+ return SyncopeConsoleApplication.get().getSecuritySettings().getAuthorizationStrategy().
+ isActionAuthorized(this, RENDER);
+ }
+
+ @Override
+ public String getTitle() {
+ setSummaryModel(new StringResourceModel("mergeLinkedAccounts.searchResource.summary",
+ Model.of(wizardModel.getMergingUser())));
+ setTitleModel(new StringResourceModel("mergeLinkedAccounts.searchResource.title",
+ Model.of(wizardModel.getMergingUser())));
+ return super.getTitle();
+ }
+
+ private class ResourceSelectionDirectoryPanel extends
+ DirectoryPanel<ResourceTO, ResourceTO,
+ ResourceSelectionDirectoryPanel.ResourcesDataProvider, ResourceRestClient> {
+
+ private static final long serialVersionUID = 6005711052393825472L;
+
+ ResourceSelectionDirectoryPanel(final String id, final PageReference pageReference) {
+ super(id, pageReference, true);
+
+ this.restClient = new ResourceRestClient();
+ modal.size(Modal.Size.Large);
+ setOutputMarkupId(true);
+ disableCheckBoxes();
+ initResultTable();
+ }
+
+ @Override
+ protected ResourcesDataProvider dataProvider() {
+ return new ResourcesDataProvider(this.rows);
+ }
+
+ @Override
+ protected String paginatorRowsKey() {
+ return Constants.PREF_RESOURCES_PAGINATOR_ROWS;
+ }
+
+ @Override
+ protected List<IColumn<ResourceTO, String>> getColumns() {
+ List<IColumn<ResourceTO, String>> columns = new ArrayList<>();
+ columns.add(new PropertyColumn<>(new ResourceModel("resource"), "key", "key"));
+ return columns;
+ }
+
+ @Override
+ protected ActionsPanel<ResourceTO> getActions(final IModel<ResourceTO> model) {
+ final ActionsPanel<ResourceTO> panel = super.getActions(model);
+ panel.add(new ActionLink<ResourceTO>() {
+ private static final long serialVersionUID = -7978723352517770644L;
+
+ @Override
+ public void onClick(final AjaxRequestTarget target, final ResourceTO resource) {
+ MergeLinkedAccountsWizardModel model = MergeLinkedAccountsResourcesPanel.this.wizardModel;
+ String connObjectKeyValue = restClient.getConnObjectKeyValue(
+ resource.getKey(),
+ model.getMergingUser().getType(),
+ model.getMergingUser().getKey());
+ if (connObjectKeyValue != null) {
+ model.setResource(resource);
+ String tableId = MergeLinkedAccountsResourcesPanel.this.
+ get("resources:container:content:searchContainer:resultTable"
+ + ":tablePanel:groupForm:checkgroup:dataTable").
+ getMarkupId();
+ String js = "$('#" + tableId + "').removeClass('active');";
+ js += "$('#" + tableId + " tbody tr td div').filter(function() "
+ + "{return $(this).text() === \"" + resource.getKey() + "\";})"
+ + ".parent().parent().addClass('active');";
+ target.prependJavaScript(js);
+
+ } else {
+ error("Unable to determine connector object key");
+ ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+ }
+ }
+ }, ActionLink.ActionType.SELECT, StandardEntitlement.RESOURCE_READ);
+ return panel;
+ }
+
+ @Override
+ protected Collection<ActionLink.ActionType> getBatches() {
+ return Collections.emptyList();
+ }
+
+ protected final class ResourcesDataProvider extends DirectoryDataProvider<ResourceTO> {
+
+ private static final long serialVersionUID = -185944053385660794L;
+
+ private final SortableDataProviderComparator<ResourceTO> comparator;
+
+ private ResourcesDataProvider(final int paginatorRows) {
+ super(paginatorRows);
+ setSort("key", SortOrder.ASCENDING);
+ comparator = new SortableDataProviderComparator<>(this);
+ }
+
+ @Override
+ public Iterator<ResourceTO> iterator(final long first, final long count) {
+ List<ResourceTO> list = restClient.list();
+ Collections.sort(list, comparator);
+ return list.subList((int) first, (int) first + (int) count).iterator();
+ }
+
+ @Override
+ public long size() {
+ return restClient.list().size();
+ }
+
+ @Override
+ public IModel<ResourceTO> model(final ResourceTO object) {
+ return new CompoundPropertyModel<>(object);
+ }
+ }
+ }
+}
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.java
new file mode 100644
index 0000000..73ab52a
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.java
@@ -0,0 +1,177 @@
+/*
+ * 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 org.apache.syncope.client.console.commons.DirectoryDataProvider;
+import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+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.common.lib.to.LinkedAccountTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+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.extensions.wizard.WizardStep;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MergeLinkedAccountsReviewPanel extends WizardStep {
+ private static final long serialVersionUID = 1221037007528732347L;
+
+ public MergeLinkedAccountsReviewPanel(final MergeLinkedAccountsWizardModel wizardModel,
+ final PageReference pageReference) {
+ super();
+ setOutputMarkupId(true);
+ setTitleModel(new StringResourceModel("mergeLinkedAccounts.reviewAccounts.title"));
+ add(new LinkedAccountsReviewDirectoryPanel("linkedAccounts", pageReference, wizardModel));
+ }
+
+ private static class LinkedAccountsReviewDirectoryPanel extends
+ DirectoryPanel<LinkedAccountTO, LinkedAccountTO,
+ LinkedAccountsReviewDirectoryPanel.LinkedAccountsDataProvider, ResourceRestClient> {
+
+ private static final String PAGINATOR_ROWS = "linked.account.review.paginator.rows";
+
+ private static final long serialVersionUID = 6005711052393825472L;
+
+ private final MergeLinkedAccountsWizardModel wizardModel;
+
+ LinkedAccountsReviewDirectoryPanel(final String id, final PageReference pageReference,
+ final MergeLinkedAccountsWizardModel wizardModel) {
+ super(id, pageReference, true);
+ this.restClient = new ResourceRestClient();
+ this.wizardModel = wizardModel;
+ modal.size(Modal.Size.Large);
+ setOutputMarkupId(true);
+ disableCheckBoxes();
+ initResultTable();
+ }
+
+ @Override
+ protected LinkedAccountsDataProvider dataProvider() {
+ return new LinkedAccountsDataProvider(this.rows);
+ }
+
+ @Override
+ protected String paginatorRowsKey() {
+ return PAGINATOR_ROWS;
+ }
+
+ @Override
+ protected List<IColumn<LinkedAccountTO, String>> getColumns() {
+ List<IColumn<LinkedAccountTO, String>> columns = new ArrayList<>();
+ columns.add(new PropertyColumn<>(new ResourceModel("resource"), "resource", "resource"));
+ columns.add(new PropertyColumn<>(
+ new ResourceModel("connObjectKeyValue"), "connObjectKeyValue", "connObjectKeyValue"));
+ columns.add(new PropertyColumn<>(
+ new ResourceModel("username"), "username", "username"));
+ columns.add(new BooleanPropertyColumn<>(
+ new ResourceModel("suspended"), "suspended", "suspended"));
+ return columns;
+ }
+
+ @Override
+ protected Collection<ActionLink.ActionType> getBatches() {
+ return Collections.emptyList();
+ }
+
+ private List<LinkedAccountTO> previewAccounts() {
+ UserTO mergingUser = wizardModel.getMergingUser();
+
+ // Move linked accounts into the target/base user as linked accounts
+ List<LinkedAccountTO> accounts = mergingUser.getLinkedAccounts().stream().map(acct -> {
+ LinkedAccountTO linkedAccount =
+ new LinkedAccountTO.Builder(acct.getResource(), acct.getConnObjectKeyValue())
+ .password(acct.getPassword())
+ .suspended(acct.isSuspended())
+ .username(acct.getUsername())
+ .build();
+ linkedAccount.getPlainAttrs().addAll(acct.getPlainAttrs());
+ linkedAccount.getPrivileges().addAll(acct.getPrivileges());
+ return linkedAccount;
+ }).collect(Collectors.toList());
+
+ // Move merging user's resources into the target/base user as a linked account
+ accounts.addAll(mergingUser.getResources().stream().map(resource -> {
+ String connObjectKeyValue = restClient.getConnObjectKeyValue(resource,
+ mergingUser.getType(), mergingUser.getKey());
+ return new LinkedAccountTO.Builder(resource, connObjectKeyValue).build();
+ }).collect(Collectors.toList()));
+
+ // Move merging user into target/base user as a linked account
+ String connObjectKeyValue = restClient.getConnObjectKeyValue(
+ wizardModel.getResource().getKey(),
+ mergingUser.getType(), mergingUser.getKey());
+ LinkedAccountTO linkedAccount =
+ new LinkedAccountTO.Builder(wizardModel.getResource().getKey(), connObjectKeyValue)
+ .password(mergingUser.getPassword())
+ .suspended(mergingUser.isSuspended())
+ .username(mergingUser.getUsername())
+ .build();
+ linkedAccount.getPlainAttrs().addAll(mergingUser.getPlainAttrs());
+ linkedAccount.getPrivileges().addAll(mergingUser.getPrivileges());
+ accounts.add(linkedAccount);
+
+ return accounts;
+ }
+
+ protected final class LinkedAccountsDataProvider extends DirectoryDataProvider<LinkedAccountTO> {
+
+ private static final long serialVersionUID = -185944053385660794L;
+
+ private final SortableDataProviderComparator<LinkedAccountTO> comparator;
+
+ private LinkedAccountsDataProvider(final int paginatorRows) {
+ super(paginatorRows);
+ setSort("resource", SortOrder.ASCENDING);
+ comparator = new SortableDataProviderComparator<>(this);
+ }
+
+ @Override
+ public Iterator<LinkedAccountTO> iterator(final long first, final long count) {
+ List<LinkedAccountTO> list = previewAccounts();
+ Collections.sort(list, comparator);
+ return list.subList((int) first, (int) first + (int) count).iterator();
+ }
+
+ @Override
+ public long size() {
+ return previewAccounts().size();
+ }
+
+ @Override
+ public IModel<LinkedAccountTO> model(final LinkedAccountTO object) {
+ return new CompoundPropertyModel<>(object);
+ }
+ }
+ }
+}
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.java
new file mode 100644
index 0000000..5e8d55a
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.java
@@ -0,0 +1,112 @@
+/*
+ * 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.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.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;
+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.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Fragment;
+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;
+
+ private final UserSearchPanel userSearchPanel;
+
+ private final AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient();
+
+ private final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient();
+
+ private final UserSelectionDirectoryPanel userDirectoryPanel;
+
+ private final Fragment userSearchFragment;
+
+ private final MergeLinkedAccountsWizardModel wizardModel;
+
+ public MergeLinkedAccountsSearchPanel(final MergeLinkedAccountsWizardModel model, final PageReference pageRef) {
+ super();
+ setOutputMarkupId(true);
+
+ this.wizardModel = model;
+ setTitleModel(new StringResourceModel("mergeLinkedAccounts.searchUser", Model.of(model.getBaseUser())));
+ ownerContainer = new WebMarkupContainer("ownerContainer");
+ ownerContainer.setOutputMarkupId(true);
+ add(ownerContainer);
+
+ userSearchFragment = new Fragment("search", "userSearchFragment", this);
+ userSearchPanel = UserSearchPanel.class.cast(new UserSearchPanel.Builder(
+ 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"));
+
+ userSearchFragment.add(userDirectoryPanel);
+ ownerContainer.add(userSearchFragment);
+ }
+
+ @Override
+ public void onEvent(final IEvent<?> event) {
+ 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());
+ userDirectoryPanel.search(fiql, target);
+ } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) {
+ AnySelectionDirectoryPanel.ItemSelection payload =
+ (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();
+ String js = "$('#" + tableId + " tr').removeClass('active');";
+ js += "$('#" + tableId + " td[title=" + sel.getKey() + "]').parent().addClass('active');";
+ payload.getTarget().prependJavaScript(js);
+ }
+ }
+}
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
index 7c437ea..5d3bc79b 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
@@ -40,6 +40,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
import org.apache.syncope.client.console.wizards.AjaxWizard;
import org.apache.syncope.client.console.wizards.WizardMgtPanel;
import org.apache.syncope.client.console.wizards.any.AnyWrapper;
+import org.apache.syncope.client.console.wizards.any.MergeLinkedAccountsWizardBuilder;
import org.apache.syncope.client.console.wizards.any.UserWrapper;
import org.apache.syncope.common.lib.AnyOperations;
import org.apache.syncope.common.lib.SyncopeConstants;
@@ -342,6 +343,25 @@ public class UserDirectoryPanel extends AnyDirectoryPanel<UserTO, UserRestClient
}, ActionType.MANAGE_ACCOUNTS,
String.format("%s,%s,%s", StandardEntitlement.USER_READ, StandardEntitlement.USER_UPDATE,
StandardEntitlement.RESOURCE_GET_CONNOBJECT));
+
+ if (wizardInModal) {
+ panel.add(new ActionLink<UserTO>() {
+ private static final long serialVersionUID = 8011039414597736111L;
+
+ @Override
+ public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
+ model.setObject(UserRestClient.class.cast(restClient).read(model.getObject().getKey()));
+ MergeLinkedAccountsWizardBuilder builder =
+ new MergeLinkedAccountsWizardBuilder(model, pageRef, UserDirectoryPanel.this, modal);
+ builder.setEventSink(builder);
+ target.add(modal.setContent(builder.build(BaseModal.CONTENT_ID, AjaxWizard.Mode.CREATE)));
+ modal.header(new StringResourceModel("mergeLinkedAccounts.title", model));
+ modal.show(true);
+ }
+ }, ActionType.MERGE_ACCOUNTS,
+ String.format("%s,%s,%s,%s", StandardEntitlement.USER_READ, StandardEntitlement.USER_UPDATE,
+ StandardEntitlement.USER_DELETE, StandardEntitlement.RESOURCE_GET_CONNOBJECT));
+ }
}
if (wizardInModal) {
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
index a90911f..1e0fdf3 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
@@ -27,6 +27,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.PagedConnObjectTOResult;
import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery;
import org.apache.syncope.common.rest.api.service.ResourceService;
import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -56,6 +57,20 @@ public class ResourceRestClient extends BaseRestClient {
return getService(ResourceService.class).readConnObject(resource, anyTypeKey, anyKey);
}
+ public String getConnObjectKeyValue(final String resource, final String anyTypeKey, final String anyKey) {
+ try {
+ Response response = getService(ResourceService.class).getConnObjectKeyValue(resource, anyTypeKey, anyKey);
+ if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) {
+ return response.getHeaderString(RESTHeaders.CONNOBJECT_KEY);
+ }
+ } catch (Exception e) {
+ LOG.debug("Error fetching connector object key", e);
+ }
+ LOG.error("Unable to determine connector object key value for resource {}, {} and {}",
+ resource, anyTypeKey, anyKey);
+ return null;
+ }
+
public Pair<String, List<ConnObjectTO>> searchConnObjects(
final String resource,
final String anyTypeKey,
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java
index ecd70d0..a3ffc29 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/AjaxFallbackDataTable.java
@@ -119,6 +119,7 @@ public class AjaxFallbackDataTable<T extends Serializable, S> extends DataTable<
return null;
}
+
@Override
protected Item<T> newRowItem(final String id, final int index, final IModel<T> model) {
final OddEvenItem<T> item = new OddEvenItem<>(id, index, model);
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
index d62156f..306bbf3 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
@@ -93,6 +93,7 @@ public abstract class ActionLink<T extends Serializable> implements Serializable
RECONCILIATION_PULL("update"),
MANAGE_RESOURCES("update"),
MANAGE_ACCOUNTS("update"),
+ MERGE_ACCOUNTS("update"),
MANAGE_USERS("update"),
MANAGE_GROUPS("update"),
PROPAGATION_TASKS("read"),
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java
index b1e71ce..7485492 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizard.java
@@ -81,6 +81,8 @@ public abstract class AjaxWizard<T extends Serializable> extends Wizard
private final PageReference pageRef;
+ private AjaxWizardMgtButtonBar<T> buttonBar;
+
/**
* Construct.
*
@@ -154,7 +156,12 @@ public abstract class AjaxWizard<T extends Serializable> extends Wizard
@Override
protected Component newButtonBar(final String id) {
- return new AjaxWizardMgtButtonBar<>(id, this, mode);
+ this.buttonBar = new AjaxWizardMgtButtonBar<>(id, this, mode);
+ return this.buttonBar;
+ }
+
+ public AjaxWizardMgtButtonBar<T> getButtonBar() {
+ return buttonBar;
}
protected abstract void onCancelInternal();
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java
new file mode 100644
index 0000000..ed9a119
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java
@@ -0,0 +1,200 @@
+/*
+ * 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.any;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+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;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wizards.AjaxWizard;
+import org.apache.syncope.client.console.wizards.AjaxWizardBuilder;
+import org.apache.syncope.client.lib.batch.BatchRequest;
+import org.apache.syncope.common.lib.patch.LinkedAccountPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.LinkedAccountTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.PatchOperation;
+import org.apache.syncope.common.rest.api.Preference;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.event.IEventSink;
+import org.apache.wicket.extensions.wizard.WizardModel;
+import org.apache.wicket.model.IModel;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class MergeLinkedAccountsWizardBuilder extends AjaxWizardBuilder<UserTO> implements IEventSink {
+ private static final long serialVersionUID = -9142332740863374891L;
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private final UserDirectoryPanel parentPanel;
+
+ private final BaseModal<?> modal;
+
+ private MergeLinkedAccountsWizardModel model;
+
+ public MergeLinkedAccountsWizardBuilder(final IModel<UserTO> model, final PageReference pageRef,
+ final UserDirectoryPanel parentPanel, final BaseModal<?> modal) {
+ super(model.getObject(), pageRef);
+ this.parentPanel = parentPanel;
+ this.modal = modal;
+ }
+
+ @Override
+ protected WizardModel buildModelSteps(final UserTO modelObject, final WizardModel wizardModel) {
+ this.model = new MergeLinkedAccountsWizardModel(modelObject);
+ wizardModel.add(new MergeLinkedAccountsSearchPanel(model, getPageReference()));
+ wizardModel.add(new MergeLinkedAccountsResourcesPanel(model, getPageReference()));
+ wizardModel.add(new MergeLinkedAccountsReviewPanel(model, getPageReference()));
+ return wizardModel;
+ }
+
+ @Override
+ public void onEvent(final IEvent<?> event) {
+ if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) {
+ ((AjaxWizard.NewItemCancelEvent<?>) event.getPayload()).getTarget().ifPresent(modal::close);
+ }
+ if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
+ Optional<AjaxRequestTarget> targetResult = ((AjaxWizard.NewItemFinishEvent<?>)
+ event.getPayload()).getTarget();
+ try {
+ mergeAccounts();
+ this.parentPanel.info(this.parentPanel.getString(Constants.OPERATION_SUCCEEDED));
+ targetResult.ifPresent(target -> {
+ ((BasePage) this.parentPanel.getPage()).getNotificationPanel().refresh(target);
+ parentPanel.updateResultTable(target);
+ modal.close(target);
+ });
+ } catch (Exception e) {
+ this.parentPanel.error(this.parentPanel.getString(Constants.ERROR) + ": " + e.getMessage());
+ targetResult.ifPresent(target -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target));
+ }
+ }
+ }
+
+ private void mergeAccounts() throws Exception {
+ final UserTO mergingUserTO = this.model.getMergingUser();
+ final ResourceRestClient resourceRestClient = new ResourceRestClient();
+
+ UserPatch userPatch = new UserPatch();
+ userPatch.setKey(this.model.getBaseUser().getUsername());
+
+ // Move linked accounts into the target/base user as linked accounts
+ mergingUserTO.getLinkedAccounts().forEach(acct -> {
+ LinkedAccountTO linkedAccount =
+ new LinkedAccountTO.Builder(acct.getResource(), acct.getConnObjectKeyValue())
+ .password(acct.getPassword())
+ .suspended(acct.isSuspended())
+ .username(acct.getUsername())
+ .build();
+ linkedAccount.getPlainAttrs().addAll(acct.getPlainAttrs());
+ linkedAccount.getPrivileges().addAll(acct.getPrivileges());
+ LinkedAccountPatch patch = new LinkedAccountPatch.Builder()
+ .linkedAccountTO(linkedAccount)
+ .operation(PatchOperation.ADD_REPLACE)
+ .build();
+ userPatch.getLinkedAccounts().add(patch);
+ });
+
+ // Move merging user's resources into the target/base user as a linked account
+ mergingUserTO.getResources().forEach(resource -> {
+ String connObjectKeyValue = resourceRestClient.getConnObjectKeyValue(resource,
+ mergingUserTO.getType(), mergingUserTO.getKey());
+ LinkedAccountTO linkedAccount =
+ new LinkedAccountTO.Builder(resource, connObjectKeyValue)
+ .build();
+ linkedAccount.getPlainAttrs().addAll(mergingUserTO.getPlainAttrs());
+ linkedAccount.getPrivileges().addAll(mergingUserTO.getPrivileges());
+ LinkedAccountPatch patch = new LinkedAccountPatch.Builder()
+ .linkedAccountTO(linkedAccount)
+ .operation(PatchOperation.ADD_REPLACE)
+ .build();
+ userPatch.getLinkedAccounts().add(patch);
+ });
+
+ // Move merging user into target/base user as a linked account
+ String connObjectKeyValue = resourceRestClient.getConnObjectKeyValue(
+ this.model.getResource().getKey(),
+ mergingUserTO.getType(), mergingUserTO.getKey());
+ LinkedAccountTO linkedAccount =
+ new LinkedAccountTO.Builder(this.model.getResource().getKey(), connObjectKeyValue)
+ .password(mergingUserTO.getPassword())
+ .suspended(mergingUserTO.isSuspended())
+ .username(mergingUserTO.getUsername())
+ .build();
+ linkedAccount.getPlainAttrs().addAll(mergingUserTO.getPlainAttrs());
+ linkedAccount.getPrivileges().addAll(mergingUserTO.getPrivileges());
+ LinkedAccountPatch patch = new LinkedAccountPatch.Builder().linkedAccountTO(linkedAccount)
+ .operation(PatchOperation.ADD_REPLACE)
+ .build();
+ userPatch.getLinkedAccounts().add(patch);
+
+ BatchRequest batchRequest = SyncopeConsoleSession.get().batch();
+
+ // Delete merging user
+ BatchRequestItem deleteRequest = new BatchRequestItem();
+ deleteRequest.setMethod(HttpMethod.DELETE);
+ deleteRequest.setRequestURI("/users/" + mergingUserTO.getKey());
+ deleteRequest.getHeaders().put(HttpHeaders.CONTENT_TYPE, Collections.singletonList(MediaType.APPLICATION_JSON));
+ batchRequest.getItems().add(deleteRequest);
+
+ // Update user with linked accounts
+ String updateUserPayload = MAPPER.writeValueAsString(userPatch);
+ BatchRequestItem updateUser = new BatchRequestItem();
+ updateUser.setMethod(HttpMethod.PATCH);
+ updateUser.setRequestURI("/users/" + this.model.getBaseUser().getUsername());
+ updateUser.setHeaders(new HashMap<>());
+ updateUser.getHeaders().put(RESTHeaders.PREFER,
+ Collections.singletonList(Preference.RETURN_NO_CONTENT.toString()));
+ updateUser.getHeaders().put(HttpHeaders.ACCEPT,
+ Collections.singletonList(MediaType.APPLICATION_JSON));
+ updateUser.getHeaders().put(HttpHeaders.CONTENT_TYPE,
+ Collections.singletonList(MediaType.APPLICATION_JSON));
+ updateUser.getHeaders().put(HttpHeaders.CONTENT_LENGTH,
+ Collections.singletonList(updateUserPayload.length()));
+ updateUser.setContent(updateUserPayload);
+ batchRequest.getItems().add(updateUser);
+
+ Map<String, String> batchResponse = new UserRestClient().batch(batchRequest);
+ batchResponse.forEach((key, value) -> {
+ if (!value.equalsIgnoreCase("success")) {
+ throw new IllegalArgumentException("Unable to report a success operation status for " + key);
+ }
+ });
+ }
+
+}
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardModel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardModel.java
new file mode 100644
index 0000000..46c80cc
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardModel.java
@@ -0,0 +1,58 @@
+/*
+ * 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.any;
+
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.to.UserTO;
+
+import java.io.Serializable;
+
+public class MergeLinkedAccountsWizardModel implements Serializable {
+ private static final long serialVersionUID = -2420343164344634869L;
+
+ private final UserTO baseUser;
+
+ private UserTO mergingUser;
+
+ private ResourceTO resource;
+
+ public MergeLinkedAccountsWizardModel(final UserTO baseUser) {
+ this.baseUser = baseUser;
+ }
+
+ public ResourceTO getResource() {
+ return resource;
+ }
+
+ public UserTO getBaseUser() {
+ return baseUser;
+ }
+
+ public UserTO getMergingUser() {
+ return mergingUser;
+ }
+
+ public void setMergingUser(final UserTO mergingUser) {
+ this.mergingUser = mergingUser;
+ }
+
+ public void setResource(final ResourceTO resource) {
+ this.resource = resource;
+ }
+}
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
new file mode 100644
index 0000000..c8f3ec9
--- /dev/null
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.html
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<wicket:panel>
+ <div wicket:id="resources">
+ </div>
+</wicket:panel>
+</html>
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.properties
index 0611fa8..ecc7e94 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel.properties
@@ -14,7 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+resource=Resource
+mergeLinkedAccounts.searchResource.title=Select a resource to use for ${username}, merged as a linked account.
+mergeLinkedAccounts.searchResource.summary=The selected resource must have USER provisioning rules defined.
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_fr_CA.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_fr_CA.properties
index 0611fa8..ecc7e94 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_fr_CA.properties
@@ -14,7 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+resource=Resource
+mergeLinkedAccounts.searchResource.title=Select a resource to use for ${username}, merged as a linked account.
+mergeLinkedAccounts.searchResource.summary=The selected resource must have USER provisioning rules defined.
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_it.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_it.properties
index 0611fa8..ecc7e94 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_it.properties
@@ -14,7 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+resource=Resource
+mergeLinkedAccounts.searchResource.title=Select a resource to use for ${username}, merged as a linked account.
+mergeLinkedAccounts.searchResource.summary=The selected resource must have USER provisioning rules defined.
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ja.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ja.properties
index 0611fa8..ecc7e94 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ja.properties
@@ -14,7 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+resource=Resource
+mergeLinkedAccounts.searchResource.title=Select a resource to use for ${username}, merged as a linked account.
+mergeLinkedAccounts.searchResource.summary=The selected resource must have USER provisioning rules defined.
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_pt_BR.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_pt_BR.properties
index 0611fa8..ecc7e94 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_pt_BR.properties
@@ -14,7 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+resource=Resource
+mergeLinkedAccounts.searchResource.title=Select a resource to use for ${username}, merged as a linked account.
+mergeLinkedAccounts.searchResource.summary=The selected resource must have USER provisioning rules defined.
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ru.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ru.properties
index 0611fa8..ecc7e94 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsResourcesPanel_ru.properties
@@ -14,7 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+resource=Resource
+mergeLinkedAccounts.searchResource.title=Select a resource to use for ${username}, merged as a linked account.
+mergeLinkedAccounts.searchResource.summary=The selected resource must have USER provisioning rules defined.
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.html
new file mode 100644
index 0000000..7927485
--- /dev/null
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.html
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<wicket:panel>
+ <div wicket:id="linkedAccounts">
+ </div>
+</wicket:panel>
+</html>
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.properties
index 0611fa8..3fd6639 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel.properties
@@ -14,7 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.reviewAccounts.title=Preview finalized linked accounts
+connObjectKeyValue=Connector Key
+resource=Resource
+key=Key
+username=Username
+suspended=Suspended
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_fr_CA.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_fr_CA.properties
index 0611fa8..c2d89fa 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_fr_CA.properties
@@ -14,7 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.reviewAccounts.title=Preview finalized linked accounts
+connObjectKeyValue=Connector
+resource=Resource
+key=Key
+username=Username
+suspended=Suspended
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_it.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_it.properties
index 0611fa8..c2d89fa 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_it.properties
@@ -14,7 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.reviewAccounts.title=Preview finalized linked accounts
+connObjectKeyValue=Connector
+resource=Resource
+key=Key
+username=Username
+suspended=Suspended
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ja.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ja.properties
index 0611fa8..c2d89fa 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ja.properties
@@ -14,7 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.reviewAccounts.title=Preview finalized linked accounts
+connObjectKeyValue=Connector
+resource=Resource
+key=Key
+username=Username
+suspended=Suspended
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_pt_BR.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_pt_BR.properties
index 0611fa8..c2d89fa 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_pt_BR.properties
@@ -14,7 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.reviewAccounts.title=Preview finalized linked accounts
+connObjectKeyValue=Connector
+resource=Resource
+key=Key
+username=Username
+suspended=Suspended
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ru.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ru.properties
index 0611fa8..c2d89fa 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsReviewPanel_ru.properties
@@ -14,7 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.reviewAccounts.title=Preview finalized linked accounts
+connObjectKeyValue=Connector
+resource=Resource
+key=Key
+username=Username
+suspended=Suspended
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.html
new file mode 100644
index 0000000..82edc13
--- /dev/null
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.html
@@ -0,0 +1,31 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<wicket:panel>
+ <div wicket:id="ownerContainer">
+ <span wicket:id="search">[SEARCH]</span>
+ </div>
+ <wicket:fragment wicket:id="userSearchFragment">
+ <span wicket:id="usersearch">[USER SEARCH]</span>
+ <div class="searchResult">
+ <span wicket:id="searchResult">[USER SEARCH RESULT]</span>
+ </div>
+ </wicket:fragment>
+</wicket:panel>
+</html>
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties
index 0611fa8..5d25d9c 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel.properties
@@ -14,7 +14,4 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.searchUser=Select user to merge
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_fr_CA.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_fr_CA.properties
index 0611fa8..5d25d9c 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_fr_CA.properties
@@ -14,7 +14,4 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.searchUser=Select user to merge
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_it.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_it.properties
index 0611fa8..9ed65ca 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_it.properties
@@ -14,7 +14,4 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.searchUser=Selezione dell'utente
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ja.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ja.properties
index 0611fa8..5d25d9c 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ja.properties
@@ -14,7 +14,4 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.searchUser=Select user to merge
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_pt_BR.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_pt_BR.properties
index 0611fa8..5d25d9c 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_pt_BR.properties
@@ -14,7 +14,4 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.searchUser=Select user to merge
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ru.properties
similarity index 76%
copy from client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
copy to client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ru.properties
index 0611fa8..5d25d9c 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/MergeLinkedAccountsSearchPanel_ru.properties
@@ -14,7 +14,4 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+mergeLinkedAccounts.searchUser=Select user to merge
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel.properties
index 1d4db0d..9cbfc62 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel.properties
@@ -18,4 +18,5 @@ any.edit=Edit ${anyTO.type} ${anyTO.username}
any.propagation.tasks=Propagation tasks for ${type} ${username}
any.notification.tasks=Notification tasks for ${type} ${username}
linkedAccounts.title=Manage user accounts
+mergeLinkedAccounts.title=Merge user accounts with ${username}
auditHistory.title=${anyTO.type} ${anyTO.username} history
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
index 0611fa8..60a2dfe 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_fr_CA.properties
@@ -15,6 +15,7 @@
# specific language governing permissions and limitations
# under the License.
any.edit=Modifier ${anyTO.type} ${anyTO.username}
-any.propagation.tasks=T�ches de propagation pour ${type} ${username}
-any.notification.tasks=T�ches de notification pour ${type} ${username}
-linkedAccounts.title=G�rer les comptes utilisateur
+any.propagation.tasks=Tâches de propagation pour ${type} ${username}
+any.notification.tasks=Tâches de notification pour ${type} ${username}
+linkedAccounts.title=Gérer les comptes utilisateur
+mergeLinkedAccounts.title=Merge user accounts with ${username}
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_it.properties
index ef6c91a..aedc995 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_it.properties
@@ -18,4 +18,5 @@ any.edit=Modifica ${anyTO.type} ${anyTO.username}
any.propagation.tasks=Task di propagazione per ${type} ${username}
any.notification.tasks=Task di notifica per ${type} ${username}
linkedAccounts.title=Gestisci account utente
+mergeLinkedAccounts.title=Combinare account utente con ${username}
auditHistory.title=${anyTO.type} ${anyTO.username} history
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ja.properties
index a2193e2..a4745d4 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ja.properties
@@ -19,3 +19,4 @@ any.propagation.tasks=${type} ${username} \u306e\u4f1d\u64ad\u30bf\u30b9\u30af
any.notification.tasks=${type} ${username} \u306e\u901a\u77e5\u30bf\u30b9\u30af
linkedAccounts.title=\u30e6\u30fc\u30b6\u30fc\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u7ba1\u7406\u3059\u308b
auditHistory.title=${anyTO.type} ${anyTO.username} history
+mergeLinkedAccounts.title=Merge user accounts with ${username}
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_pt_BR.properties
index 84d5d86..230d7b1 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_pt_BR.properties
@@ -19,3 +19,4 @@ any.propagation.tasks=Propagation tasks for ${type} ${username}
any.notification.tasks=Notification tasks for ${type} ${username}
linkedAccounts.title=Gerenciar contas de usu\u00e1rio
auditHistory.title=${anyTO.type} ${anyTO.username} history
+mergeLinkedAccounts.title=Merge user accounts with ${username}
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ru.properties
index 378c49a..b3b1b1d 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/UserDirectoryPanel_ru.properties
@@ -20,3 +20,4 @@ any.propagation.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u0432\u044b\u043f\u0
any.notification.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u0434\u043b\u044f ${type} ${username}
linkedAccounts.title=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u043c\u0438 \u0437\u0430\u043f\u0438\u0441\u044f\u043c\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439
auditHistory.title=${anyTO.type} ${anyTO.username} history
+mergeLinkedAccounts.title=Merge user accounts with ${username}
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
index af95e02..9806974 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
@@ -281,3 +281,7 @@ external_editor.title=external editor icon
external_editor.class=fa fa-picture-o
external_editor.alt=external editor icon
+merge_accounts.class=fa fa-compress
+merge_accounts.title=merge accounts
+merge_accounts.alt=merge accounts icon
+
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
index 34be920..b5f67c6 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
@@ -35,6 +35,8 @@ public final class RESTHeaders {
public static final String RESOURCE_KEY = "X-Syncope-Key";
+ public static final String CONNOBJECT_KEY = "X-Syncope-ConnObject-Key";
+
/**
* Asks for asynchronous propagation towards external resources with null priority.
*/
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ResourceService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ResourceService.java
index cca2ac0..54e9df9 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ResourceService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ResourceService.java
@@ -33,6 +33,7 @@ import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
@@ -58,6 +59,30 @@ import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery;
public interface ResourceService extends JAXRSService {
/**
+ * Returns the calculated connObjectKey value for the given type and key.
+ *
+ * @param key name of resource to read connector object from
+ * @param anyTypeKey any object type
+ * @param anyKey user, group or any object key
+ * @return connObjectKey value for the external resource, for the given type and key
+ */
+ @ApiResponses({
+ @ApiResponse(responseCode = "201",
+ description = "connObjectKey value for the external resource, for the given type and key", headers = {
+ @Header(name = RESTHeaders.CONNOBJECT_KEY, schema =
+ @Schema(type = "string"),
+ description = "connObjectKey value for the external resource, for the given type and key")
+ }),
+ @ApiResponse(responseCode = "404",
+ description = "user, group or any object not found, or connObjectKey cannot be calculated") })
+ @OPTIONS
+ @Path("{key}/{anyTypeKey}/{anyKey}")
+ Response getConnObjectKeyValue(
+ @NotNull @PathParam("key") String key,
+ @NotNull @PathParam("anyTypeKey") String anyTypeKey,
+ @NotNull @PathParam("anyKey") String anyKey);
+
+ /**
* Returns connector object from the external resource, for the given type and key.
*
* @param key name of resource to read connector object from
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
index b888641..ba0946c 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
@@ -53,6 +53,7 @@ import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.ConnInstance;
import org.apache.syncope.core.persistence.api.entity.VirSchema;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.MappingManager;
import org.apache.syncope.core.provisioning.api.VirAttrHandler;
import org.apache.syncope.core.provisioning.api.data.ConnInstanceDataBinder;
import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
@@ -99,6 +100,9 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
private OutboundMatcher outboundMatcher;
@Autowired
+ private MappingManager mappingManager;
+
+ @Autowired
private ConnectorFactory connFactory;
@Autowired
@@ -281,6 +285,26 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
"Provision on resource '" + resourceKey + "' for type '" + anyTypeKey + "'"));
}
+ @Transactional(readOnly = true)
+ public String getConnObjectKeyValue(
+ final String key,
+ final String anyTypeKey,
+ final String anyKey) {
+
+ Provision provision = getProvision(key, anyTypeKey);
+
+ // 1. find any
+ Any<?> any = anyUtilsFactory.getInstance(provision.getAnyType().getKind()).dao().authFind(anyKey);
+ if (any == null) {
+ throw new NotFoundException(provision.getAnyType() + " " + anyKey);
+ }
+
+ // 2.get ConnObjectKey value
+ return mappingManager.getConnObjectKeyValue(any, provision).
+ orElseThrow(() -> new NotFoundException(
+ "No ConnObjectKey value found for " + anyTypeKey + " " + anyKey + " on resource '" + key + "'"));
+ }
+
@PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')")
@Transactional(readOnly = true)
public ConnObjectTO readConnObjectByAnyKey(
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
index beb377a..5197c82 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
@@ -93,6 +93,12 @@ public class ResourceServiceImpl extends AbstractServiceImpl implements Resource
}
@Override
+ public Response getConnObjectKeyValue(final String key, final String anyTypeKey, final String anyKey) {
+ String connObjectKeyValue = logic.getConnObjectKeyValue(key, anyTypeKey, anyKey);
+ return Response.noContent().header(RESTHeaders.CONNOBJECT_KEY, connObjectKeyValue).build();
+ }
+
+ @Override
public ConnObjectTO readConnObject(final String key, final String anyTypeKey, final String value) {
return SyncopeConstants.UUID_PATTERN.matcher(value).matches()
? logic.readConnObjectByAnyKey(key, anyTypeKey, value)
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LinkedAccountsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LinkedAccountsITCase.java
new file mode 100644
index 0000000..c0ca592
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LinkedAccountsITCase.java
@@ -0,0 +1,197 @@
+/*
+ * 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.console;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.pages.Login;
+import org.apache.syncope.client.console.panels.search.SearchClause;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.LinkedAccountTO;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.rest.api.service.UserService;
+import org.apache.syncope.fit.core.UserITCase;
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxEventBehavior;
+import org.apache.wicket.extensions.wizard.NextButton;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.repeater.OddEvenItem;
+import org.apache.wicket.util.tester.FormTester;
+import org.apache.wicket.util.tester.WicketTesterHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class LinkedAccountsITCase extends AbstractConsoleITCase {
+ private static final String TAB_PANEL = "body:content:body:container:content:tabbedPanel:panel:searchResult:";
+
+ private static final String RESULT_DATA_TABLE = "searchResult:container:content:searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable:";
+
+ private static final String RESOURCES_DATA_TABLE = "view:resources:container:content:searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable:";
+
+ private static final String SELECT_USER_ACTION = "searchResult:outerObjectsRepeater:1:outer:container:content:"
+ + "togglePanelContainer:container:actions:actions:actionRepeater:0:action:action";
+
+ private static final String SELECT_RESOURCE_ACTION = "view:resources:outerObjectsRepeater:1:outer:container:content:"
+ + "togglePanelContainer:container:actions:actions:actionRepeater:0:action:action";
+
+ private static final String PARENT_FORM = "outerObjectsRepeater:0:outer:form:";
+
+ private static final String FORM = PARENT_FORM + "content:form:";
+
+ private static final String SEARCH_PANEL = FORM + "view:ownerContainer:search:";
+
+ private static final String USER_SEARCH_PANEL = SEARCH_PANEL + "usersearch:";
+
+ private static final String USER_SEARCH_FORM = TAB_PANEL + USER_SEARCH_PANEL + "searchFormContainer:search:multiValueContainer:innerForm:";
+
+ private static final String CONTAINER = TAB_PANEL + "container:content:";
+
+ private static UserTO user;
+
+ @BeforeEach
+ public void login() {
+ doLogin(ADMIN_UNAME, ADMIN_PWD);
+
+ // create user with linked account
+ String email = "linkedAccount" + RandomStringUtils.randomNumeric(4) + "@syncope.apache.org";
+ user = UserITCase.getSampleTO(email);
+ String connObjectKeyValue = "uid=" + user.getUsername() + ",ou=People,o=isp";
+
+ LinkedAccountTO account = new LinkedAccountTO.Builder("resource-ldap", connObjectKeyValue).build();
+ account.getPlainAttrs().add(new AttrTO.Builder().schema("surname").value("LINKED_SURNAME").build());
+ user.getLinkedAccounts().add(account);
+
+ UserService userService = SyncopeConsoleSession.get().getService(UserService.class);
+ Response response = userService.create(user, true);
+ user = response.readEntity(new GenericType<ProvisioningResult<UserTO>>() {
+ }).getEntity();
+ assertNotNull(user.getKey());
+ assertEquals(account.getConnObjectKeyValue(), user.getLinkedAccounts().get(0).getConnObjectKeyValue());
+ }
+
+ @AfterEach
+ public void cleanUp() {
+ try {
+ SyncopeConsoleSession.get().getService(UserService.class).delete(user.getKey());
+ } catch (final SyncopeClientException e) {
+ if (e.getType() != ClientExceptionType.NotFound) {
+ throw e;
+ }
+ }
+ }
+
+ @Test
+ public void createLinkedAccountAndMergeWithUser() {
+
+ // Locate and select first user
+ TESTER.clickLink("body:realmsLI:realms");
+ TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
+
+ Component verdiUserComponent = findComponentByProp("username", CONTAINER
+ + ":searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable", "verdi");
+ assertNotNull(verdiUserComponent);
+ TESTER.executeAjaxEvent(verdiUserComponent.getPageRelativePath(), Constants.ON_CLICK);
+
+ // Click action menu to bring up merge window
+ TESTER.clickLink(TAB_PANEL + "outerObjectsRepeater:1:outer:container:content:togglePanelContainer:container:"
+ + "actions:actions:actionRepeater:8:action:action");
+
+
+ // Search for user
+ TESTER.executeAjaxEvent(USER_SEARCH_FORM + "content:panelPlus:add", Constants.ON_CLICK);
+ FormTester formTester = TESTER.newFormTester(USER_SEARCH_FORM);
+
+ DropDownChoice type = (DropDownChoice) TESTER.getComponentFromLastRenderedPage(USER_SEARCH_FORM + "content:view:0:panel:container:type:dropDownChoiceField");
+ TESTER.executeAjaxEvent(USER_SEARCH_FORM + "content:view:0:panel:container:type:dropDownChoiceField", Constants.ON_CHANGE);
+ type.setModelValue(new String[]{"ATTRIBUTE"});
+ type.setDefaultModelObject(SearchClause.Type.ATTRIBUTE);
+
+ TextField property = (TextField) TESTER.getComponentFromLastRenderedPage(USER_SEARCH_FORM + "content:view:0:panel:container:property:textField");
+ assertNotNull(property);
+ property.setModelValue(new String[]{"username"});
+
+ TextField value = (TextField) TESTER.getComponentFromLastRenderedPage(USER_SEARCH_FORM + "content:view:0:panel:container:value:textField");
+ assertNotNull(value);
+ value.setModelValue(new String[]{user.getUsername()});
+
+ TESTER.cleanupFeedbackMessages();
+ formTester.submit("content:view:0:panel:container:operatorContainer:operator:search");
+ TESTER.assertNoErrorMessage();
+
+ // Locate result in data table
+ Component comp = findComponentByProp("username", TAB_PANEL + SEARCH_PANEL + RESULT_DATA_TABLE, user.getUsername());
+ TESTER.executeAjaxEvent(comp.getPageRelativePath(), Constants.ON_CLICK);
+
+ UserTO userTO = (UserTO) ((OddEvenItem) TESTER.getComponentFromLastRenderedPage(TAB_PANEL + SEARCH_PANEL
+ + RESULT_DATA_TABLE + "body:rows:1")).getModel().getObject();
+ assertNotNull(userTO);
+
+ // Select user
+ TESTER.clickLink(TAB_PANEL + SEARCH_PANEL + SELECT_USER_ACTION);
+
+ // move onto the next panel
+ TESTER.getComponentFromLastRenderedPage(TAB_PANEL + FORM + "view").setEnabled(false);
+ formTester = TESTER.newFormTester(TAB_PANEL + FORM);
+ formTester.submit("buttons:next");
+
+ // Select a resource
+ comp = findComponentByProp ("key", TAB_PANEL + FORM + RESOURCES_DATA_TABLE + "body:rows", "resource-ldap");
+ assertNotNull(comp);
+ TESTER.executeAjaxEvent(comp.getPageRelativePath(), Constants.ON_CLICK);
+ TESTER.clickLink(TAB_PANEL + FORM + SELECT_RESOURCE_ACTION);
+
+ // move onto the next panel
+ TESTER.getComponentFromLastRenderedPage(TAB_PANEL + FORM + "view").setEnabled(false);
+ formTester = TESTER.newFormTester(TAB_PANEL + FORM);
+ formTester.submit("buttons:next");
+
+ // Finish merge
+ TESTER.getComponentFromLastRenderedPage(TAB_PANEL + FORM + "view").setEnabled(false);
+ formTester = TESTER.newFormTester(TAB_PANEL + FORM);
+ formTester.submit("buttons:finish");
+
+ UserService userService = SyncopeConsoleSession.get().getService(UserService.class);
+
+ // User must have been deleted after the merge
+ try {
+ userService.read(user.getKey());
+ fail("User must have been deleted; expect an exception here");
+ } catch (final SyncopeClientException e) {
+ if (e.getType() != ClientExceptionType.NotFound) {
+ fail(e.getMessage(), e);
+ }
+ }
+ // User must include merged accounts now
+ UserTO verdi = userService.read(UserTO.class.cast(verdiUserComponent.getDefaultModelObject()).getKey());
+ assertFalse(verdi.getLinkedAccounts().isEmpty());
+ }
+}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
index bd2d559..c4241ed 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
@@ -727,7 +727,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
TESTER.executeAjaxEvent(component.getPageRelativePath(), Constants.ON_CLICK);
TESTER.clickLink("body:content:body:container:content:tabbedPanel:panel:searchResult:outerObjectsRepeater:"
- + "1:outer:container:content:togglePanelContainer:container:actions:actions:actionRepeater:9:"
+ + "1:outer:container:content:togglePanelContainer:container:actions:actions:actionRepeater:10:"
+ "action:action");
TESTER.assertComponent(
@@ -784,7 +784,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
TESTER.executeAjaxEvent(TESTER.getComponentFromLastRenderedPage(
"body:content:body:container:content:tabbedPanel:panel:searchResult:outerObjectsRepeater:"
- + "1:outer:container:content:togglePanelContainer:container:actions:actions:actionRepeater:10:"
+ + "1:outer:container:content:togglePanelContainer:container:actions:actions:actionRepeater:11:"
+ "action:action"), Constants.ON_CLICK);
assertSuccessMessage();
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java
index 030b7cb..1507310 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java
@@ -91,7 +91,7 @@ public class UsersITCase extends AbstractConsoleITCase {
TESTER.executeAjaxEvent(component.getPageRelativePath(), Constants.ON_CLICK);
TESTER.clickLink(TAB_PANEL + "outerObjectsRepeater:1:outer:container:content:togglePanelContainer:container:"
- + "actions:actions:actionRepeater:9:action:action");
+ + "actions:actions:actionRepeater:10:action:action");
TESTER.assertComponent(TAB_PANEL + "outerObjectsRepeater:0:outer:form:content:form:view:username:textField",
TextField.class);
@@ -551,7 +551,7 @@ public class UsersITCase extends AbstractConsoleITCase {
TESTER.executeAjaxEvent(component.getPageRelativePath(), Constants.ON_CLICK);
TESTER.assertComponent(TAB_PANEL
+ "outerObjectsRepeater:1:outer:container:content:togglePanelContainer:container:"
- + "actions:actions:actionRepeater:10:action:action", IndicatingOnConfirmAjaxLink.class);
+ + "actions:actions:actionRepeater:11:action:action", IndicatingOnConfirmAjaxLink.class);
}
@Test