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