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