1 package org
.argeo
.cms
.e4
.users
;
3 import java
.util
.ArrayList
;
4 import java
.util
.HashMap
;
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
;
47 /** Wizard to update users */
48 public class UserBatchUpdateWizard
extends Wizard
{
50 private final static Log log
= LogFactory
.getLog(UserBatchUpdateWizard
.class);
51 private UserAdminWrapper userAdminWrapper
;
54 private ChooseCommandWizardPage chooseCommandPage
;
55 private ChooseUsersWizardPage userListPage
;
56 private ValidateAndLaunchWizardPage validatePage
;
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";
63 private final Map
<String
, String
> commands
= new HashMap
<String
, String
>() {
64 private static final long serialVersionUID
= 1L;
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);
73 public UserBatchUpdateWizard(UserAdminWrapper userAdminWrapper
) {
74 this.userAdminWrapper
= userAdminWrapper
;
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
);
88 public boolean performFinish() {
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 ?"))
96 // We cannot use jobs, user modifications are still meant to be done in
98 // UpdateJob job = null;
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
);
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
);
118 public boolean canFinish() {
119 if (this.getContainer().getCurrentPage() == validatePage
)
124 private class ResetPassword
{
125 private char[] newPwd
;
126 private UserAdminWrapper userAdminWrapper
;
127 private List
<User
> usersToUpdate
;
129 public ResetPassword(UserAdminWrapper userAdminWrapper
, List
<User
> usersToUpdate
, char[] newPwd
) {
130 this.newPwd
= newPwd
;
131 this.usersToUpdate
= usersToUpdate
;
132 this.userAdminWrapper
= userAdminWrapper
;
135 @SuppressWarnings("unchecked")
136 protected void doUpdate() {
137 userAdminWrapper
.beginTransactionIfNeeded();
139 for (User user
: usersToUpdate
) {
140 // the char array is emptied after being used.
141 user
.getCredentials().put(null, newPwd
.clone());
143 userAdminWrapper
.commitOrNotifyTransactionStateChange();
144 } catch (Exception e
) {
145 throw new CmsException("Cannot perform batch update on users", e
);
147 WorkTransaction ut
= userAdminWrapper
.getUserTransaction();
148 if (!ut
.isNoTransactionStatus())
154 private class ResetEmail
{
155 private String newEmail
;
156 private UserAdminWrapper userAdminWrapper
;
157 private List
<User
> usersToUpdate
;
159 public ResetEmail(UserAdminWrapper userAdminWrapper
, List
<User
> usersToUpdate
, String newEmail
) {
160 this.newEmail
= newEmail
;
161 this.usersToUpdate
= usersToUpdate
;
162 this.userAdminWrapper
= userAdminWrapper
;
165 @SuppressWarnings("unchecked")
166 protected void doUpdate() {
167 userAdminWrapper
.beginTransactionIfNeeded();
169 for (User user
: usersToUpdate
) {
170 // the char array is emptied after being used.
171 user
.getProperties().put(LdapAttrs
.mail
.name(), newEmail
);
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
);
181 WorkTransaction ut
= userAdminWrapper
.getUserTransaction();
182 if (!ut
.isNoTransactionStatus())
188 // @SuppressWarnings("unused")
189 // private class AddToGroup extends UpdateJob {
190 // private String groupID;
191 // private Session session;
193 // public AddToGroup(Session session, List<Node> nodesToUpdate,
195 // super(session, nodesToUpdate);
196 // this.session = session;
197 // this.groupID = groupID;
200 // protected void doUpdate(Node node) {
201 // log.info("Add/Remove to group actions are not yet implemented");
202 // // TODO implement this
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);
213 // * Base privileged job that will be run asynchronously to perform the
217 // private abstract class UpdateJob extends PrivilegedJob {
219 // private final UserAdminWrapper userAdminWrapper;
220 // private final List<User> usersToUpdate;
222 // protected abstract void doUpdate(User user);
224 // public UpdateJob(UserAdminWrapper userAdminWrapper,
225 // List<User> usersToUpdate) {
226 // super("Perform update");
227 // this.usersToUpdate = usersToUpdate;
228 // this.userAdminWrapper = userAdminWrapper;
232 // protected IStatus doRun(IProgressMonitor progressMonitor) {
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) {
240 // monitor.worked(1);
242 // userAdminWrapper.getUserTransaction().commit();
243 // } catch (Exception e) {
244 // throw new CmsException(
245 // "Cannot perform batch update on users", e);
247 // UserTransaction ut = userAdminWrapper.getUserTransaction();
249 // if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION)
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();
258 // return Status.OK_STATUS;
264 * Displays a combo box that enables user to choose which action to perform
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
;
272 private Text pwd2Txt
;
274 public ChooseCommandWizardPage() {
275 super("Choose a command to run.");
276 setTitle("Choose a command to run.");
280 public void createControl(Composite parent
) {
281 GridLayout gl
= new GridLayout();
282 Composite container
= new Composite(parent
, SWT
.NO_FOCUS
);
283 container
.setLayout(gl
);
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
);
290 final Composite bottomPart
= new Composite(container
, SWT
.NO_FOCUS
);
291 bottomPart
.setLayoutData(EclipseUiUtils
.fillAll());
292 bottomPart
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
294 chooseCommandCmb
.addSelectionListener(new SelectionAdapter() {
295 private static final long serialVersionUID
= 1L;
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
);
306 populateBooleanFlagCmp(bottomPart
);
308 bottomPart
.layout(true, true);
311 setControl(container
);
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));
322 private void populatePasswordCmp(Composite parent
) {
323 EclipseUiUtils
.clear(parent
);
324 Composite body
= new Composite(parent
, SWT
.NO_FOCUS
);
326 ModifyListener ml
= new ModifyListener() {
327 private static final long serialVersionUID
= -1558726363536729634L;
330 public void modifyText(ModifyEvent event
) {
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
);
341 private void populateEmailCmp(Composite parent
) {
342 EclipseUiUtils
.clear(parent
);
343 Composite body
= new Composite(parent
, SWT
.NO_FOCUS
);
345 ModifyListener ml
= new ModifyListener() {
346 private static final long serialVersionUID
= 2147704227294268317L;
349 public void modifyText(ModifyEvent event
) {
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
);
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";
372 if (EclipseUiUtils
.notEmpty(errorMsg
)) {
373 setMessage(errorMsg
, WizardPage
.ERROR
);
374 setPageComplete(false);
376 setMessage("Page complete, you can proceed to user choice", WizardPage
.INFORMATION
);
377 setPageComplete(true);
380 getContainer().updateButtons();
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));
391 protected String
getCommand() {
392 return commands
.get(chooseCommandCmb
.getItem(chooseCommandCmb
.getSelectionIndex()));
395 protected String
getCommandLbl() {
396 return chooseCommandCmb
.getItem(chooseCommandCmb
.getSelectionIndex());
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();
405 return !trueChk
.getSelection();
408 @SuppressWarnings("unused")
409 protected String
getStringValue() {
411 if (valueTxt
!= null) {
412 value
= valueTxt
.getText();
413 if ("".equals(value
.trim()))
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())
427 return pwdTxt
.getText().toCharArray();
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())
438 return valueTxt
.getText();
443 * Displays a list of users with a check box to be able to choose some of them
445 private class ChooseUsersWizardPage
extends WizardPage
implements IPageChangedListener
{
446 private static final long serialVersionUID
= 7651807402211214274L;
447 private ChooseUserTableViewer userTableCmp
;
449 public ChooseUsersWizardPage() {
450 super("Choose Users");
451 setTitle("Select users who will be impacted");
455 public void createControl(Composite parent
) {
456 Composite pageCmp
= new Composite(parent
, SWT
.NONE
);
457 pageCmp
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
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));
465 // Only show technical DN to admin
466 if (CurrentUser
.isInRole(NodeConstants
.ROLE_ADMIN
))
467 columnDefs
.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300));
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();
477 // Add listener to update message when shown
478 final IWizardContainer wContainer
= this.getContainer();
479 if (wContainer
instanceof IPageChangeProvider
) {
480 ((IPageChangeProvider
) wContainer
).addPageChangedListener(this);
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
);
493 protected List
<User
> getSelectedUsers() {
494 return userTableCmp
.getSelectedUsers();
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() };
502 public ChooseUserTableViewer(Composite parent
, int style
) {
503 super(parent
, style
);
507 protected List
<User
> listFilteredElements(String filter
) {
511 StringBuilder builder
= new StringBuilder();
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("*)");
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("))");
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
);
534 List
<User
> users
= new ArrayList
<User
>();
535 for (Role role
: roles
)
536 // Prevent current logged in user to perform batch on
538 if (!UserAdminUtils
.isCurrentUser((User
) role
))
539 users
.add((User
) role
);
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
;
550 public ValidateAndLaunchWizardPage() {
551 super("Validate and launch");
552 setTitle("Validate and launch");
556 public void createControl(Composite parent
) {
557 Composite pageCmp
= new Composite(parent
, SWT
.NO_FOCUS
);
558 pageCmp
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
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();
573 // Add listener to update message when shown
574 final IWizardContainer wContainer
= this.getContainer();
575 if (wContainer
instanceof IPageChangeProvider
) {
576 ((IPageChangeProvider
) wContainer
).addPageChangedListener(this);
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?";
594 private class ChosenUsersTableViewer
extends LdifUsersTable
{
595 private static final long serialVersionUID
= 7814764735794270541L;
597 public ChosenUsersTableViewer(Composite parent
, int style
) {
598 super(parent
, style
);
602 protected List
<User
> listFilteredElements(String filter
) {
603 return userListPage
.getSelectedUsers();