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