View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.client.console.wizards.any;
20  
21  import com.fasterxml.jackson.databind.json.JsonMapper;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Optional;
26  import javax.ws.rs.HttpMethod;
27  import javax.ws.rs.core.HttpHeaders;
28  import javax.ws.rs.core.MediaType;
29  import org.apache.syncope.client.console.SyncopeConsoleSession;
30  import org.apache.syncope.client.console.pages.BasePage;
31  import org.apache.syncope.client.console.panels.UserDirectoryPanel;
32  import org.apache.syncope.client.console.rest.ResourceRestClient;
33  import org.apache.syncope.client.console.rest.UserRestClient;
34  import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
35  import org.apache.syncope.client.console.wizards.BaseAjaxWizardBuilder;
36  import org.apache.syncope.client.lib.batch.BatchRequest;
37  import org.apache.syncope.client.ui.commons.Constants;
38  import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
39  import org.apache.syncope.common.lib.request.LinkedAccountUR;
40  import org.apache.syncope.common.lib.request.UserUR;
41  import org.apache.syncope.common.lib.to.LinkedAccountTO;
42  import org.apache.syncope.common.lib.to.UserTO;
43  import org.apache.syncope.common.lib.types.PatchOperation;
44  import org.apache.syncope.common.rest.api.Preference;
45  import org.apache.syncope.common.rest.api.RESTHeaders;
46  import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
47  import org.apache.wicket.PageReference;
48  import org.apache.wicket.ajax.AjaxRequestTarget;
49  import org.apache.wicket.event.IEvent;
50  import org.apache.wicket.event.IEventSink;
51  import org.apache.wicket.extensions.wizard.WizardModel;
52  import org.apache.wicket.model.IModel;
53  
54  public class MergeLinkedAccountsWizardBuilder extends BaseAjaxWizardBuilder<UserTO> implements IEventSink {
55  
56      private static final long serialVersionUID = -9142332740863374891L;
57  
58      protected static final JsonMapper MAPPER = JsonMapper.builder().findAndAddModules().build();
59  
60      protected final UserDirectoryPanel parentPanel;
61  
62      protected final BaseModal<?> modal;
63  
64      protected MergeLinkedAccountsWizardModel model;
65  
66      protected final ResourceRestClient resourceRestClient;
67  
68      protected final UserRestClient userRestClient;
69  
70      public MergeLinkedAccountsWizardBuilder(
71              final IModel<UserTO> model,
72              final UserDirectoryPanel parentPanel,
73              final BaseModal<?> modal,
74              final ResourceRestClient resourceRestClient,
75              final UserRestClient userRestClient,
76              final PageReference pageRef) {
77  
78          super(model.getObject(), pageRef);
79  
80          this.parentPanel = parentPanel;
81          this.modal = modal;
82          this.resourceRestClient = resourceRestClient;
83          this.userRestClient = userRestClient;
84      }
85  
86      @Override
87      protected WizardModel buildModelSteps(final UserTO modelObject, final WizardModel wizardModel) {
88          model = new MergeLinkedAccountsWizardModel(modelObject);
89          wizardModel.add(new MergeLinkedAccountsSearchPanel(model, getPageReference()));
90          wizardModel.add(new MergeLinkedAccountsResourcesPanel(model, getPageReference()));
91          wizardModel.add(new MergeLinkedAccountsReviewPanel(model, getPageReference()));
92          return wizardModel;
93      }
94  
95      @Override
96      public void onEvent(final IEvent<?> event) {
97          if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) {
98              ((AjaxWizard.NewItemCancelEvent<?>) event.getPayload()).getTarget().ifPresent(modal::close);
99          }
100         if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
101             Optional<AjaxRequestTarget> target =
102                     ((AjaxWizard.NewItemFinishEvent<?>) event.getPayload()).getTarget();
103             try {
104                 mergeAccounts();
105 
106                 parentPanel.info(parentPanel.getString(Constants.OPERATION_SUCCEEDED));
107                 target.ifPresent(t -> {
108                     ((BasePage) parentPanel.getPage()).getNotificationPanel().refresh(t);
109                     parentPanel.updateResultTable(t);
110                     modal.close(t);
111                 });
112             } catch (Exception e) {
113                 parentPanel.error(parentPanel.getString(Constants.ERROR) + ": " + e.getMessage());
114                 target.ifPresent(t -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(t));
115             }
116         }
117     }
118 
119     private void mergeAccounts() throws Exception {
120         UserTO mergingUserTO = model.getMergingUser();
121 
122         UserUR userUR = new UserUR();
123         userUR.setKey(model.getBaseUser().getUsername());
124 
125         // Move linked accounts into the target/base user as linked accounts
126         mergingUserTO.getLinkedAccounts().forEach(acct -> {
127             LinkedAccountTO linkedAccount =
128                     new LinkedAccountTO.Builder(acct.getResource(), acct.getConnObjectKeyValue()).
129                             password(acct.getPassword()).
130                             suspended(acct.isSuspended()).
131                             username(acct.getUsername()).
132                             build();
133             linkedAccount.getPlainAttrs().addAll(acct.getPlainAttrs());
134             linkedAccount.getPrivileges().addAll(acct.getPrivileges());
135             LinkedAccountUR patch = new LinkedAccountUR.Builder().
136                     linkedAccountTO(linkedAccount).
137                     operation(PatchOperation.ADD_REPLACE).
138                     build();
139             userUR.getLinkedAccounts().add(patch);
140         });
141 
142         // Move merging user's resources into the target/base user as a linked account
143         mergingUserTO.getResources().forEach(resource -> {
144             String connObjectKeyValue = resourceRestClient.getConnObjectKeyValue(
145                     resource, mergingUserTO.getType(), mergingUserTO.getKey());
146             LinkedAccountTO linkedAccount = new LinkedAccountTO.Builder(resource, connObjectKeyValue).build();
147             linkedAccount.getPlainAttrs().addAll(mergingUserTO.getPlainAttrs());
148             linkedAccount.getPrivileges().addAll(mergingUserTO.getPrivileges());
149             LinkedAccountUR patch = new LinkedAccountUR.Builder().
150                     linkedAccountTO(linkedAccount).
151                     operation(PatchOperation.ADD_REPLACE).
152                     build();
153             userUR.getLinkedAccounts().add(patch);
154         });
155 
156         // Move merging user into target/base user as a linked account
157         String connObjectKeyValue = resourceRestClient.getConnObjectKeyValue(
158                 model.getResource().getKey(),
159                 mergingUserTO.getType(), mergingUserTO.getKey());
160         LinkedAccountTO linkedAccount = new LinkedAccountTO.Builder(model.getResource().getKey(), connObjectKeyValue).
161                 password(mergingUserTO.getPassword()).
162                 suspended(mergingUserTO.isSuspended()).
163                 username(mergingUserTO.getUsername()).
164                 build();
165         linkedAccount.getPlainAttrs().addAll(mergingUserTO.getPlainAttrs());
166         linkedAccount.getPrivileges().addAll(mergingUserTO.getPrivileges());
167         LinkedAccountUR patch = new LinkedAccountUR.Builder().linkedAccountTO(linkedAccount).
168                 operation(PatchOperation.ADD_REPLACE).
169                 build();
170         userUR.getLinkedAccounts().add(patch);
171 
172         BatchRequest batchRequest = SyncopeConsoleSession.get().batch();
173 
174         // Delete merging user
175         BatchRequestItem deleteRequest = new BatchRequestItem();
176         deleteRequest.setMethod(HttpMethod.DELETE);
177         deleteRequest.setRequestURI("/users/" + mergingUserTO.getKey());
178         deleteRequest.getHeaders().put(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_JSON));
179         batchRequest.getItems().add(deleteRequest);
180 
181         // Update user with linked accounts
182         String updateUserPayload = MAPPER.writeValueAsString(userUR);
183         BatchRequestItem updateUser = new BatchRequestItem();
184         updateUser.setMethod(HttpMethod.PATCH);
185         updateUser.setRequestURI("/users/" + model.getBaseUser().getUsername());
186         updateUser.setHeaders(new HashMap<>());
187         updateUser.getHeaders().put(RESTHeaders.PREFER, List.of(Preference.RETURN_NO_CONTENT.toString()));
188         updateUser.getHeaders().put(HttpHeaders.ACCEPT, List.of(MediaType.APPLICATION_JSON));
189         updateUser.getHeaders().put(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_JSON));
190         updateUser.getHeaders().put(HttpHeaders.CONTENT_LENGTH, List.of(updateUserPayload.length()));
191         updateUser.setContent(updateUserPayload);
192         batchRequest.getItems().add(updateUser);
193 
194         Map<String, String> batchResponse = userRestClient.batch(batchRequest);
195         batchResponse.forEach((key, value) -> {
196             if (!value.equalsIgnoreCase("success")) {
197                 throw new IllegalArgumentException("Unable to report a success operation status for " + key);
198             }
199         });
200     }
201 }