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.panels;
20  
21  import java.util.ArrayList;
22  import java.util.Comparator;
23  import java.util.List;
24  import java.util.stream.Collectors;
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.syncope.client.console.SyncopeConsoleSession;
27  import org.apache.syncope.client.console.layout.AnyLayout;
28  import org.apache.syncope.client.console.layout.AnyLayoutUtils;
29  import org.apache.syncope.client.console.layout.IdMUserFormLayoutInfo;
30  import org.apache.syncope.client.console.layout.LinkedAccountForm;
31  import org.apache.syncope.client.console.layout.LinkedAccountFormLayoutInfo;
32  import org.apache.syncope.client.console.pages.BasePage;
33  import org.apache.syncope.client.console.rest.AnyTypeRestClient;
34  import org.apache.syncope.client.console.rest.RoleRestClient;
35  import org.apache.syncope.client.console.rest.UserRestClient;
36  import org.apache.syncope.client.console.status.LinkedAccountStatusPanel;
37  import org.apache.syncope.client.console.status.ReconTaskPanel;
38  import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
39  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
40  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel;
41  import org.apache.syncope.client.console.wizards.WizardMgtPanel;
42  import org.apache.syncope.client.console.wizards.any.LinkedAccountWizardBuilder;
43  import org.apache.syncope.client.ui.commons.ConnIdSpecialName;
44  import org.apache.syncope.client.ui.commons.Constants;
45  import org.apache.syncope.client.ui.commons.panels.ModalPanel;
46  import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
47  import org.apache.syncope.common.lib.SyncopeClientException;
48  import org.apache.syncope.common.lib.request.LinkedAccountUR;
49  import org.apache.syncope.common.lib.request.UserUR;
50  import org.apache.syncope.common.lib.to.AnyTypeTO;
51  import org.apache.syncope.common.lib.to.LinkedAccountTO;
52  import org.apache.syncope.common.lib.to.PullTaskTO;
53  import org.apache.syncope.common.lib.to.PushTaskTO;
54  import org.apache.syncope.common.lib.to.UserTO;
55  import org.apache.syncope.common.lib.types.IdRepoEntitlement;
56  import org.apache.syncope.common.lib.types.PatchOperation;
57  import org.apache.wicket.AttributeModifier;
58  import org.apache.wicket.Component;
59  import org.apache.wicket.PageReference;
60  import org.apache.wicket.ajax.AjaxRequestTarget;
61  import org.apache.wicket.ajax.markup.html.AjaxLink;
62  import org.apache.wicket.event.Broadcast;
63  import org.apache.wicket.markup.html.basic.Label;
64  import org.apache.wicket.markup.html.panel.Panel;
65  import org.apache.wicket.model.IModel;
66  import org.apache.wicket.model.Model;
67  import org.apache.wicket.model.StringResourceModel;
68  import org.apache.wicket.spring.injection.annot.SpringBean;
69  import org.slf4j.Logger;
70  import org.slf4j.LoggerFactory;
71  
72  public class LinkedAccountModalPanel extends Panel implements ModalPanel {
73  
74      private static final long serialVersionUID = -4603032036433309900L;
75  
76      protected static final Logger LOG = LoggerFactory.getLogger(LinkedAccountModalPanel.class);
77  
78      @SpringBean
79      protected AnyTypeRestClient anyTypeRestClient;
80  
81      @SpringBean
82      protected RoleRestClient roleRestClient;
83  
84      @SpringBean
85      protected UserRestClient userRestClient;
86  
87      protected LinkedAccountForm wizard;
88  
89      protected final WizardMgtPanel<LinkedAccountTO> list;
90  
91      private final AjaxLink<LinkedAccountTO> addAjaxLink;
92  
93      protected ActionLinksTogglePanel<LinkedAccountTO> actionTogglePanel;
94  
95      protected final List<LinkedAccountTO> linkedAccountTOs;
96  
97      @SuppressWarnings("unchecked")
98      public LinkedAccountModalPanel(
99              final IModel<UserTO> model,
100             final PageReference pageRef,
101             final boolean recounciliationOnly) {
102 
103         super(BaseModal.CONTENT_ID, model);
104 
105         final MultilevelPanel mlp = new MultilevelPanel("mlpContainer");
106         mlp.setOutputMarkupId(true);
107 
108         setOutputMarkupId(true);
109 
110         actionTogglePanel = new ActionLinksTogglePanel<>("toggle", pageRef);
111         add(actionTogglePanel);
112 
113         AnyLayout anyLayout = AnyLayoutUtils.fetch(
114                 roleRestClient,
115                 anyTypeRestClient.listAnyTypes().stream().map(AnyTypeTO::getKey).collect(Collectors.toList()));
116         LinkedAccountFormLayoutInfo linkedAccountFormLayoutInfo =
117                 anyLayout.getUser() instanceof IdMUserFormLayoutInfo
118                 ? IdMUserFormLayoutInfo.class.cast(anyLayout.getUser()).getLinkedAccountFormLayoutInfo()
119                 : new LinkedAccountFormLayoutInfo();
120 
121         try {
122             wizard = linkedAccountFormLayoutInfo.getFormClass().getConstructor(
123                     IModel.class,
124                     LinkedAccountFormLayoutInfo.class,
125                     UserRestClient.class,
126                     AnyTypeRestClient.class,
127                     PageReference.class).
128                     newInstance(model, linkedAccountFormLayoutInfo, userRestClient, anyTypeRestClient, pageRef);
129         } catch (Exception e) {
130             LOG.error("Error instantiating form layout", e);
131             wizard = new LinkedAccountWizardBuilder(
132                     model, linkedAccountFormLayoutInfo, userRestClient, anyTypeRestClient, pageRef);
133         }
134 
135         ListViewPanel.Builder<LinkedAccountTO> builder = new ListViewPanel.Builder<>(
136                 LinkedAccountTO.class, pageRef) {
137 
138             private static final long serialVersionUID = -5322423525438435153L;
139 
140             @Override
141             protected LinkedAccountTO getActualItem(final LinkedAccountTO item, final List<LinkedAccountTO> list) {
142                 return item == null
143                         ? null
144                         : list.stream().filter(
145                                 in -> ((item.getKey() == null && in.getKey() == null)
146                                 || (in.getKey() != null && in.getKey().equals(item.getKey())))).
147                                 findAny().orElse(null);
148             }
149 
150             @Override
151             protected void customActionCallback(final AjaxRequestTarget target) {
152                 // change modal footer visibility
153                 send(LinkedAccountModalPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target));
154             }
155 
156             @Override
157             protected void customActionOnCancelCallback(final AjaxRequestTarget target) {
158                 // change modal footer visibility
159                 send(LinkedAccountModalPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target));
160             }
161 
162             @Override
163             @SuppressWarnings("unchecked")
164             protected void customActionOnFinishCallback(final AjaxRequestTarget target) {
165                 checkAddButton(model.getObject().getRealm());
166 
167                 linkedAccountTOs.clear();
168                 linkedAccountTOs.addAll(model.getObject().getLinkedAccounts());
169                 sortLinkedAccounts();
170 
171                 ListViewPanel.class.cast(list).refreshList(linkedAccountTOs);
172 
173                 // change modal footer visibility
174                 send(LinkedAccountModalPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target));
175             }
176 
177             @Override
178             protected Component getValueComponent(final String key, final LinkedAccountTO bean) {
179                 if ("suspended".equalsIgnoreCase(key)) {
180                     Label label = new Label("field", StringUtils.EMPTY);
181                     if (bean.isSuspended()) {
182                         label.add(new AttributeModifier("class", "fa fa-check"));
183                         label.add(new AttributeModifier("style", "display: table-cell; text-align: center;"));
184                     }
185                     return label;
186                 }
187                 return super.getValueComponent(key, bean);
188             }
189 
190             @Override
191             protected ActionLinksTogglePanel<LinkedAccountTO> getTogglePanel() {
192                 return actionTogglePanel;
193             }
194         };
195 
196         linkedAccountTOs = new ArrayList<>(model.getObject().getLinkedAccounts());
197         sortLinkedAccounts();
198 
199         builder.setItems(linkedAccountTOs);
200         builder.includes("connObjectKeyValue", "resource", "suspended");
201         builder.setReuseItem(false);
202         builder.withChecks(ListViewPanel.CheckAvailability.NONE);
203 
204         builder.addAction(new ActionLink<>() {
205 
206             private static final long serialVersionUID = 2555747430358755813L;
207 
208             @Override
209             public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) {
210                 mlp.next(linkedAccountTO.getResource(), new LinkedAccountStatusPanel(
211                         linkedAccountTO.getResource(),
212                         model.getObject().getType(),
213                         linkedAccountTO.getConnObjectKeyValue()),
214                         target);
215                 target.add(mlp);
216 
217                 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
218                 send(LinkedAccountModalPanel.this, Broadcast.BREADTH,
219                         new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target));
220             }
221         }, ActionLink.ActionType.VIEW, IdRepoEntitlement.USER_READ);
222 
223         if (!recounciliationOnly) {
224             builder.addAction(new ActionLink<>() {
225 
226                 private static final long serialVersionUID = 2555747430358755813L;
227 
228                 @Override
229                 public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) {
230                     try {
231                         send(LinkedAccountModalPanel.this, Broadcast.DEPTH,
232                                 new AjaxWizard.NewItemActionEvent<>(linkedAccountTO, 1, target).
233                                         setTitleModel(new StringResourceModel("inner.edit.linkedAccount",
234                                                 LinkedAccountModalPanel.this, Model.of(linkedAccountTO))));
235                     } catch (SyncopeClientException e) {
236                         LOG.error("While attempting to create new linked account", e);
237                         SyncopeConsoleSession.get().onException(e);
238                         ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
239                     }
240 
241                     send(LinkedAccountModalPanel.this, Broadcast.BREADTH,
242                             new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target));
243                 }
244             }, ActionLink.ActionType.EDIT, IdRepoEntitlement.USER_UPDATE);
245 
246             builder.addAction(new ActionLink<>() {
247 
248                 private static final long serialVersionUID = 2555747430358755813L;
249 
250                 @Override
251                 public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) {
252                     try {
253                         linkedAccountTO.setSuspended(!linkedAccountTO.isSuspended());
254                         LinkedAccountUR linkedAccountUR = new LinkedAccountUR.Builder().
255                                 operation(PatchOperation.ADD_REPLACE).
256                                 linkedAccountTO(linkedAccountTO).build();
257 
258                         UserUR req = new UserUR();
259                         req.setKey(model.getObject().getKey());
260                         req.getLinkedAccounts().add(linkedAccountUR);
261                         model.setObject(userRestClient.update(model.getObject().getETagValue(), req).getEntity());
262 
263                         SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
264                     } catch (SyncopeClientException e) {
265                         LOG.error("While toggling status of linked account", e);
266                         SyncopeConsoleSession.get().onException(e);
267                         ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
268                     }
269 
270                     checkAddButton(model.getObject().getRealm());
271                     ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
272                     send(LinkedAccountModalPanel.this, Broadcast.DEPTH, new ListViewPanel.ListViewReload<>(target));
273                 }
274             }, ActionLink.ActionType.ENABLE, IdRepoEntitlement.USER_UPDATE);
275         }
276 
277         builder.addAction(new ActionLink<>() {
278 
279             private static final long serialVersionUID = 2555747430358755813L;
280 
281             @Override
282             public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) {
283                 mlp.next("PUSH " + linkedAccountTO.getResource(),
284                         new ReconTaskPanel(
285                                 linkedAccountTO.getResource(),
286                                 new PushTaskTO(),
287                                 model.getObject().getType(),
288                                 null,
289                                 ConnIdSpecialName.UID + "==" + linkedAccountTO.getConnObjectKeyValue(),
290                                 true,
291                                 mlp,
292                                 pageRef),
293                         target);
294                 target.add(mlp);
295 
296                 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
297                 send(LinkedAccountModalPanel.this, Broadcast.BREADTH,
298                         new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target));
299             }
300         }, ActionLink.ActionType.RECONCILIATION_PUSH,
301                 String.format("%s,%s", IdRepoEntitlement.USER_READ, IdRepoEntitlement.TASK_EXECUTE));
302 
303         builder.addAction(new ActionLink<>() {
304 
305             private static final long serialVersionUID = 2555747430358755813L;
306 
307             @Override
308             public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) {
309                 mlp.next("PULL " + linkedAccountTO.getResource(),
310                         new ReconTaskPanel(
311                                 linkedAccountTO.getResource(),
312                                 new PullTaskTO(),
313                                 model.getObject().getType(),
314                                 null,
315                                 ConnIdSpecialName.UID + "==" + linkedAccountTO.getConnObjectKeyValue(),
316                                 true,
317                                 mlp,
318                                 pageRef),
319                         target);
320                 target.add(mlp);
321 
322                 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
323                 send(LinkedAccountModalPanel.this, Broadcast.BREADTH,
324                         new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target));
325             }
326         }, ActionLink.ActionType.RECONCILIATION_PULL,
327                 String.format("%s,%s", IdRepoEntitlement.USER_READ, IdRepoEntitlement.TASK_EXECUTE));
328 
329         if (!recounciliationOnly) {
330             builder.addAction(new ActionLink<>() {
331 
332                 private static final long serialVersionUID = 2555747430358755813L;
333 
334                 @Override
335                 public void onClick(final AjaxRequestTarget target, final LinkedAccountTO linkedAccountTO) {
336                     try {
337                         LinkedAccountUR linkedAccountUR = new LinkedAccountUR.Builder().
338                                 operation(PatchOperation.DELETE).
339                                 linkedAccountTO(linkedAccountTO).build();
340 
341                         UserUR req = new UserUR();
342                         req.setKey(model.getObject().getKey());
343                         req.getLinkedAccounts().add(linkedAccountUR);
344                         model.setObject(userRestClient.update(model.getObject().getETagValue(), req).getEntity());
345                         linkedAccountTOs.remove(linkedAccountTO);
346 
347                         SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
348                     } catch (Exception e) {
349                         LOG.error("While removing linked account {}", linkedAccountTO.getKey(), e);
350                         SyncopeConsoleSession.get().onException(e);
351                     }
352 
353                     checkAddButton(model.getObject().getRealm());
354                     ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
355                     send(LinkedAccountModalPanel.this, Broadcast.DEPTH, new ListViewPanel.ListViewReload<>(target));
356                 }
357             }, ActionLink.ActionType.DELETE, IdRepoEntitlement.USER_UPDATE, true);
358         }
359 
360         builder.addNewItemPanelBuilder(wizard);
361 
362         list = builder.build(MultilevelPanel.FIRST_LEVEL_ID);
363         list.setOutputMarkupId(true);
364         list.setReadOnly(!SyncopeConsoleSession.get().
365                 owns(IdRepoEntitlement.USER_UPDATE, model.getObject().getRealm()));
366 
367         addAjaxLink = new AjaxLink<>("add") {
368 
369             private static final long serialVersionUID = -7978723352517770644L;
370 
371             @Override
372             public void onClick(final AjaxRequestTarget target) {
373                 send(LinkedAccountModalPanel.this, Broadcast.BREADTH,
374                         new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target));
375 
376                 // this opens the wizard (set above) in CREATE mode
377                 send(list, Broadcast.DEPTH, new AjaxWizard.NewItemActionEvent<>(new LinkedAccountTO(), target).
378                         setTitleModel(
379                                 new StringResourceModel("inner.create.linkedAccount", LinkedAccountModalPanel.this)));
380             }
381         };
382         list.addOrReplaceInnerObject(addAjaxLink.setEnabled(!recounciliationOnly).setVisible(!recounciliationOnly));
383 
384         add(mlp.setFirstLevel(list));
385     }
386 
387     private void sortLinkedAccounts() {
388         linkedAccountTOs.sort(Comparator.comparing(LinkedAccountTO::getConnObjectKeyValue));
389     }
390 
391     private void checkAddButton(final String realm) {
392         addAjaxLink.setVisible(SyncopeConsoleSession.get().owns(IdRepoEntitlement.USER_UPDATE, realm));
393     }
394 }