2 * Copyright (C) 2007-2012 Argeo GmbH
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org
.argeo
.security
.ui
.admin
.wizards
;
18 import java
.util
.ArrayList
;
19 import java
.util
.HashMap
;
20 import java
.util
.List
;
23 import javax
.jcr
.Node
;
24 import javax
.jcr
.NodeIterator
;
25 import javax
.jcr
.RepositoryException
;
26 import javax
.jcr
.Session
;
27 import javax
.jcr
.version
.VersionManager
;
29 import org
.apache
.commons
.logging
.Log
;
30 import org
.apache
.commons
.logging
.LogFactory
;
31 import org
.argeo
.ArgeoException
;
32 import org
.argeo
.ArgeoMonitor
;
33 import org
.argeo
.eclipse
.ui
.EclipseArgeoMonitor
;
34 import org
.argeo
.jcr
.ArgeoNames
;
35 import org
.argeo
.jcr
.JcrUtils
;
36 import org
.argeo
.security
.UserAdminService
;
37 import org
.argeo
.security
.jcr
.JcrSecurityModel
;
38 import org
.argeo
.security
.jcr
.JcrUserDetails
;
39 import org
.argeo
.security
.ui
.PrivilegedJob
;
40 import org
.argeo
.security
.ui
.admin
.SecurityAdminPlugin
;
41 import org
.argeo
.security
.ui
.admin
.UserTableComposite
;
42 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
43 import org
.eclipse
.core
.runtime
.IStatus
;
44 import org
.eclipse
.core
.runtime
.Status
;
45 import org
.eclipse
.jface
.dialogs
.IPageChangeProvider
;
46 import org
.eclipse
.jface
.dialogs
.IPageChangedListener
;
47 import org
.eclipse
.jface
.dialogs
.PageChangedEvent
;
48 import org
.eclipse
.jface
.wizard
.IWizardContainer
;
49 import org
.eclipse
.jface
.wizard
.Wizard
;
50 import org
.eclipse
.jface
.wizard
.WizardPage
;
51 import org
.eclipse
.swt
.SWT
;
52 import org
.eclipse
.swt
.events
.SelectionEvent
;
53 import org
.eclipse
.swt
.events
.SelectionListener
;
54 import org
.eclipse
.swt
.layout
.FillLayout
;
55 import org
.eclipse
.swt
.layout
.GridData
;
56 import org
.eclipse
.swt
.layout
.GridLayout
;
57 import org
.eclipse
.swt
.widgets
.Button
;
58 import org
.eclipse
.swt
.widgets
.Combo
;
59 import org
.eclipse
.swt
.widgets
.Composite
;
60 import org
.eclipse
.swt
.widgets
.Control
;
61 import org
.eclipse
.swt
.widgets
.Label
;
62 import org
.eclipse
.swt
.widgets
.Text
;
64 /** Wizard to update users */
65 public class UserBatchUpdateWizard
extends Wizard
{
66 private final static Log log
= LogFactory
67 .getLog(UserBatchUpdateWizard
.class);
68 private Session session
;
69 private UserAdminService userAdminService
;
72 private ChooseCommandWizardPage chooseCommandPage
;
73 private ChooseUsersWizardPage userListPage
;
74 private ValidateAndLaunchWizardPage validatePage
;
76 // /////////////////////////////////////////////////
77 // / Definition of the various implemented commands
78 private final static String CMD_UPDATE_PASSWORD
= "resetPassword";
79 private final static String CMD_GROUP_MEMBERSHIP
= "groupMembership";
81 private final Map
<String
, String
> commands
= new HashMap
<String
, String
>() {
82 private static final long serialVersionUID
= 1L;
84 put("Enable user(s)", ArgeoNames
.ARGEO_ENABLED
);
85 put("Expire credentials", ArgeoNames
.ARGEO_CREDENTIALS_NON_EXPIRED
);
86 put("Expire account(s)", ArgeoNames
.ARGEO_ACCOUNT_NON_EXPIRED
);
87 put("Lock account(s)", ArgeoNames
.ARGEO_ACCOUNT_NON_LOCKED
);
88 put("Reset password(s)", CMD_UPDATE_PASSWORD
);
89 // TODO implement role / group management
90 // put("Add/Remove from group", CMD_GROUP_MEMBERSHIP);
94 public UserBatchUpdateWizard(Session session
,
95 UserAdminService userAdminService
, JcrSecurityModel jcrSecurityModel
) {
96 this.session
= session
;
97 this.userAdminService
= userAdminService
;
98 // this.jcrSecurityModel = jcrSecurityModel;
102 public void addPages() {
103 chooseCommandPage
= new ChooseCommandWizardPage();
104 addPage(chooseCommandPage
);
105 userListPage
= new ChooseUsersWizardPage(session
);
106 addPage(userListPage
);
107 validatePage
= new ValidateAndLaunchWizardPage(session
);
108 addPage(validatePage
);
112 public boolean performFinish() {
116 UpdateJob job
= null;
117 if (ArgeoNames
.ARGEO_ENABLED
.equals(chooseCommandPage
.getCommand())) {
118 job
= new UpdateBoolean(session
, userListPage
.getSelectedUsers(),
119 ArgeoNames
.ARGEO_ENABLED
,
120 chooseCommandPage
.getBoleanValue());
121 } else if (ArgeoNames
.ARGEO_CREDENTIALS_NON_EXPIRED
122 .equals(chooseCommandPage
.getCommand())) {
123 job
= new UpdateBoolean(session
, userListPage
.getSelectedUsers(),
124 ArgeoNames
.ARGEO_CREDENTIALS_NON_EXPIRED
,
125 chooseCommandPage
.getBoleanValue());
126 } else if (ArgeoNames
.ARGEO_ACCOUNT_NON_EXPIRED
127 .equals(chooseCommandPage
.getCommand())) {
128 job
= new UpdateBoolean(session
, userListPage
.getSelectedUsers(),
129 ArgeoNames
.ARGEO_ACCOUNT_NON_EXPIRED
,
130 chooseCommandPage
.getBoleanValue());
131 } else if (ArgeoNames
.ARGEO_ACCOUNT_NON_LOCKED
.equals(chooseCommandPage
133 job
= new UpdateBoolean(session
, userListPage
.getSelectedUsers(),
134 ArgeoNames
.ARGEO_ACCOUNT_NON_LOCKED
,
135 chooseCommandPage
.getBoleanValue());
136 } else if (CMD_UPDATE_PASSWORD
.equals(chooseCommandPage
.getCommand())) {
137 String newValue
= chooseCommandPage
.getPwdValue();
138 if (newValue
== null)
139 throw new ArgeoException(
140 "Password cannot be null or an empty string");
141 job
= new ResetPassword(session
, userAdminService
,
142 userListPage
.getSelectedUsers(), newValue
);
150 public void setSession(Session session
) {
151 this.session
= session
;
154 public boolean canFinish() {
155 if (this.getContainer().getCurrentPage() == validatePage
)
160 // /////////////////////////
162 private class UpdateBoolean
extends UpdateJob
{
163 private String propertyName
;
164 private boolean value
;
166 public UpdateBoolean(Session session
, List
<Node
> nodesToUpdate
,
167 String propertyName
, boolean value
) {
168 super(session
, nodesToUpdate
);
169 this.propertyName
= propertyName
;
173 protected void doUpdate(Node node
) {
175 node
.setProperty(propertyName
, value
);
176 } catch (RepositoryException re
) {
177 throw new ArgeoException(
178 "Unable to update boolean value for node " + node
, re
);
183 private class ResetPassword
extends UpdateJob
{
184 private String newValue
;
185 private UserAdminService userAdminService
;
187 public ResetPassword(Session session
,
188 UserAdminService userAdminService
, List
<Node
> nodesToUpdate
,
190 super(session
, nodesToUpdate
);
191 this.newValue
= newValue
;
192 this.userAdminService
= userAdminService
;
195 protected void doUpdate(Node node
) {
197 String userId
= node
.getProperty(ArgeoNames
.ARGEO_USER_ID
)
199 if (userAdminService
.userExists(userId
)) {
200 JcrUserDetails userDetails
= (JcrUserDetails
) userAdminService
201 .loadUserByUsername(userId
);
202 userAdminService
.updateUser(userDetails
203 .cloneWithNewPassword(newValue
));
205 } catch (RepositoryException re
) {
206 throw new ArgeoException(
207 "Unable to update boolean value for node " + node
, re
);
212 @SuppressWarnings("unused")
213 private class AddToGroup
extends UpdateJob
{
214 private String groupID
;
215 private Session session
;
217 public AddToGroup(Session session
, List
<Node
> nodesToUpdate
,
219 super(session
, nodesToUpdate
);
220 this.session
= session
;
221 this.groupID
= groupID
;
224 protected void doUpdate(Node node
) {
225 log
.info("Add/Remove to group actions are not yet implemented");
226 // TODO implement this
228 // throw new ArgeoException("Not yet implemented");
229 // } catch (RepositoryException re) {
230 // throw new ArgeoException(
231 // "Unable to update boolean value for node " + node, re);
237 * Base privileged job that will be run asynchronously to perform the batch
240 private abstract class UpdateJob
extends PrivilegedJob
{
242 private final Session currSession
;
243 private final List
<Node
> nodesToUpdate
;
245 protected abstract void doUpdate(Node node
);
247 public UpdateJob(Session session
, List
<Node
> nodesToUpdate
) {
248 super("Perform update");
250 this.currSession
= session
.getRepository().login();
251 // "move" nodes to update in the new session
252 // the "old" session will be closed by the calling command
253 // before the job has effectively ran
254 // TODO there must be a cleaner way to do.
255 List
<Node
> nodes
= new ArrayList
<Node
>();
256 for (Node node
: nodesToUpdate
) {
257 nodes
.add(currSession
.getNode(node
.getPath()));
259 this.nodesToUpdate
= nodes
;
260 } catch (RepositoryException e
) {
261 throw new ArgeoException("Error while dupplicating "
262 + "session for job", e
);
267 protected IStatus
doRun(IProgressMonitor progressMonitor
) {
269 ArgeoMonitor monitor
= new EclipseArgeoMonitor(progressMonitor
);
270 VersionManager vm
= currSession
.getWorkspace()
271 .getVersionManager();
272 int total
= nodesToUpdate
.size();
273 monitor
.beginTask("Performing change", total
);
274 for (Node node
: nodesToUpdate
) {
275 String path
= node
.getPath();
282 } catch (Exception e
) {
283 log
.error("Cannot perform batch update on users", e
);
284 // e.printStackTrace();
286 // Dig exception to find the root cause that will enable the
287 // user to understand the problem
289 Throwable originalCause
= e
;
290 while (cause
!= null) {
291 if (log
.isTraceEnabled())
292 log
.trace("Parent Cause message : "
293 + cause
.getMessage());
294 originalCause
= cause
;
295 cause
= cause
.getCause();
297 return new Status(IStatus
.ERROR
, SecurityAdminPlugin
.PLUGIN_ID
,
298 "Cannot perform updates.", originalCause
);
300 JcrUtils
.logoutQuietly(currSession
);
302 return Status
.OK_STATUS
;
306 // //////////////////////
308 /** Displays a combo box that enables user to choose which action to perform */
309 private class ChooseCommandWizardPage
extends WizardPage
{
310 private static final long serialVersionUID
= 1L;
312 private Combo chooseCommandCmb
;
313 private Button trueChk
;
314 private Text valueTxt
;
316 private Text pwd2Txt
;
318 public ChooseCommandWizardPage() {
319 super("Choose a command to run.");
320 setTitle("Choose a command to run.");
324 public void createControl(Composite parent
) {
325 GridLayout gl
= new GridLayout();
326 Composite container
= new Composite(parent
, SWT
.NO_FOCUS
);
327 container
.setLayout(gl
);
329 chooseCommandCmb
= new Combo(container
, SWT
.NO_FOCUS
);
330 String
[] values
= commands
.keySet().toArray(
331 new String
[commands
.size()]);
332 chooseCommandCmb
.setItems(values
);
333 chooseCommandCmb
.setLayoutData(new GridData(SWT
.FILL
, SWT
.TOP
,
336 final Composite bottomPart
= new Composite(container
, SWT
.NO_FOCUS
);
337 gl
= new GridLayout();
338 gl
.horizontalSpacing
= gl
.marginWidth
= gl
.verticalSpacing
= 0;
339 bottomPart
.setLayout(gl
);
340 bottomPart
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true,
343 chooseCommandCmb
.addSelectionListener(new SelectionListener() {
344 private static final long serialVersionUID
= 1L;
347 public void widgetSelected(SelectionEvent e
) {
348 if (getCommand().equals(CMD_UPDATE_PASSWORD
))
349 populatePasswordCmp(bottomPart
);
350 else if (getCommand().equals(CMD_GROUP_MEMBERSHIP
))
351 populateGroupCmp(bottomPart
);
353 populateBooleanFlagCmp(bottomPart
);
354 bottomPart
.pack(true);
359 public void widgetDefaultSelected(SelectionEvent e
) {
363 setControl(container
);
366 private void cleanParent(Composite parent
) {
367 if (parent
.getChildren().length
> 0) {
368 for (Control control
: parent
.getChildren())
373 private void populateBooleanFlagCmp(Composite parent
) {
375 trueChk
= new Button(parent
, SWT
.CHECK
);
376 trueChk
.setText("Do it. (It will to the contrary if unchecked)");
377 trueChk
.setSelection(true);
378 trueChk
.setLayoutData(new GridData(SWT
.LEFT
, SWT
.TOP
, false, false));
381 private void populatePasswordCmp(Composite parent
) {
383 Composite body
= new Composite(parent
, SWT
.NO_FOCUS
);
384 body
.setLayout(new GridLayout(2, false));
385 body
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
386 pwdTxt
= createLP(body
, "New password", "");
387 pwd2Txt
= createLP(body
, "Repeat password", "");
390 /** Creates label and password. */
391 protected Text
createLP(Composite body
, String label
, String value
) {
392 Label lbl
= new Label(body
, SWT
.NONE
);
393 lbl
.setLayoutData(new GridData(SWT
.RIGHT
, SWT
.CENTER
, false, false));
395 Text text
= new Text(body
, SWT
.BORDER
| SWT
.PASSWORD
);
397 text
.setLayoutData(new GridData(SWT
.FILL
, SWT
.CENTER
, true, false));
401 private void populateGroupCmp(Composite parent
) {
402 if (parent
.getChildren().length
> 0) {
403 for (Control control
: parent
.getChildren())
406 trueChk
= new Button(parent
, SWT
.CHECK
);
407 trueChk
.setText("Add to group. (It will remove user(s) from the "
408 + "corresponding group if unchecked)");
409 trueChk
.setSelection(true);
410 trueChk
.setLayoutData(new GridData(SWT
.LEFT
, SWT
.TOP
, false, false));
413 protected String
getCommand() {
414 return commands
.get(chooseCommandCmb
.getItem(chooseCommandCmb
415 .getSelectionIndex()));
418 protected String
getCommandLbl() {
419 return chooseCommandCmb
.getItem(chooseCommandCmb
420 .getSelectionIndex());
423 protected boolean getBoleanValue() {
424 // FIXME this is not consistent and will lead to errors.
425 if (ArgeoNames
.ARGEO_ENABLED
.equals(getCommand()))
426 return trueChk
.getSelection();
428 return !trueChk
.getSelection();
431 @SuppressWarnings("unused")
432 protected String
getStringValue() {
434 if (valueTxt
!= null) {
435 value
= valueTxt
.getText();
436 if ("".equals(value
.trim()))
442 protected String
getPwdValue() {
443 String newPwd
= null;
444 if (pwdTxt
== null || pwd2Txt
== null)
446 if (!pwdTxt
.getText().equals("") || !pwd2Txt
.getText().equals("")) {
447 if (pwdTxt
.getText().equals(pwd2Txt
.getText())) {
448 newPwd
= pwdTxt
.getText();
454 throw new ArgeoException("Passwords are not equals");
462 * Displays a list of users with a check box to be able to choose some of
465 private class ChooseUsersWizardPage
extends WizardPage
implements
466 IPageChangedListener
{
467 private static final long serialVersionUID
= 1L;
468 private UserTableComposite userTableCmp
;
469 private Composite container
;
470 private Session session
;
472 public ChooseUsersWizardPage(Session session
) {
473 super("Choose Users");
474 this.session
= session
;
475 setTitle("Select users who will be impacted");
479 public void createControl(Composite parent
) {
480 container
= new Composite(parent
, SWT
.NONE
);
481 container
.setLayout(new FillLayout());
482 userTableCmp
= new MyUserTableCmp(container
, SWT
.NO_FOCUS
, session
);
483 userTableCmp
.populate(true, true);
484 setControl(container
);
486 // Add listener to update message when shown
487 final IWizardContainer container
= this.getContainer();
488 if (container
instanceof IPageChangeProvider
) {
489 ((IPageChangeProvider
) container
).addPageChangedListener(this);
495 public void pageChanged(PageChangedEvent event
) {
496 if (event
.getSelectedPage() == this) {
497 String msg
= "Chosen batch action: "
498 + chooseCommandPage
.getCommandLbl();
499 ((WizardPage
) event
.getSelectedPage()).setMessage(msg
);
503 protected List
<Node
> getSelectedUsers() {
504 return userTableCmp
.getSelectedUsers();
507 private class MyUserTableCmp
extends UserTableComposite
{
509 private static final long serialVersionUID
= 1L;
511 public MyUserTableCmp(Composite parent
, int style
, Session session
) {
512 super(parent
, style
, session
);
516 protected void refreshFilteredList() {
517 List
<Node
> nodes
= new ArrayList
<Node
>();
519 NodeIterator ni
= listFilteredElements(session
,
522 users
: while (ni
.hasNext()) {
523 Node currNode
= ni
.nextNode();
524 String username
= currNode
.hasProperty(ARGEO_USER_ID
) ? currNode
525 .getProperty(ARGEO_USER_ID
).getString() : "";
526 if (username
.equals(session
.getUserID()))
531 getTableViewer().setInput(nodes
.toArray());
532 } catch (RepositoryException e
) {
533 throw new ArgeoException("Unable to list users", e
);
540 * Recapitulation of input data before running real update
542 private class ValidateAndLaunchWizardPage
extends WizardPage
implements
543 IPageChangedListener
{
544 private static final long serialVersionUID
= 1L;
545 private UserTableComposite userTableCmp
;
546 private Session session
;
548 public ValidateAndLaunchWizardPage(Session session
) {
549 super("Validate and launch");
550 this.session
= session
;
551 setTitle("Validate and launch");
555 public void createControl(Composite parent
) {
556 Composite mainCmp
= new Composite(parent
, SWT
.NO_FOCUS
);
557 mainCmp
.setLayout(new FillLayout());
559 // Add listener to update user list when shown
560 final IWizardContainer container
= this.getContainer();
561 if (container
instanceof IPageChangeProvider
) {
562 ((IPageChangeProvider
) container
).addPageChangedListener(this);
565 userTableCmp
= new UserTableComposite(mainCmp
, SWT
.NO_FOCUS
,
567 userTableCmp
.populate(false, false);
572 public void pageChanged(PageChangedEvent event
) {
573 if (event
.getSelectedPage() == this) {
574 @SuppressWarnings({ "unchecked", "rawtypes" })
575 Object
[] values
= ((ArrayList
) userListPage
.getSelectedUsers())
576 .toArray(new Object
[userListPage
.getSelectedUsers()
578 userTableCmp
.getTableViewer().setInput(values
);
579 String msg
= "Following batch action: ["
580 + chooseCommandPage
.getCommandLbl()
581 + "] will be perfomed on the users listed below.\n"
582 + "Are you sure you want to proceed?";
583 ((WizardPage
) event
.getSelectedPage()).setMessage(msg
);
587 // private class MyUserTableCmp extends UserTableComposite {
588 // public MyUserTableCmp(Composite parent, int style, Session session) {
589 // super(parent, style, session);
593 // protected void refreshFilteredList() {
594 // @SuppressWarnings({ "unchecked", "rawtypes" })
596 // setFilteredList(values);
600 // public void setVisible(boolean visible) {
601 // super.setVisible(visible);
603 // refreshFilteredList();