1 package org
.argeo
.cms
.e4
.users
;
3 import java
.util
.ArrayList
;
4 import java
.util
.HashMap
;
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
.osgi
.transaction
.WorkTransaction
;
21 import org
.argeo
.util
.naming
.LdapAttrs
;
22 import org
.argeo
.util
.naming
.LdapObjs
;
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
;
46 /** Wizard to update users */
47 public class UserBatchUpdateWizard
extends Wizard
{
49 private final static CmsLog log
= CmsLog
.getLog(UserBatchUpdateWizard
.class);
50 private UserAdminWrapper userAdminWrapper
;
53 private ChooseCommandWizardPage chooseCommandPage
;
54 private ChooseUsersWizardPage userListPage
;
55 private ValidateAndLaunchWizardPage validatePage
;
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";
62 private final Map
<String
, String
> commands
= new HashMap
<String
, String
>() {
63 private static final long serialVersionUID
= 1L;
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);
72 public UserBatchUpdateWizard(UserAdminWrapper userAdminWrapper
) {
73 this.userAdminWrapper
= userAdminWrapper
;
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
);
87 public boolean performFinish() {
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 ?"))
95 // We cannot use jobs, user modifications are still meant to be done in
97 // UpdateJob job = null;
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
);
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
);
117 public boolean canFinish() {
118 if (this.getContainer().getCurrentPage() == validatePage
)
123 private class ResetPassword
{
124 private char[] newPwd
;
125 private UserAdminWrapper userAdminWrapper
;
126 private List
<User
> usersToUpdate
;
128 public ResetPassword(UserAdminWrapper userAdminWrapper
, List
<User
> usersToUpdate
, char[] newPwd
) {
129 this.newPwd
= newPwd
;
130 this.usersToUpdate
= usersToUpdate
;
131 this.userAdminWrapper
= userAdminWrapper
;
134 @SuppressWarnings("unchecked")
135 protected void doUpdate() {
136 userAdminWrapper
.beginTransactionIfNeeded();
138 for (User user
: usersToUpdate
) {
139 // the char array is emptied after being used.
140 user
.getCredentials().put(null, newPwd
.clone());
142 userAdminWrapper
.commitOrNotifyTransactionStateChange();
143 } catch (Exception e
) {
144 throw new CmsException("Cannot perform batch update on users", e
);
146 WorkTransaction ut
= userAdminWrapper
.getUserTransaction();
147 if (!ut
.isNoTransactionStatus())
153 private class ResetEmail
{
154 private String newEmail
;
155 private UserAdminWrapper userAdminWrapper
;
156 private List
<User
> usersToUpdate
;
158 public ResetEmail(UserAdminWrapper userAdminWrapper
, List
<User
> usersToUpdate
, String newEmail
) {
159 this.newEmail
= newEmail
;
160 this.usersToUpdate
= usersToUpdate
;
161 this.userAdminWrapper
= userAdminWrapper
;
164 @SuppressWarnings("unchecked")
165 protected void doUpdate() {
166 userAdminWrapper
.beginTransactionIfNeeded();
168 for (User user
: usersToUpdate
) {
169 // the char array is emptied after being used.
170 user
.getProperties().put(LdapAttrs
.mail
.name(), newEmail
);
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
);
180 WorkTransaction ut
= userAdminWrapper
.getUserTransaction();
181 if (!ut
.isNoTransactionStatus())
187 // @SuppressWarnings("unused")
188 // private class AddToGroup extends UpdateJob {
189 // private String groupID;
190 // private Session session;
192 // public AddToGroup(Session session, List<Node> nodesToUpdate,
194 // super(session, nodesToUpdate);
195 // this.session = session;
196 // this.groupID = groupID;
199 // protected void doUpdate(Node node) {
200 // log.info("Add/Remove to group actions are not yet implemented");
201 // // TODO implement this
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);
212 // * Base privileged job that will be run asynchronously to perform the
216 // private abstract class UpdateJob extends PrivilegedJob {
218 // private final UserAdminWrapper userAdminWrapper;
219 // private final List<User> usersToUpdate;
221 // protected abstract void doUpdate(User user);
223 // public UpdateJob(UserAdminWrapper userAdminWrapper,
224 // List<User> usersToUpdate) {
225 // super("Perform update");
226 // this.usersToUpdate = usersToUpdate;
227 // this.userAdminWrapper = userAdminWrapper;
231 // protected IStatus doRun(IProgressMonitor progressMonitor) {
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) {
239 // monitor.worked(1);
241 // userAdminWrapper.getUserTransaction().commit();
242 // } catch (Exception e) {
243 // throw new CmsException(
244 // "Cannot perform batch update on users", e);
246 // UserTransaction ut = userAdminWrapper.getUserTransaction();
248 // if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION)
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();
257 // return Status.OK_STATUS;
263 * Displays a combo box that enables user to choose which action to perform
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
;
271 private Text pwd2Txt
;
273 public ChooseCommandWizardPage() {
274 super("Choose a command to run.");
275 setTitle("Choose a command to run.");
279 public void createControl(Composite parent
) {
280 GridLayout gl
= new GridLayout();
281 Composite container
= new Composite(parent
, SWT
.NO_FOCUS
);
282 container
.setLayout(gl
);
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
);
289 final Composite bottomPart
= new Composite(container
, SWT
.NO_FOCUS
);
290 bottomPart
.setLayoutData(EclipseUiUtils
.fillAll());
291 bottomPart
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
293 chooseCommandCmb
.addSelectionListener(new SelectionAdapter() {
294 private static final long serialVersionUID
= 1L;
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
);
305 populateBooleanFlagCmp(bottomPart
);
307 bottomPart
.layout(true, true);
310 setControl(container
);
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));
321 private void populatePasswordCmp(Composite parent
) {
322 EclipseUiUtils
.clear(parent
);
323 Composite body
= new Composite(parent
, SWT
.NO_FOCUS
);
325 ModifyListener ml
= new ModifyListener() {
326 private static final long serialVersionUID
= -1558726363536729634L;
329 public void modifyText(ModifyEvent event
) {
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
);
340 private void populateEmailCmp(Composite parent
) {
341 EclipseUiUtils
.clear(parent
);
342 Composite body
= new Composite(parent
, SWT
.NO_FOCUS
);
344 ModifyListener ml
= new ModifyListener() {
345 private static final long serialVersionUID
= 2147704227294268317L;
348 public void modifyText(ModifyEvent event
) {
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
);
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";
371 if (EclipseUiUtils
.notEmpty(errorMsg
)) {
372 setMessage(errorMsg
, WizardPage
.ERROR
);
373 setPageComplete(false);
375 setMessage("Page complete, you can proceed to user choice", WizardPage
.INFORMATION
);
376 setPageComplete(true);
379 getContainer().updateButtons();
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));
390 protected String
getCommand() {
391 return commands
.get(chooseCommandCmb
.getItem(chooseCommandCmb
.getSelectionIndex()));
394 protected String
getCommandLbl() {
395 return chooseCommandCmb
.getItem(chooseCommandCmb
.getSelectionIndex());
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();
404 return !trueChk
.getSelection();
407 @SuppressWarnings("unused")
408 protected String
getStringValue() {
410 if (valueTxt
!= null) {
411 value
= valueTxt
.getText();
412 if ("".equals(value
.trim()))
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())
426 return pwdTxt
.getText().toCharArray();
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())
437 return valueTxt
.getText();
442 * Displays a list of users with a check box to be able to choose some of them
444 private class ChooseUsersWizardPage
extends WizardPage
implements IPageChangedListener
{
445 private static final long serialVersionUID
= 7651807402211214274L;
446 private ChooseUserTableViewer userTableCmp
;
448 public ChooseUsersWizardPage() {
449 super("Choose Users");
450 setTitle("Select users who will be impacted");
454 public void createControl(Composite parent
) {
455 Composite pageCmp
= new Composite(parent
, SWT
.NONE
);
456 pageCmp
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
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));
464 // Only show technical DN to admin
465 if (CurrentUser
.isInRole(CmsConstants
.ROLE_ADMIN
))
466 columnDefs
.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300));
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();
476 // Add listener to update message when shown
477 final IWizardContainer wContainer
= this.getContainer();
478 if (wContainer
instanceof IPageChangeProvider
) {
479 ((IPageChangeProvider
) wContainer
).addPageChangedListener(this);
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
);
492 protected List
<User
> getSelectedUsers() {
493 return userTableCmp
.getSelectedUsers();
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() };
501 public ChooseUserTableViewer(Composite parent
, int style
) {
502 super(parent
, style
);
506 protected List
<User
> listFilteredElements(String filter
) {
510 StringBuilder builder
= new StringBuilder();
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("*)");
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("))");
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
);
533 List
<User
> users
= new ArrayList
<User
>();
534 for (Role role
: roles
)
535 // Prevent current logged in user to perform batch on
537 if (!UserAdminUtils
.isCurrentUser((User
) role
))
538 users
.add((User
) role
);
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
;
549 public ValidateAndLaunchWizardPage() {
550 super("Validate and launch");
551 setTitle("Validate and launch");
555 public void createControl(Composite parent
) {
556 Composite pageCmp
= new Composite(parent
, SWT
.NO_FOCUS
);
557 pageCmp
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
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();
572 // Add listener to update message when shown
573 final IWizardContainer wContainer
= this.getContainer();
574 if (wContainer
instanceof IPageChangeProvider
) {
575 ((IPageChangeProvider
) wContainer
).addPageChangedListener(this);
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?";
593 private class ChosenUsersTableViewer
extends LdifUsersTable
{
594 private static final long serialVersionUID
= 7814764735794270541L;
596 public ChosenUsersTableViewer(Composite parent
, int style
) {
597 super(parent
, style
);
601 protected List
<User
> listFilteredElements(String filter
) {
602 return userListPage
.getSelectedUsers();