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 de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
22  import java.io.Serializable;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.List;
26  import java.util.stream.Collectors;
27  import org.apache.commons.lang3.SerializationUtils;
28  import org.apache.syncope.client.console.SyncopeConsoleSession;
29  import org.apache.syncope.client.console.SyncopeWebApplication;
30  import org.apache.syncope.client.console.audit.AuditHistoryModal;
31  import org.apache.syncope.client.console.commons.IdRepoConstants;
32  import org.apache.syncope.client.console.notifications.NotificationTasks;
33  import org.apache.syncope.client.console.pages.BasePage;
34  import org.apache.syncope.client.console.rest.UserRestClient;
35  import org.apache.syncope.client.console.status.ChangePasswordModal;
36  import org.apache.syncope.client.console.tasks.AnyPropagationTasks;
37  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
38  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
39  import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
40  import org.apache.syncope.client.console.wizards.WizardMgtPanel;
41  import org.apache.syncope.client.ui.commons.Constants;
42  import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
43  import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
44  import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
45  import org.apache.syncope.common.lib.AnyOperations;
46  import org.apache.syncope.common.lib.SyncopeConstants;
47  import org.apache.syncope.common.lib.info.PlatformInfo;
48  import org.apache.syncope.common.lib.request.UserUR;
49  import org.apache.syncope.common.lib.to.AnyTypeClassTO;
50  import org.apache.syncope.common.lib.to.DerSchemaTO;
51  import org.apache.syncope.common.lib.to.PlainSchemaTO;
52  import org.apache.syncope.common.lib.to.ProvisioningResult;
53  import org.apache.syncope.common.lib.to.UserTO;
54  import org.apache.syncope.common.lib.types.AnyTypeKind;
55  import org.apache.syncope.common.lib.types.IdRepoEntitlement;
56  import org.apache.syncope.common.rest.api.service.UserSelfService;
57  import org.apache.wicket.PageReference;
58  import org.apache.wicket.ajax.AjaxRequestTarget;
59  import org.apache.wicket.event.Broadcast;
60  import org.apache.wicket.model.CompoundPropertyModel;
61  import org.apache.wicket.model.IModel;
62  import org.apache.wicket.model.Model;
63  import org.apache.wicket.model.ResourceModel;
64  import org.apache.wicket.model.StringResourceModel;
65  
66  public class UserDirectoryPanel extends AnyDirectoryPanel<UserTO, UserRestClient> {
67  
68      private static final long serialVersionUID = -1100228004207271270L;
69  
70      protected UserDirectoryPanel(final String id, final Builder builder) {
71          this(id, builder, true);
72      }
73  
74      protected UserDirectoryPanel(final String id, final Builder builder, final boolean wizardInModal) {
75          super(id, builder, wizardInModal);
76  
77          altDefaultModal.setWindowClosedCallback(target -> {
78              updateResultTable(target);
79              modal.show(false);
80          });
81      }
82  
83      @Override
84      protected String paginatorRowsKey() {
85          return IdRepoConstants.PREF_USERS_PAGINATOR_ROWS;
86      }
87  
88      @Override
89      protected String[] getDefaultAttributeSelection() {
90          return UserDisplayAttributesModalPanel.DEFAULT_SELECTION;
91      }
92  
93      @Override
94      protected Collection<ActionType> getBatches() {
95          List<ActionType> batches = new ArrayList<>();
96          batches.add(ActionType.MUSTCHANGEPASSWORD);
97          batches.add(ActionType.DELETE);
98          batches.add(ActionType.SUSPEND);
99          batches.add(ActionType.REACTIVATE);
100         return batches;
101     }
102 
103     @Override
104     public ActionsPanel<Serializable> getHeader(final String componentId) {
105         final ActionsPanel<Serializable> panel = super.getHeader(componentId);
106 
107         panel.add(new ActionLink<>() {
108 
109             private static final long serialVersionUID = -7978723352517770644L;
110 
111             @Override
112             public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
113                 target.add(displayAttributeModal.setContent(new UserDisplayAttributesModalPanel<>(
114                         displayAttributeModal,
115                         page.getPageReference(),
116                         plainSchemas.stream().map(PlainSchemaTO::getKey).collect(Collectors.toList()),
117                         derSchemas.stream().map(DerSchemaTO::getKey).collect(Collectors.toList()))));
118 
119                 displayAttributeModal.header(new ResourceModel("any.attr.display"));
120                 displayAttributeModal.addSubmitButton();
121                 displayAttributeModal.show(true);
122             }
123 
124             @Override
125             protected boolean statusCondition(final Serializable modelObject) {
126                 return wizardInModal;
127             }
128         }, ActionType.CHANGE_VIEW, IdRepoEntitlement.USER_READ).hideLabel();
129         return panel;
130     }
131 
132     @Override
133     public ActionsPanel<UserTO> getActions(final IModel<UserTO> model) {
134         final ActionsPanel<UserTO> panel = super.getActions(model);
135 
136         panel.add(new ActionLink<>() {
137 
138             private static final long serialVersionUID = -7978723352517770644L;
139 
140             @Override
141             public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
142                 send(UserDirectoryPanel.this, Broadcast.EXACT,
143                         new AjaxWizard.EditItemActionEvent<>(
144                                 new UserWrapper(restClient.read(model.getObject().getKey())), target));
145             }
146         }, ActionType.EDIT,
147                 String.format("%s,%s", IdRepoEntitlement.USER_READ, IdRepoEntitlement.USER_UPDATE)).
148                 setRealms(realm, model.getObject().getDynRealms());
149 
150         panel.add(new ActionLink<>() {
151 
152             private static final long serialVersionUID = -7978723352517770644L;
153 
154             @Override
155             public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
156                 try {
157                     model.setObject(restClient.read(model.getObject().getKey()));
158                     restClient.mustChangePassword(
159                             model.getObject().getETagValue(),
160                             !model.getObject().isMustChangePassword(),
161                             model.getObject().getKey());
162 
163                     SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
164                     target.add(container);
165                 } catch (Exception e) {
166                     LOG.error("While actioning object {}", model.getObject().getKey(), e);
167                     SyncopeConsoleSession.get().onException(e);
168                 }
169                 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
170             }
171         }, ActionType.MUSTCHANGEPASSWORD, IdRepoEntitlement.USER_UPDATE).
172                 setRealms(realm, model.getObject().getDynRealms());
173 
174         if (wizardInModal) {
175             panel.add(new ActionLink<>() {
176 
177                 private static final long serialVersionUID = -4875218360625971340L;
178 
179                 @Override
180                 public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
181                     model.setObject(restClient.read(model.getObject().getKey()));
182                     IModel<AnyWrapper<UserTO>> formModel = new CompoundPropertyModel<>(
183                             new UserWrapper(model.getObject()));
184                     displayAttributeModal.setFormModel(formModel);
185 
186                     target.add(displayAttributeModal.setContent(new ChangePasswordModal(
187                             displayAttributeModal,
188                             new UserWrapper(model.getObject()),
189                             pageRef)));
190 
191                     displayAttributeModal.header(new Model<>(
192                             getString("any.edit", new Model<>(new UserWrapper(model.getObject())))));
193 
194                     displayAttributeModal.size(Modal.Size.Large);
195                     displayAttributeModal.show(true);
196                 }
197             }, ActionType.PASSWORD_MANAGEMENT, IdRepoEntitlement.USER_UPDATE).
198                     setRealms(realm, model.getObject().getDynRealms());
199 
200             PlatformInfo platformInfo = SyncopeConsoleSession.get().getAnonymousClient().platform();
201             if (platformInfo.isPwdResetAllowed() && !platformInfo.isPwdResetRequiringSecurityQuestions()) {
202                 panel.add(new ActionLink<>() {
203 
204                     private static final long serialVersionUID = -7978723352517770644L;
205 
206                     @Override
207                     public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
208                         try {
209                             SyncopeConsoleSession.get().getAnonymousClient().getService(UserSelfService.class).
210                                     requestPasswordReset(model.getObject().getUsername(), null);
211 
212                             SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
213                             target.add(container);
214                         } catch (Exception e) {
215                             LOG.error("While actioning object {}", model.getObject().getKey(), e);
216                             SyncopeConsoleSession.get().onException(e);
217                         }
218                         ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
219                     }
220                 }, ActionType.REQUEST_PASSWORD_RESET, IdRepoEntitlement.USER_UPDATE).
221                         setRealms(realm, model.getObject().getDynRealms());
222             }
223 
224             SyncopeWebApplication.get().getAnyDirectoryPanelAdditionalActionLinksProvider().get(
225                     model,
226                     realm,
227                     altDefaultModal,
228                     getString("any.edit", new Model<>(new UserWrapper(model.getObject()))),
229                     this,
230                     pageRef).forEach(panel::add);
231 
232             panel.add(new ActionLink<>() {
233 
234                 private static final long serialVersionUID = -7978723352517770644L;
235 
236                 @Override
237                 public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
238                     target.add(utilityModal.setContent(new AnyPropagationTasks(
239                             utilityModal, AnyTypeKind.USER, model.getObject().getKey(), pageRef)));
240 
241                     utilityModal.header(new StringResourceModel("any.propagation.tasks", model));
242                     utilityModal.show(true);
243                 }
244             }, ActionType.PROPAGATION_TASKS, IdRepoEntitlement.TASK_LIST);
245 
246             panel.add(new ActionLink<>() {
247 
248                 private static final long serialVersionUID = -7978723352517770644L;
249 
250                 @Override
251                 public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
252                     target.add(utilityModal.setContent(
253                             new NotificationTasks(AnyTypeKind.USER, model.getObject().getKey(), pageRef)));
254                     utilityModal.header(new StringResourceModel("any.notification.tasks", model));
255                     utilityModal.show(true);
256                     target.add(utilityModal);
257                 }
258             }, ActionType.NOTIFICATION_TASKS, IdRepoEntitlement.TASK_LIST);
259         }
260 
261         if (wizardInModal) {
262             panel.add(new ActionLink<>() {
263 
264                 private static final long serialVersionUID = -1978723352517770644L;
265 
266                 @Override
267                 public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
268                     model.setObject(restClient.read(model.getObject().getKey()));
269                     target.add(altDefaultModal.setContent(new AuditHistoryModal<>(
270                             null,
271                             null,
272                             model.getObject(),
273                             IdRepoEntitlement.USER_UPDATE,
274                             auditRestClient) {
275 
276                         private static final long serialVersionUID = 959378158400669867L;
277 
278                         @Override
279                         protected void restore(final String json, final AjaxRequestTarget target) {
280                             // The original audit record masks the password and the security
281                             // answer; so we cannot use the audit record to resurrect the entry
282                             // based on mask data.
283                             //
284                             // The method behavior below will reset the audit record such
285                             // that the current security answer and the password for the object
286                             // are always maintained, and such properties for the
287                             // user cannot be restored using audit records.
288                             UserTO original = model.getObject();
289                             try {
290                                 UserTO updated = MAPPER.readValue(json, UserTO.class);
291                                 UserUR updateReq = AnyOperations.diff(updated, original, false);
292                                 updateReq.setPassword(null);
293                                 updateReq.setSecurityAnswer(null);
294                                 ProvisioningResult<UserTO> result =
295                                         restClient.update(original.getETagValue(), updateReq);
296                                 model.getObject().setLastChangeDate(result.getEntity().getLastChangeDate());
297 
298                                 SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
299                                 target.add(container);
300                             } catch (Exception e) {
301                                 LOG.error("While restoring user {}", model.getObject().getKey(), e);
302                                 SyncopeConsoleSession.get().onException(e);
303                             }
304                             ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
305                         }
306                     }));
307 
308                     altDefaultModal.header(new Model<>(
309                             getString("auditHistory.title", new Model<>(new UserWrapper(model.getObject())))));
310 
311                     altDefaultModal.show(true);
312                 }
313             }, ActionType.VIEW_AUDIT_HISTORY,
314                     String.format("%s,%s", IdRepoEntitlement.USER_READ, IdRepoEntitlement.AUDIT_LIST)).
315                     setRealms(realm, model.getObject().getDynRealms());
316         }
317 
318         panel.add(new ActionLink<>() {
319 
320             private static final long serialVersionUID = -7978723352517770644L;
321 
322             @Override
323             public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
324                 UserTO clone = SerializationUtils.clone(model.getObject());
325                 clone.setKey(null);
326                 clone.setUsername(model.getObject().getUsername() + "_clone");
327                 send(UserDirectoryPanel.this, Broadcast.EXACT,
328                         new AjaxWizard.NewItemActionEvent<>(new UserWrapper(clone), target));
329             }
330 
331             @Override
332             protected boolean statusCondition(final UserTO modelObject) {
333                 return addAjaxLink.isVisibleInHierarchy() && realm.startsWith(SyncopeConstants.ROOT_REALM);
334             }
335         }, ActionType.CLONE, IdRepoEntitlement.USER_CREATE).setRealm(realm);
336 
337         panel.add(new ActionLink<>() {
338 
339             private static final long serialVersionUID = -7978723352517770644L;
340 
341             @Override
342             public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
343                 try {
344                     restClient.delete(model.getObject().getETagValue(), model.getObject().getKey());
345 
346                     SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
347                     target.add(container);
348                 } catch (Exception e) {
349                     LOG.error("While deleting user {}", model.getObject().getKey(), e);
350                     SyncopeConsoleSession.get().onException(e);
351                 }
352                 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
353             }
354 
355             @Override
356             protected boolean statusCondition(final UserTO modelObject) {
357                 return realm.startsWith(SyncopeConstants.ROOT_REALM);
358             }
359         }, ActionType.DELETE, IdRepoEntitlement.USER_DELETE, true).setRealm(realm);
360 
361         return panel;
362     }
363 
364     public static class Builder extends AnyDirectoryPanel.Builder<UserTO, UserRestClient> {
365 
366         private static final long serialVersionUID = -6603152478702381900L;
367 
368         public Builder(
369                 final List<AnyTypeClassTO> anyTypeClassTOs,
370                 final UserRestClient restClient,
371                 final String type,
372                 final PageReference pageRef) {
373 
374             super(anyTypeClassTOs, restClient, type, pageRef);
375             setShowResultPage(true);
376         }
377 
378         @Override
379         protected WizardMgtPanel<AnyWrapper<UserTO>> newInstance(final String id, final boolean wizardInModal) {
380             return new UserDirectoryPanel(id, this, wizardInModal);
381         }
382     }
383 }