1 package org
.argeo
.cms
.ui
.workbench
.internal
.useradmin
.parts
;
3 import java
.util
.ArrayList
;
4 import java
.util
.HashMap
;
8 import javax
.transaction
.SystemException
;
9 import javax
.transaction
.UserTransaction
;
11 import org
.apache
.commons
.logging
.Log
;
12 import org
.apache
.commons
.logging
.LogFactory
;
13 import org
.argeo
.cms
.CmsException
;
14 import org
.argeo
.cms
.auth
.CurrentUser
;
15 import org
.argeo
.cms
.ui
.workbench
.internal
.useradmin
.UiAdminUtils
;
16 import org
.argeo
.cms
.ui
.workbench
.internal
.useradmin
.UserAdminWrapper
;
17 import org
.argeo
.cms
.ui
.workbench
.internal
.useradmin
.providers
.CommonNameLP
;
18 import org
.argeo
.cms
.ui
.workbench
.internal
.useradmin
.providers
.DomainNameLP
;
19 import org
.argeo
.cms
.ui
.workbench
.internal
.useradmin
.providers
.MailLP
;
20 import org
.argeo
.cms
.ui
.workbench
.internal
.useradmin
.providers
.UserNameLP
;
21 import org
.argeo
.cms
.util
.UserAdminUtils
;
22 import org
.argeo
.eclipse
.ui
.ColumnDefinition
;
23 import org
.argeo
.eclipse
.ui
.EclipseUiUtils
;
24 import org
.argeo
.eclipse
.ui
.parts
.LdifUsersTable
;
25 import org
.argeo
.naming
.LdapAttrs
;
26 import org
.argeo
.naming
.LdapObjs
;
27 import org
.argeo
.node
.NodeConstants
;
28 import org
.eclipse
.jface
.dialogs
.IPageChangeProvider
;
29 import org
.eclipse
.jface
.dialogs
.IPageChangedListener
;
30 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
31 import org
.eclipse
.jface
.dialogs
.PageChangedEvent
;
32 import org
.eclipse
.jface
.wizard
.IWizardContainer
;
33 import org
.eclipse
.jface
.wizard
.Wizard
;
34 import org
.eclipse
.jface
.wizard
.WizardPage
;
35 import org
.eclipse
.swt
.SWT
;
36 import org
.eclipse
.swt
.events
.ModifyEvent
;
37 import org
.eclipse
.swt
.events
.ModifyListener
;
38 import org
.eclipse
.swt
.events
.SelectionAdapter
;
39 import org
.eclipse
.swt
.events
.SelectionEvent
;
40 import org
.eclipse
.swt
.layout
.GridData
;
41 import org
.eclipse
.swt
.layout
.GridLayout
;
42 import org
.eclipse
.swt
.widgets
.Button
;
43 import org
.eclipse
.swt
.widgets
.Combo
;
44 import org
.eclipse
.swt
.widgets
.Composite
;
45 import org
.eclipse
.swt
.widgets
.Text
;
46 import org
.osgi
.framework
.InvalidSyntaxException
;
47 import org
.osgi
.service
.useradmin
.Role
;
48 import org
.osgi
.service
.useradmin
.User
;
49 import org
.osgi
.service
.useradmin
.UserAdminEvent
;
51 /** Wizard to update users */
52 public class UserBatchUpdateWizard
extends Wizard
{
54 private final static Log log
= LogFactory
.getLog(UserBatchUpdateWizard
.class);
55 private UserAdminWrapper userAdminWrapper
;
58 private ChooseCommandWizardPage chooseCommandPage
;
59 private ChooseUsersWizardPage userListPage
;
60 private ValidateAndLaunchWizardPage validatePage
;
62 // Various implemented commands keys
63 private final static String CMD_UPDATE_PASSWORD
= "resetPassword";
64 private final static String CMD_UPDATE_EMAIL
= "resetEmail";
65 private final static String CMD_GROUP_MEMBERSHIP
= "groupMembership";
67 private final Map
<String
, String
> commands
= new HashMap
<String
, String
>() {
68 private static final long serialVersionUID
= 1L;
70 put("Reset password(s)", CMD_UPDATE_PASSWORD
);
71 put("Reset email(s)", CMD_UPDATE_EMAIL
);
72 // TODO implement role / group management
73 // put("Add/Remove from group", CMD_GROUP_MEMBERSHIP);
77 public UserBatchUpdateWizard(UserAdminWrapper userAdminWrapper
) {
78 this.userAdminWrapper
= userAdminWrapper
;
82 public void addPages() {
83 chooseCommandPage
= new ChooseCommandWizardPage();
84 addPage(chooseCommandPage
);
85 userListPage
= new ChooseUsersWizardPage();
86 addPage(userListPage
);
87 validatePage
= new ValidateAndLaunchWizardPage();
88 addPage(validatePage
);
92 public boolean performFinish() {
95 UserTransaction ut
= userAdminWrapper
.getUserTransaction();
97 if (ut
.getStatus() != javax
.transaction
.Status
.STATUS_NO_TRANSACTION
98 && !MessageDialog
.openConfirm(getShell(), "Existing Transaction",
99 "A user transaction is already existing, " + "are you sure you want to proceed ?"))
101 } catch (SystemException e
) {
102 throw new CmsException("Cannot get user transaction state " + "before user batch update", e
);
105 // We cannot use jobs, user modifications are still meant to be done in
107 // UpdateJob job = null;
111 if (CMD_UPDATE_PASSWORD
.equals(chooseCommandPage
.getCommand())) {
112 char[] newValue
= chooseCommandPage
.getPwdValue();
113 if (newValue
== null)
114 throw new CmsException("Password cannot be null or an empty string");
115 ResetPassword job
= new ResetPassword(userAdminWrapper
, userListPage
.getSelectedUsers(), newValue
);
117 } else if (CMD_UPDATE_EMAIL
.equals(chooseCommandPage
.getCommand())) {
118 String newValue
= chooseCommandPage
.getEmailValue();
119 if (newValue
== null)
120 throw new CmsException("Password cannot be null or an empty string");
121 ResetEmail job
= new ResetEmail(userAdminWrapper
, userListPage
.getSelectedUsers(), newValue
);
127 public boolean canFinish() {
128 if (this.getContainer().getCurrentPage() == validatePage
)
133 private class ResetPassword
{
134 private char[] newPwd
;
135 private UserAdminWrapper userAdminWrapper
;
136 private List
<User
> usersToUpdate
;
138 public ResetPassword(UserAdminWrapper userAdminWrapper
, List
<User
> usersToUpdate
, char[] newPwd
) {
139 this.newPwd
= newPwd
;
140 this.usersToUpdate
= usersToUpdate
;
141 this.userAdminWrapper
= userAdminWrapper
;
144 @SuppressWarnings("unchecked")
145 protected void doUpdate() {
146 userAdminWrapper
.beginTransactionIfNeeded();
148 for (User user
: usersToUpdate
) {
149 // the char array is emptied after being used.
150 user
.getCredentials().put(null, newPwd
.clone());
152 userAdminWrapper
.commitOrNotifyTransactionStateChange();
153 } catch (Exception e
) {
154 throw new CmsException("Cannot perform batch update on users", e
);
156 UserTransaction ut
= userAdminWrapper
.getUserTransaction();
158 if (ut
.getStatus() != javax
.transaction
.Status
.STATUS_NO_TRANSACTION
)
160 } catch (IllegalStateException
| SecurityException
| SystemException e
) {
161 log
.error("Unable to rollback session in 'finally', " + "the system might be in a dirty state");
168 private class ResetEmail
{
169 private String newEmail
;
170 private UserAdminWrapper userAdminWrapper
;
171 private List
<User
> usersToUpdate
;
173 public ResetEmail(UserAdminWrapper userAdminWrapper
, List
<User
> usersToUpdate
, String newEmail
) {
174 this.newEmail
= newEmail
;
175 this.usersToUpdate
= usersToUpdate
;
176 this.userAdminWrapper
= userAdminWrapper
;
179 @SuppressWarnings("unchecked")
180 protected void doUpdate() {
181 userAdminWrapper
.beginTransactionIfNeeded();
183 for (User user
: usersToUpdate
) {
184 // the char array is emptied after being used.
185 user
.getProperties().put(LdapAttrs
.mail
.name(), newEmail
);
188 userAdminWrapper
.commitOrNotifyTransactionStateChange();
189 if (!usersToUpdate
.isEmpty())
190 userAdminWrapper
.notifyListeners(
191 new UserAdminEvent(null, UserAdminEvent
.ROLE_CHANGED
, usersToUpdate
.get(0)));
192 } catch (Exception e
) {
193 throw new CmsException("Cannot perform batch update on users", e
);
195 UserTransaction ut
= userAdminWrapper
.getUserTransaction();
197 if (ut
.getStatus() != javax
.transaction
.Status
.STATUS_NO_TRANSACTION
)
199 } catch (IllegalStateException
| SecurityException
| SystemException e
) {
200 log
.error("Unable to rollback session in finally block, the system might be in a dirty state");
207 // @SuppressWarnings("unused")
208 // private class AddToGroup extends UpdateJob {
209 // private String groupID;
210 // private Session session;
212 // public AddToGroup(Session session, List<Node> nodesToUpdate,
214 // super(session, nodesToUpdate);
215 // this.session = session;
216 // this.groupID = groupID;
219 // protected void doUpdate(Node node) {
220 // log.info("Add/Remove to group actions are not yet implemented");
221 // // TODO implement this
223 // // throw new CmsException("Not yet implemented");
224 // // } catch (RepositoryException re) {
225 // // throw new CmsException(
226 // // "Unable to update boolean value for node " + node, re);
232 // * Base privileged job that will be run asynchronously to perform the
236 // private abstract class UpdateJob extends PrivilegedJob {
238 // private final UserAdminWrapper userAdminWrapper;
239 // private final List<User> usersToUpdate;
241 // protected abstract void doUpdate(User user);
243 // public UpdateJob(UserAdminWrapper userAdminWrapper,
244 // List<User> usersToUpdate) {
245 // super("Perform update");
246 // this.usersToUpdate = usersToUpdate;
247 // this.userAdminWrapper = userAdminWrapper;
251 // protected IStatus doRun(IProgressMonitor progressMonitor) {
253 // JcrMonitor monitor = new EclipseJcrMonitor(progressMonitor);
254 // int total = usersToUpdate.size();
255 // monitor.beginTask("Performing change", total);
256 // userAdminWrapper.beginTransactionIfNeeded();
257 // for (User user : usersToUpdate) {
259 // monitor.worked(1);
261 // userAdminWrapper.getUserTransaction().commit();
262 // } catch (Exception e) {
263 // throw new CmsException(
264 // "Cannot perform batch update on users", e);
266 // UserTransaction ut = userAdminWrapper.getUserTransaction();
268 // if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION)
270 // } catch (IllegalStateException | SecurityException
271 // | SystemException e) {
272 // log.error("Unable to rollback session in 'finally', "
273 // + "the system might be in a dirty state");
274 // e.printStackTrace();
277 // return Status.OK_STATUS;
283 * Displays a combo box that enables user to choose which action to perform
285 private class ChooseCommandWizardPage
extends WizardPage
{
286 private static final long serialVersionUID
= -8069434295293996633L;
287 private Combo chooseCommandCmb
;
288 private Button trueChk
;
289 private Text valueTxt
;
291 private Text pwd2Txt
;
293 public ChooseCommandWizardPage() {
294 super("Choose a command to run.");
295 setTitle("Choose a command to run.");
299 public void createControl(Composite parent
) {
300 GridLayout gl
= new GridLayout();
301 Composite container
= new Composite(parent
, SWT
.NO_FOCUS
);
302 container
.setLayout(gl
);
304 chooseCommandCmb
= new Combo(container
, SWT
.READ_ONLY
);
305 chooseCommandCmb
.setLayoutData(EclipseUiUtils
.fillWidth());
306 String
[] values
= commands
.keySet().toArray(new String
[0]);
307 chooseCommandCmb
.setItems(values
);
309 final Composite bottomPart
= new Composite(container
, SWT
.NO_FOCUS
);
310 bottomPart
.setLayoutData(EclipseUiUtils
.fillAll());
311 bottomPart
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
313 chooseCommandCmb
.addSelectionListener(new SelectionAdapter() {
314 private static final long serialVersionUID
= 1L;
317 public void widgetSelected(SelectionEvent e
) {
318 if (getCommand().equals(CMD_UPDATE_PASSWORD
))
319 populatePasswordCmp(bottomPart
);
320 else if (getCommand().equals(CMD_UPDATE_EMAIL
))
321 populateEmailCmp(bottomPart
);
322 else if (getCommand().equals(CMD_GROUP_MEMBERSHIP
))
323 populateGroupCmp(bottomPart
);
325 populateBooleanFlagCmp(bottomPart
);
327 bottomPart
.layout(true, true);
330 setControl(container
);
333 private void populateBooleanFlagCmp(Composite parent
) {
334 EclipseUiUtils
.clear(parent
);
335 trueChk
= new Button(parent
, SWT
.CHECK
);
336 trueChk
.setText("Do it. (It will to the contrary if unchecked)");
337 trueChk
.setSelection(true);
338 trueChk
.setLayoutData(new GridData(SWT
.LEFT
, SWT
.TOP
, false, false));
341 private void populatePasswordCmp(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
= -1558726363536729634L;
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 pwdTxt
= EclipseUiUtils
.createGridLP(body
, "New password", ml
);
357 pwd2Txt
= EclipseUiUtils
.createGridLP(body
, "Repeat password", ml
);
360 private void populateEmailCmp(Composite parent
) {
361 EclipseUiUtils
.clear(parent
);
362 Composite body
= new Composite(parent
, SWT
.NO_FOCUS
);
364 ModifyListener ml
= new ModifyListener() {
365 private static final long serialVersionUID
= 2147704227294268317L;
368 public void modifyText(ModifyEvent event
) {
373 body
.setLayout(new GridLayout(2, false));
374 body
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
375 valueTxt
= EclipseUiUtils
.createGridLT(body
, "New e-mail", ml
);
378 private void checkPageComplete() {
379 String errorMsg
= null;
380 if (chooseCommandCmb
.getSelectionIndex() < 0)
381 errorMsg
= "Please select an action";
382 else if (CMD_UPDATE_EMAIL
.equals(getCommand())) {
383 if (!valueTxt
.getText().matches(UiAdminUtils
.EMAIL_PATTERN
))
384 errorMsg
= "Not a valid e-mail address";
385 } else if (CMD_UPDATE_PASSWORD
.equals(getCommand())) {
386 if (EclipseUiUtils
.isEmpty(pwdTxt
.getText()) || pwdTxt
.getText().length() < 4)
387 errorMsg
= "Please enter a password that is at least 4 character long";
388 else if (!pwdTxt
.getText().equals(pwd2Txt
.getText()))
389 errorMsg
= "Passwords are different";
391 if (EclipseUiUtils
.notEmpty(errorMsg
)) {
392 setMessage(errorMsg
, WizardPage
.ERROR
);
393 setPageComplete(false);
395 setMessage("Page complete, you can proceed to user choice", WizardPage
.INFORMATION
);
396 setPageComplete(true);
399 getContainer().updateButtons();
402 private void populateGroupCmp(Composite parent
) {
403 EclipseUiUtils
.clear(parent
);
404 trueChk
= new Button(parent
, SWT
.CHECK
);
405 trueChk
.setText("Add to group. (It will remove user(s) from the " + "corresponding group if unchecked)");
406 trueChk
.setSelection(true);
407 trueChk
.setLayoutData(new GridData(SWT
.LEFT
, SWT
.TOP
, false, false));
410 protected String
getCommand() {
411 return commands
.get(chooseCommandCmb
.getItem(chooseCommandCmb
.getSelectionIndex()));
414 protected String
getCommandLbl() {
415 return chooseCommandCmb
.getItem(chooseCommandCmb
.getSelectionIndex());
418 @SuppressWarnings("unused")
419 protected boolean getBoleanValue() {
420 // FIXME this is not consistent and will lead to errors.
421 if ("argeo:enabled".equals(getCommand()))
422 return trueChk
.getSelection();
424 return !trueChk
.getSelection();
427 @SuppressWarnings("unused")
428 protected String
getStringValue() {
430 if (valueTxt
!= null) {
431 value
= valueTxt
.getText();
432 if ("".equals(value
.trim()))
438 protected char[] getPwdValue() {
439 // We do not directly reset the password text fields: There is no
440 // need to over secure this process: setting a pwd to multi users
441 // at the same time is anyhow a bad practice and should be used only
442 // in test environment or for temporary access
443 if (pwdTxt
== null || pwdTxt
.isDisposed())
446 return pwdTxt
.getText().toCharArray();
449 protected String
getEmailValue() {
450 // We do not directly reset the password text fields: There is no
451 // need to over secure this process: setting a pwd to multi users
452 // at the same time is anyhow a bad practice and should be used only
453 // in test environment or for temporary access
454 if (valueTxt
== null || valueTxt
.isDisposed())
457 return valueTxt
.getText();
462 * Displays a list of users with a check box to be able to choose some of
465 private class ChooseUsersWizardPage
extends WizardPage
implements IPageChangedListener
{
466 private static final long serialVersionUID
= 7651807402211214274L;
467 private ChooseUserTableViewer userTableCmp
;
469 public ChooseUsersWizardPage() {
470 super("Choose Users");
471 setTitle("Select users who will be impacted");
475 public void createControl(Composite parent
) {
476 Composite pageCmp
= new Composite(parent
, SWT
.NONE
);
477 pageCmp
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
479 // Define the displayed columns
480 List
<ColumnDefinition
> columnDefs
= new ArrayList
<ColumnDefinition
>();
481 columnDefs
.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150));
482 columnDefs
.add(new ColumnDefinition(new MailLP(), "E-mail", 150));
483 columnDefs
.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200));
485 // Only show technical DN to admin
486 if (CurrentUser
.isInRole(NodeConstants
.ROLE_ADMIN
))
487 columnDefs
.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300));
489 userTableCmp
= new ChooseUserTableViewer(pageCmp
, SWT
.MULTI
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
490 userTableCmp
.setLayoutData(EclipseUiUtils
.fillAll());
491 userTableCmp
.setColumnDefinitions(columnDefs
);
492 userTableCmp
.populate(true, true);
493 userTableCmp
.refresh();
497 // Add listener to update message when shown
498 final IWizardContainer wContainer
= this.getContainer();
499 if (wContainer
instanceof IPageChangeProvider
) {
500 ((IPageChangeProvider
) wContainer
).addPageChangedListener(this);
506 public void pageChanged(PageChangedEvent event
) {
507 if (event
.getSelectedPage() == this) {
508 String msg
= "Chosen batch action: " + chooseCommandPage
.getCommandLbl();
509 ((WizardPage
) event
.getSelectedPage()).setMessage(msg
);
513 protected List
<User
> getSelectedUsers() {
514 return userTableCmp
.getSelectedUsers();
517 private class ChooseUserTableViewer
extends LdifUsersTable
{
518 private static final long serialVersionUID
= 5080437561015853124L;
519 private final String
[] knownProps
= { LdapAttrs
.uid
.name(), LdapAttrs
.DN
, LdapAttrs
.cn
.name(),
520 LdapAttrs
.givenName
.name(), LdapAttrs
.sn
.name(), LdapAttrs
.mail
.name() };
522 public ChooseUserTableViewer(Composite parent
, int style
) {
523 super(parent
, style
);
527 protected List
<User
> listFilteredElements(String filter
) {
531 StringBuilder builder
= new StringBuilder();
533 StringBuilder tmpBuilder
= new StringBuilder();
534 if (EclipseUiUtils
.notEmpty(filter
))
535 for (String prop
: knownProps
) {
536 tmpBuilder
.append("(");
537 tmpBuilder
.append(prop
);
538 tmpBuilder
.append("=*");
539 tmpBuilder
.append(filter
);
540 tmpBuilder
.append("*)");
542 if (tmpBuilder
.length() > 1) {
543 builder
.append("(&(").append(LdapAttrs
.objectClass
.name()).append("=")
544 .append(LdapObjs
.inetOrgPerson
.name()).append(")(|");
545 builder
.append(tmpBuilder
.toString());
546 builder
.append("))");
548 builder
.append("(").append(LdapAttrs
.objectClass
.name()).append("=")
549 .append(LdapObjs
.inetOrgPerson
.name()).append(")");
550 roles
= userAdminWrapper
.getUserAdmin().getRoles(builder
.toString());
551 } catch (InvalidSyntaxException e
) {
552 throw new CmsException("Unable to get roles with filter: " + filter
, e
);
554 List
<User
> users
= new ArrayList
<User
>();
555 for (Role role
: roles
)
556 // Prevent current logged in user to perform batch on
558 if (!UserAdminUtils
.isCurrentUser((User
) role
))
559 users
.add((User
) role
);
565 /** Summary of input data before launching the process */
566 private class ValidateAndLaunchWizardPage
extends WizardPage
implements IPageChangedListener
{
567 private static final long serialVersionUID
= 7098918351451743853L;
568 private ChosenUsersTableViewer userTableCmp
;
570 public ValidateAndLaunchWizardPage() {
571 super("Validate and launch");
572 setTitle("Validate and launch");
576 public void createControl(Composite parent
) {
577 Composite pageCmp
= new Composite(parent
, SWT
.NO_FOCUS
);
578 pageCmp
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
580 List
<ColumnDefinition
> columnDefs
= new ArrayList
<ColumnDefinition
>();
581 columnDefs
.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150));
582 columnDefs
.add(new ColumnDefinition(new MailLP(), "E-mail", 150));
583 columnDefs
.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200));
584 // Only show technical DN to admin
585 if (CurrentUser
.isInRole(NodeConstants
.ROLE_ADMIN
))
586 columnDefs
.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300));
587 userTableCmp
= new ChosenUsersTableViewer(pageCmp
, SWT
.MULTI
| SWT
.H_SCROLL
| SWT
.V_SCROLL
);
588 userTableCmp
.setLayoutData(EclipseUiUtils
.fillAll());
589 userTableCmp
.setColumnDefinitions(columnDefs
);
590 userTableCmp
.populate(false, false);
591 userTableCmp
.refresh();
593 // Add listener to update message when shown
594 final IWizardContainer wContainer
= this.getContainer();
595 if (wContainer
instanceof IPageChangeProvider
) {
596 ((IPageChangeProvider
) wContainer
).addPageChangedListener(this);
601 public void pageChanged(PageChangedEvent event
) {
602 if (event
.getSelectedPage() == this) {
603 @SuppressWarnings({ "unchecked", "rawtypes" })
604 Object
[] values
= ((ArrayList
) userListPage
.getSelectedUsers())
605 .toArray(new Object
[userListPage
.getSelectedUsers().size()]);
606 userTableCmp
.getTableViewer().setInput(values
);
607 String msg
= "Following batch action: [" + chooseCommandPage
.getCommandLbl()
608 + "] will be perfomed on the users listed below.\n";
609 // + "Are you sure you want to proceed?";
614 private class ChosenUsersTableViewer
extends LdifUsersTable
{
615 private static final long serialVersionUID
= 7814764735794270541L;
617 public ChosenUsersTableViewer(Composite parent
, int style
) {
618 super(parent
, style
);
622 protected List
<User
> listFilteredElements(String filter
) {
623 return userListPage
.getSelectedUsers();