]> git.argeo.org Git - lgpl/argeo-commons.git/blob - UserBatchUpdateWizard.java
01ccc7d73221d7732f4ccf26bb106ec9067ce36f
[lgpl/argeo-commons.git] / UserBatchUpdateWizard.java
1 package org.argeo.cms.e4.users;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.List;
6 import java.util.Map;
7
8 import org.argeo.api.cms.CmsConstants;
9 import org.argeo.api.cms.CmsLog;
10 import org.argeo.cms.CmsException;
11 import org.argeo.cms.auth.CurrentUser;
12 import org.argeo.cms.auth.UserAdminUtils;
13 import org.argeo.cms.e4.users.providers.CommonNameLP;
14 import org.argeo.cms.e4.users.providers.DomainNameLP;
15 import org.argeo.cms.e4.users.providers.MailLP;
16 import org.argeo.cms.e4.users.providers.UserNameLP;
17 import org.argeo.eclipse.ui.ColumnDefinition;
18 import org.argeo.eclipse.ui.EclipseUiUtils;
19 import org.argeo.eclipse.ui.parts.LdifUsersTable;
20 import org.argeo.util.naming.LdapAttrs;
21 import org.argeo.util.naming.LdapObjs;
22 import org.argeo.util.transaction.WorkTransaction;
23 import org.eclipse.jface.dialogs.IPageChangeProvider;
24 import org.eclipse.jface.dialogs.IPageChangedListener;
25 import org.eclipse.jface.dialogs.MessageDialog;
26 import org.eclipse.jface.dialogs.PageChangedEvent;
27 import org.eclipse.jface.wizard.IWizardContainer;
28 import org.eclipse.jface.wizard.Wizard;
29 import org.eclipse.jface.wizard.WizardPage;
30 import org.eclipse.swt.SWT;
31 import org.eclipse.swt.events.ModifyEvent;
32 import org.eclipse.swt.events.ModifyListener;
33 import org.eclipse.swt.events.SelectionAdapter;
34 import org.eclipse.swt.events.SelectionEvent;
35 import org.eclipse.swt.layout.GridData;
36 import org.eclipse.swt.layout.GridLayout;
37 import org.eclipse.swt.widgets.Button;
38 import org.eclipse.swt.widgets.Combo;
39 import org.eclipse.swt.widgets.Composite;
40 import org.eclipse.swt.widgets.Text;
41 import org.osgi.framework.InvalidSyntaxException;
42 import org.osgi.service.useradmin.Role;
43 import org.osgi.service.useradmin.User;
44 import org.osgi.service.useradmin.UserAdminEvent;
45
46 /** Wizard to update users */
47 public class UserBatchUpdateWizard extends Wizard {
48
49 private final static CmsLog log = CmsLog.getLog(UserBatchUpdateWizard.class);
50 private UserAdminWrapper userAdminWrapper;
51
52 // pages
53 private ChooseCommandWizardPage chooseCommandPage;
54 private ChooseUsersWizardPage userListPage;
55 private ValidateAndLaunchWizardPage validatePage;
56
57 // Various implemented commands keys
58 private final static String CMD_UPDATE_PASSWORD = "resetPassword";
59 private final static String CMD_UPDATE_EMAIL = "resetEmail";
60 private final static String CMD_GROUP_MEMBERSHIP = "groupMembership";
61
62 private final Map<String, String> commands = new HashMap<String, String>() {
63 private static final long serialVersionUID = 1L;
64 {
65 put("Reset password(s)", CMD_UPDATE_PASSWORD);
66 put("Reset email(s)", CMD_UPDATE_EMAIL);
67 // TODO implement role / group management
68 // put("Add/Remove from group", CMD_GROUP_MEMBERSHIP);
69 }
70 };
71
72 public UserBatchUpdateWizard(UserAdminWrapper userAdminWrapper) {
73 this.userAdminWrapper = userAdminWrapper;
74 }
75
76 @Override
77 public void addPages() {
78 chooseCommandPage = new ChooseCommandWizardPage();
79 addPage(chooseCommandPage);
80 userListPage = new ChooseUsersWizardPage();
81 addPage(userListPage);
82 validatePage = new ValidateAndLaunchWizardPage();
83 addPage(validatePage);
84 }
85
86 @Override
87 public boolean performFinish() {
88 if (!canFinish())
89 return false;
90 WorkTransaction ut = userAdminWrapper.getUserTransaction();
91 if (!ut.isNoTransactionStatus() && !MessageDialog.openConfirm(getShell(), "Existing Transaction",
92 "A user transaction is already existing, " + "are you sure you want to proceed ?"))
93 return false;
94
95 // We cannot use jobs, user modifications are still meant to be done in
96 // the UIThread
97 // UpdateJob job = null;
98 // if (job != null)
99 // job.schedule();
100
101 if (CMD_UPDATE_PASSWORD.equals(chooseCommandPage.getCommand())) {
102 char[] newValue = chooseCommandPage.getPwdValue();
103 if (newValue == null)
104 throw new CmsException("Password cannot be null or an empty string");
105 ResetPassword job = new ResetPassword(userAdminWrapper, userListPage.getSelectedUsers(), newValue);
106 job.doUpdate();
107 } else if (CMD_UPDATE_EMAIL.equals(chooseCommandPage.getCommand())) {
108 String newValue = chooseCommandPage.getEmailValue();
109 if (newValue == null)
110 throw new CmsException("Password cannot be null or an empty string");
111 ResetEmail job = new ResetEmail(userAdminWrapper, userListPage.getSelectedUsers(), newValue);
112 job.doUpdate();
113 }
114 return true;
115 }
116
117 public boolean canFinish() {
118 if (this.getContainer().getCurrentPage() == validatePage)
119 return true;
120 return false;
121 }
122
123 private class ResetPassword {
124 private char[] newPwd;
125 private UserAdminWrapper userAdminWrapper;
126 private List<User> usersToUpdate;
127
128 public ResetPassword(UserAdminWrapper userAdminWrapper, List<User> usersToUpdate, char[] newPwd) {
129 this.newPwd = newPwd;
130 this.usersToUpdate = usersToUpdate;
131 this.userAdminWrapper = userAdminWrapper;
132 }
133
134 @SuppressWarnings("unchecked")
135 protected void doUpdate() {
136 userAdminWrapper.beginTransactionIfNeeded();
137 try {
138 for (User user : usersToUpdate) {
139 // the char array is emptied after being used.
140 user.getCredentials().put(null, newPwd.clone());
141 }
142 userAdminWrapper.commitOrNotifyTransactionStateChange();
143 } catch (Exception e) {
144 throw new CmsException("Cannot perform batch update on users", e);
145 } finally {
146 WorkTransaction ut = userAdminWrapper.getUserTransaction();
147 if (!ut.isNoTransactionStatus())
148 ut.rollback();
149 }
150 }
151 }
152
153 private class ResetEmail {
154 private String newEmail;
155 private UserAdminWrapper userAdminWrapper;
156 private List<User> usersToUpdate;
157
158 public ResetEmail(UserAdminWrapper userAdminWrapper, List<User> usersToUpdate, String newEmail) {
159 this.newEmail = newEmail;
160 this.usersToUpdate = usersToUpdate;
161 this.userAdminWrapper = userAdminWrapper;
162 }
163
164 @SuppressWarnings("unchecked")
165 protected void doUpdate() {
166 userAdminWrapper.beginTransactionIfNeeded();
167 try {
168 for (User user : usersToUpdate) {
169 // the char array is emptied after being used.
170 user.getProperties().put(LdapAttrs.mail.name(), newEmail);
171 }
172
173 userAdminWrapper.commitOrNotifyTransactionStateChange();
174 if (!usersToUpdate.isEmpty())
175 userAdminWrapper.notifyListeners(
176 new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, usersToUpdate.get(0)));
177 } catch (Exception e) {
178 throw new CmsException("Cannot perform batch update on users", e);
179 } finally {
180 WorkTransaction ut = userAdminWrapper.getUserTransaction();
181 if (!ut.isNoTransactionStatus())
182 ut.rollback();
183 }
184 }
185 }
186
187 // @SuppressWarnings("unused")
188 // private class AddToGroup extends UpdateJob {
189 // private String groupID;
190 // private Session session;
191 //
192 // public AddToGroup(Session session, List<Node> nodesToUpdate,
193 // String groupID) {
194 // super(session, nodesToUpdate);
195 // this.session = session;
196 // this.groupID = groupID;
197 // }
198 //
199 // protected void doUpdate(Node node) {
200 // log.info("Add/Remove to group actions are not yet implemented");
201 // // TODO implement this
202 // // try {
203 // // throw new CmsException("Not yet implemented");
204 // // } catch (RepositoryException re) {
205 // // throw new CmsException(
206 // // "Unable to update boolean value for node " + node, re);
207 // // }
208 // }
209 // }
210
211 // /**
212 // * Base privileged job that will be run asynchronously to perform the
213 // batch
214 // * update
215 // */
216 // private abstract class UpdateJob extends PrivilegedJob {
217 //
218 // private final UserAdminWrapper userAdminWrapper;
219 // private final List<User> usersToUpdate;
220 //
221 // protected abstract void doUpdate(User user);
222 //
223 // public UpdateJob(UserAdminWrapper userAdminWrapper,
224 // List<User> usersToUpdate) {
225 // super("Perform update");
226 // this.usersToUpdate = usersToUpdate;
227 // this.userAdminWrapper = userAdminWrapper;
228 // }
229 //
230 // @Override
231 // protected IStatus doRun(IProgressMonitor progressMonitor) {
232 // try {
233 // JcrMonitor monitor = new EclipseJcrMonitor(progressMonitor);
234 // int total = usersToUpdate.size();
235 // monitor.beginTask("Performing change", total);
236 // userAdminWrapper.beginTransactionIfNeeded();
237 // for (User user : usersToUpdate) {
238 // doUpdate(user);
239 // monitor.worked(1);
240 // }
241 // userAdminWrapper.getUserTransaction().commit();
242 // } catch (Exception e) {
243 // throw new CmsException(
244 // "Cannot perform batch update on users", e);
245 // } finally {
246 // UserTransaction ut = userAdminWrapper.getUserTransaction();
247 // try {
248 // if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION)
249 // ut.rollback();
250 // } catch (IllegalStateException | SecurityException
251 // | SystemException e) {
252 // log.error("Unable to rollback session in 'finally', "
253 // + "the system might be in a dirty state");
254 // e.printStackTrace();
255 // }
256 // }
257 // return Status.OK_STATUS;
258 // }
259 // }
260
261 // PAGES
262 /**
263 * Displays a combo box that enables user to choose which action to perform
264 */
265 private class ChooseCommandWizardPage extends WizardPage {
266 private static final long serialVersionUID = -8069434295293996633L;
267 private Combo chooseCommandCmb;
268 private Button trueChk;
269 private Text valueTxt;
270 private Text pwdTxt;
271 private Text pwd2Txt;
272
273 public ChooseCommandWizardPage() {
274 super("Choose a command to run.");
275 setTitle("Choose a command to run.");
276 }
277
278 @Override
279 public void createControl(Composite parent) {
280 GridLayout gl = new GridLayout();
281 Composite container = new Composite(parent, SWT.NO_FOCUS);
282 container.setLayout(gl);
283
284 chooseCommandCmb = new Combo(container, SWT.READ_ONLY);
285 chooseCommandCmb.setLayoutData(EclipseUiUtils.fillWidth());
286 String[] values = commands.keySet().toArray(new String[0]);
287 chooseCommandCmb.setItems(values);
288
289 final Composite bottomPart = new Composite(container, SWT.NO_FOCUS);
290 bottomPart.setLayoutData(EclipseUiUtils.fillAll());
291 bottomPart.setLayout(EclipseUiUtils.noSpaceGridLayout());
292
293 chooseCommandCmb.addSelectionListener(new SelectionAdapter() {
294 private static final long serialVersionUID = 1L;
295
296 @Override
297 public void widgetSelected(SelectionEvent e) {
298 if (getCommand().equals(CMD_UPDATE_PASSWORD))
299 populatePasswordCmp(bottomPart);
300 else if (getCommand().equals(CMD_UPDATE_EMAIL))
301 populateEmailCmp(bottomPart);
302 else if (getCommand().equals(CMD_GROUP_MEMBERSHIP))
303 populateGroupCmp(bottomPart);
304 else
305 populateBooleanFlagCmp(bottomPart);
306 checkPageComplete();
307 bottomPart.layout(true, true);
308 }
309 });
310 setControl(container);
311 }
312
313 private void populateBooleanFlagCmp(Composite parent) {
314 EclipseUiUtils.clear(parent);
315 trueChk = new Button(parent, SWT.CHECK);
316 trueChk.setText("Do it. (It will to the contrary if unchecked)");
317 trueChk.setSelection(true);
318 trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
319 }
320
321 private void populatePasswordCmp(Composite parent) {
322 EclipseUiUtils.clear(parent);
323 Composite body = new Composite(parent, SWT.NO_FOCUS);
324
325 ModifyListener ml = new ModifyListener() {
326 private static final long serialVersionUID = -1558726363536729634L;
327
328 @Override
329 public void modifyText(ModifyEvent event) {
330 checkPageComplete();
331 }
332 };
333
334 body.setLayout(new GridLayout(2, false));
335 body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
336 pwdTxt = EclipseUiUtils.createGridLP(body, "New password", ml);
337 pwd2Txt = EclipseUiUtils.createGridLP(body, "Repeat password", ml);
338 }
339
340 private void populateEmailCmp(Composite parent) {
341 EclipseUiUtils.clear(parent);
342 Composite body = new Composite(parent, SWT.NO_FOCUS);
343
344 ModifyListener ml = new ModifyListener() {
345 private static final long serialVersionUID = 2147704227294268317L;
346
347 @Override
348 public void modifyText(ModifyEvent event) {
349 checkPageComplete();
350 }
351 };
352
353 body.setLayout(new GridLayout(2, false));
354 body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
355 valueTxt = EclipseUiUtils.createGridLT(body, "New e-mail", ml);
356 }
357
358 private void checkPageComplete() {
359 String errorMsg = null;
360 if (chooseCommandCmb.getSelectionIndex() < 0)
361 errorMsg = "Please select an action";
362 else if (CMD_UPDATE_EMAIL.equals(getCommand())) {
363 if (!valueTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN))
364 errorMsg = "Not a valid e-mail address";
365 } else if (CMD_UPDATE_PASSWORD.equals(getCommand())) {
366 if (EclipseUiUtils.isEmpty(pwdTxt.getText()) || pwdTxt.getText().length() < 4)
367 errorMsg = "Please enter a password that is at least 4 character long";
368 else if (!pwdTxt.getText().equals(pwd2Txt.getText()))
369 errorMsg = "Passwords are different";
370 }
371 if (EclipseUiUtils.notEmpty(errorMsg)) {
372 setMessage(errorMsg, WizardPage.ERROR);
373 setPageComplete(false);
374 } else {
375 setMessage("Page complete, you can proceed to user choice", WizardPage.INFORMATION);
376 setPageComplete(true);
377 }
378
379 getContainer().updateButtons();
380 }
381
382 private void populateGroupCmp(Composite parent) {
383 EclipseUiUtils.clear(parent);
384 trueChk = new Button(parent, SWT.CHECK);
385 trueChk.setText("Add to group. (It will remove user(s) from the " + "corresponding group if unchecked)");
386 trueChk.setSelection(true);
387 trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
388 }
389
390 protected String getCommand() {
391 return commands.get(chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex()));
392 }
393
394 protected String getCommandLbl() {
395 return chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex());
396 }
397
398 @SuppressWarnings("unused")
399 protected boolean getBoleanValue() {
400 // FIXME this is not consistent and will lead to errors.
401 if ("argeo:enabled".equals(getCommand()))
402 return trueChk.getSelection();
403 else
404 return !trueChk.getSelection();
405 }
406
407 @SuppressWarnings("unused")
408 protected String getStringValue() {
409 String value = null;
410 if (valueTxt != null) {
411 value = valueTxt.getText();
412 if ("".equals(value.trim()))
413 value = null;
414 }
415 return value;
416 }
417
418 protected char[] getPwdValue() {
419 // We do not directly reset the password text fields: There is no
420 // need to over secure this process: setting a pwd to multi users
421 // at the same time is anyhow a bad practice and should be used only
422 // in test environment or for temporary access
423 if (pwdTxt == null || pwdTxt.isDisposed())
424 return null;
425 else
426 return pwdTxt.getText().toCharArray();
427 }
428
429 protected String getEmailValue() {
430 // We do not directly reset the password text fields: There is no
431 // need to over secure this process: setting a pwd to multi users
432 // at the same time is anyhow a bad practice and should be used only
433 // in test environment or for temporary access
434 if (valueTxt == null || valueTxt.isDisposed())
435 return null;
436 else
437 return valueTxt.getText();
438 }
439 }
440
441 /**
442 * Displays a list of users with a check box to be able to choose some of them
443 */
444 private class ChooseUsersWizardPage extends WizardPage implements IPageChangedListener {
445 private static final long serialVersionUID = 7651807402211214274L;
446 private ChooseUserTableViewer userTableCmp;
447
448 public ChooseUsersWizardPage() {
449 super("Choose Users");
450 setTitle("Select users who will be impacted");
451 }
452
453 @Override
454 public void createControl(Composite parent) {
455 Composite pageCmp = new Composite(parent, SWT.NONE);
456 pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
457
458 // Define the displayed columns
459 List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
460 columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150));
461 columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150));
462 columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200));
463
464 // Only show technical DN to admin
465 if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN))
466 columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300));
467
468 userTableCmp = new ChooseUserTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
469 userTableCmp.setLayoutData(EclipseUiUtils.fillAll());
470 userTableCmp.setColumnDefinitions(columnDefs);
471 userTableCmp.populate(true, true);
472 userTableCmp.refresh();
473
474 setControl(pageCmp);
475
476 // Add listener to update message when shown
477 final IWizardContainer wContainer = this.getContainer();
478 if (wContainer instanceof IPageChangeProvider) {
479 ((IPageChangeProvider) wContainer).addPageChangedListener(this);
480 }
481
482 }
483
484 @Override
485 public void pageChanged(PageChangedEvent event) {
486 if (event.getSelectedPage() == this) {
487 String msg = "Chosen batch action: " + chooseCommandPage.getCommandLbl();
488 ((WizardPage) event.getSelectedPage()).setMessage(msg);
489 }
490 }
491
492 protected List<User> getSelectedUsers() {
493 return userTableCmp.getSelectedUsers();
494 }
495
496 private class ChooseUserTableViewer extends LdifUsersTable {
497 private static final long serialVersionUID = 5080437561015853124L;
498 private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.DN, LdapAttrs.cn.name(),
499 LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() };
500
501 public ChooseUserTableViewer(Composite parent, int style) {
502 super(parent, style);
503 }
504
505 @Override
506 protected List<User> listFilteredElements(String filter) {
507 Role[] roles;
508
509 try {
510 StringBuilder builder = new StringBuilder();
511
512 StringBuilder tmpBuilder = new StringBuilder();
513 if (EclipseUiUtils.notEmpty(filter))
514 for (String prop : knownProps) {
515 tmpBuilder.append("(");
516 tmpBuilder.append(prop);
517 tmpBuilder.append("=*");
518 tmpBuilder.append(filter);
519 tmpBuilder.append("*)");
520 }
521 if (tmpBuilder.length() > 1) {
522 builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=")
523 .append(LdapObjs.inetOrgPerson.name()).append(")(|");
524 builder.append(tmpBuilder.toString());
525 builder.append("))");
526 } else
527 builder.append("(").append(LdapAttrs.objectClass.name()).append("=")
528 .append(LdapObjs.inetOrgPerson.name()).append(")");
529 roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString());
530 } catch (InvalidSyntaxException e) {
531 throw new CmsException("Unable to get roles with filter: " + filter, e);
532 }
533 List<User> users = new ArrayList<User>();
534 for (Role role : roles)
535 // Prevent current logged in user to perform batch on
536 // himself
537 if (!UserAdminUtils.isCurrentUser((User) role))
538 users.add((User) role);
539 return users;
540 }
541 }
542 }
543
544 /** Summary of input data before launching the process */
545 private class ValidateAndLaunchWizardPage extends WizardPage implements IPageChangedListener {
546 private static final long serialVersionUID = 7098918351451743853L;
547 private ChosenUsersTableViewer userTableCmp;
548
549 public ValidateAndLaunchWizardPage() {
550 super("Validate and launch");
551 setTitle("Validate and launch");
552 }
553
554 @Override
555 public void createControl(Composite parent) {
556 Composite pageCmp = new Composite(parent, SWT.NO_FOCUS);
557 pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
558
559 List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
560 columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150));
561 columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150));
562 columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200));
563 // Only show technical DN to admin
564 if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN))
565 columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300));
566 userTableCmp = new ChosenUsersTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
567 userTableCmp.setLayoutData(EclipseUiUtils.fillAll());
568 userTableCmp.setColumnDefinitions(columnDefs);
569 userTableCmp.populate(false, false);
570 userTableCmp.refresh();
571 setControl(pageCmp);
572 // Add listener to update message when shown
573 final IWizardContainer wContainer = this.getContainer();
574 if (wContainer instanceof IPageChangeProvider) {
575 ((IPageChangeProvider) wContainer).addPageChangedListener(this);
576 }
577 }
578
579 @Override
580 public void pageChanged(PageChangedEvent event) {
581 if (event.getSelectedPage() == this) {
582 @SuppressWarnings({ "unchecked", "rawtypes" })
583 Object[] values = ((ArrayList) userListPage.getSelectedUsers())
584 .toArray(new Object[userListPage.getSelectedUsers().size()]);
585 userTableCmp.getTableViewer().setInput(values);
586 String msg = "Following batch action: [" + chooseCommandPage.getCommandLbl()
587 + "] will be perfomed on the users listed below.\n";
588 // + "Are you sure you want to proceed?";
589 setMessage(msg);
590 }
591 }
592
593 private class ChosenUsersTableViewer extends LdifUsersTable {
594 private static final long serialVersionUID = 7814764735794270541L;
595
596 public ChosenUsersTableViewer(Composite parent, int style) {
597 super(parent, style);
598 }
599
600 @Override
601 protected List<User> listFilteredElements(String filter) {
602 return userListPage.getSelectedUsers();
603 }
604 }
605 }
606 }