package org.argeo.cms.e4.users; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; import org.argeo.cms.CmsException; import org.argeo.cms.auth.CurrentUser; import org.argeo.cms.auth.UserAdminUtils; import org.argeo.cms.e4.users.providers.CommonNameLP; import org.argeo.cms.e4.users.providers.DomainNameLP; import org.argeo.cms.e4.users.providers.MailLP; import org.argeo.cms.e4.users.providers.UserNameLP; import org.argeo.eclipse.ui.ColumnDefinition; import org.argeo.eclipse.ui.EclipseUiUtils; import org.argeo.eclipse.ui.parts.LdifUsersTable; import org.argeo.osgi.transaction.WorkTransaction; import org.argeo.util.naming.LdapAttrs; import org.argeo.util.naming.LdapObjs; import org.eclipse.jface.dialogs.IPageChangeProvider; import org.eclipse.jface.dialogs.IPageChangedListener; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.PageChangedEvent; import org.eclipse.jface.wizard.IWizardContainer; import org.eclipse.jface.wizard.Wizard; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Text; import org.osgi.framework.InvalidSyntaxException; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; import org.osgi.service.useradmin.UserAdminEvent; /** Wizard to update users */ public class UserBatchUpdateWizard extends Wizard { private final static CmsLog log = CmsLog.getLog(UserBatchUpdateWizard.class); private UserAdminWrapper userAdminWrapper; // pages private ChooseCommandWizardPage chooseCommandPage; private ChooseUsersWizardPage userListPage; private ValidateAndLaunchWizardPage validatePage; // Various implemented commands keys private final static String CMD_UPDATE_PASSWORD = "resetPassword"; private final static String CMD_UPDATE_EMAIL = "resetEmail"; private final static String CMD_GROUP_MEMBERSHIP = "groupMembership"; private final Map commands = new HashMap() { private static final long serialVersionUID = 1L; { put("Reset password(s)", CMD_UPDATE_PASSWORD); put("Reset email(s)", CMD_UPDATE_EMAIL); // TODO implement role / group management // put("Add/Remove from group", CMD_GROUP_MEMBERSHIP); } }; public UserBatchUpdateWizard(UserAdminWrapper userAdminWrapper) { this.userAdminWrapper = userAdminWrapper; } @Override public void addPages() { chooseCommandPage = new ChooseCommandWizardPage(); addPage(chooseCommandPage); userListPage = new ChooseUsersWizardPage(); addPage(userListPage); validatePage = new ValidateAndLaunchWizardPage(); addPage(validatePage); } @Override public boolean performFinish() { if (!canFinish()) return false; WorkTransaction ut = userAdminWrapper.getUserTransaction(); if (!ut.isNoTransactionStatus() && !MessageDialog.openConfirm(getShell(), "Existing Transaction", "A user transaction is already existing, " + "are you sure you want to proceed ?")) return false; // We cannot use jobs, user modifications are still meant to be done in // the UIThread // UpdateJob job = null; // if (job != null) // job.schedule(); if (CMD_UPDATE_PASSWORD.equals(chooseCommandPage.getCommand())) { char[] newValue = chooseCommandPage.getPwdValue(); if (newValue == null) throw new CmsException("Password cannot be null or an empty string"); ResetPassword job = new ResetPassword(userAdminWrapper, userListPage.getSelectedUsers(), newValue); job.doUpdate(); } else if (CMD_UPDATE_EMAIL.equals(chooseCommandPage.getCommand())) { String newValue = chooseCommandPage.getEmailValue(); if (newValue == null) throw new CmsException("Password cannot be null or an empty string"); ResetEmail job = new ResetEmail(userAdminWrapper, userListPage.getSelectedUsers(), newValue); job.doUpdate(); } return true; } public boolean canFinish() { if (this.getContainer().getCurrentPage() == validatePage) return true; return false; } private class ResetPassword { private char[] newPwd; private UserAdminWrapper userAdminWrapper; private List usersToUpdate; public ResetPassword(UserAdminWrapper userAdminWrapper, List usersToUpdate, char[] newPwd) { this.newPwd = newPwd; this.usersToUpdate = usersToUpdate; this.userAdminWrapper = userAdminWrapper; } @SuppressWarnings("unchecked") protected void doUpdate() { userAdminWrapper.beginTransactionIfNeeded(); try { for (User user : usersToUpdate) { // the char array is emptied after being used. user.getCredentials().put(null, newPwd.clone()); } userAdminWrapper.commitOrNotifyTransactionStateChange(); } catch (Exception e) { throw new CmsException("Cannot perform batch update on users", e); } finally { WorkTransaction ut = userAdminWrapper.getUserTransaction(); if (!ut.isNoTransactionStatus()) ut.rollback(); } } } private class ResetEmail { private String newEmail; private UserAdminWrapper userAdminWrapper; private List usersToUpdate; public ResetEmail(UserAdminWrapper userAdminWrapper, List usersToUpdate, String newEmail) { this.newEmail = newEmail; this.usersToUpdate = usersToUpdate; this.userAdminWrapper = userAdminWrapper; } @SuppressWarnings("unchecked") protected void doUpdate() { userAdminWrapper.beginTransactionIfNeeded(); try { for (User user : usersToUpdate) { // the char array is emptied after being used. user.getProperties().put(LdapAttrs.mail.name(), newEmail); } userAdminWrapper.commitOrNotifyTransactionStateChange(); if (!usersToUpdate.isEmpty()) userAdminWrapper.notifyListeners( new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, usersToUpdate.get(0))); } catch (Exception e) { throw new CmsException("Cannot perform batch update on users", e); } finally { WorkTransaction ut = userAdminWrapper.getUserTransaction(); if (!ut.isNoTransactionStatus()) ut.rollback(); } } } // @SuppressWarnings("unused") // private class AddToGroup extends UpdateJob { // private String groupID; // private Session session; // // public AddToGroup(Session session, List nodesToUpdate, // String groupID) { // super(session, nodesToUpdate); // this.session = session; // this.groupID = groupID; // } // // protected void doUpdate(Node node) { // log.info("Add/Remove to group actions are not yet implemented"); // // TODO implement this // // try { // // throw new CmsException("Not yet implemented"); // // } catch (RepositoryException re) { // // throw new CmsException( // // "Unable to update boolean value for node " + node, re); // // } // } // } // /** // * Base privileged job that will be run asynchronously to perform the // batch // * update // */ // private abstract class UpdateJob extends PrivilegedJob { // // private final UserAdminWrapper userAdminWrapper; // private final List usersToUpdate; // // protected abstract void doUpdate(User user); // // public UpdateJob(UserAdminWrapper userAdminWrapper, // List usersToUpdate) { // super("Perform update"); // this.usersToUpdate = usersToUpdate; // this.userAdminWrapper = userAdminWrapper; // } // // @Override // protected IStatus doRun(IProgressMonitor progressMonitor) { // try { // JcrMonitor monitor = new EclipseJcrMonitor(progressMonitor); // int total = usersToUpdate.size(); // monitor.beginTask("Performing change", total); // userAdminWrapper.beginTransactionIfNeeded(); // for (User user : usersToUpdate) { // doUpdate(user); // monitor.worked(1); // } // userAdminWrapper.getUserTransaction().commit(); // } catch (Exception e) { // throw new CmsException( // "Cannot perform batch update on users", e); // } finally { // UserTransaction ut = userAdminWrapper.getUserTransaction(); // try { // if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION) // ut.rollback(); // } catch (IllegalStateException | SecurityException // | SystemException e) { // log.error("Unable to rollback session in 'finally', " // + "the system might be in a dirty state"); // e.printStackTrace(); // } // } // return Status.OK_STATUS; // } // } // PAGES /** * Displays a combo box that enables user to choose which action to perform */ private class ChooseCommandWizardPage extends WizardPage { private static final long serialVersionUID = -8069434295293996633L; private Combo chooseCommandCmb; private Button trueChk; private Text valueTxt; private Text pwdTxt; private Text pwd2Txt; public ChooseCommandWizardPage() { super("Choose a command to run."); setTitle("Choose a command to run."); } @Override public void createControl(Composite parent) { GridLayout gl = new GridLayout(); Composite container = new Composite(parent, SWT.NO_FOCUS); container.setLayout(gl); chooseCommandCmb = new Combo(container, SWT.READ_ONLY); chooseCommandCmb.setLayoutData(EclipseUiUtils.fillWidth()); String[] values = commands.keySet().toArray(new String[0]); chooseCommandCmb.setItems(values); final Composite bottomPart = new Composite(container, SWT.NO_FOCUS); bottomPart.setLayoutData(EclipseUiUtils.fillAll()); bottomPart.setLayout(EclipseUiUtils.noSpaceGridLayout()); chooseCommandCmb.addSelectionListener(new SelectionAdapter() { private static final long serialVersionUID = 1L; @Override public void widgetSelected(SelectionEvent e) { if (getCommand().equals(CMD_UPDATE_PASSWORD)) populatePasswordCmp(bottomPart); else if (getCommand().equals(CMD_UPDATE_EMAIL)) populateEmailCmp(bottomPart); else if (getCommand().equals(CMD_GROUP_MEMBERSHIP)) populateGroupCmp(bottomPart); else populateBooleanFlagCmp(bottomPart); checkPageComplete(); bottomPart.layout(true, true); } }); setControl(container); } private void populateBooleanFlagCmp(Composite parent) { EclipseUiUtils.clear(parent); trueChk = new Button(parent, SWT.CHECK); trueChk.setText("Do it. (It will to the contrary if unchecked)"); trueChk.setSelection(true); trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); } private void populatePasswordCmp(Composite parent) { EclipseUiUtils.clear(parent); Composite body = new Composite(parent, SWT.NO_FOCUS); ModifyListener ml = new ModifyListener() { private static final long serialVersionUID = -1558726363536729634L; @Override public void modifyText(ModifyEvent event) { checkPageComplete(); } }; body.setLayout(new GridLayout(2, false)); body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); pwdTxt = EclipseUiUtils.createGridLP(body, "New password", ml); pwd2Txt = EclipseUiUtils.createGridLP(body, "Repeat password", ml); } private void populateEmailCmp(Composite parent) { EclipseUiUtils.clear(parent); Composite body = new Composite(parent, SWT.NO_FOCUS); ModifyListener ml = new ModifyListener() { private static final long serialVersionUID = 2147704227294268317L; @Override public void modifyText(ModifyEvent event) { checkPageComplete(); } }; body.setLayout(new GridLayout(2, false)); body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); valueTxt = EclipseUiUtils.createGridLT(body, "New e-mail", ml); } private void checkPageComplete() { String errorMsg = null; if (chooseCommandCmb.getSelectionIndex() < 0) errorMsg = "Please select an action"; else if (CMD_UPDATE_EMAIL.equals(getCommand())) { if (!valueTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN)) errorMsg = "Not a valid e-mail address"; } else if (CMD_UPDATE_PASSWORD.equals(getCommand())) { if (EclipseUiUtils.isEmpty(pwdTxt.getText()) || pwdTxt.getText().length() < 4) errorMsg = "Please enter a password that is at least 4 character long"; else if (!pwdTxt.getText().equals(pwd2Txt.getText())) errorMsg = "Passwords are different"; } if (EclipseUiUtils.notEmpty(errorMsg)) { setMessage(errorMsg, WizardPage.ERROR); setPageComplete(false); } else { setMessage("Page complete, you can proceed to user choice", WizardPage.INFORMATION); setPageComplete(true); } getContainer().updateButtons(); } private void populateGroupCmp(Composite parent) { EclipseUiUtils.clear(parent); trueChk = new Button(parent, SWT.CHECK); trueChk.setText("Add to group. (It will remove user(s) from the " + "corresponding group if unchecked)"); trueChk.setSelection(true); trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); } protected String getCommand() { return commands.get(chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex())); } protected String getCommandLbl() { return chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex()); } @SuppressWarnings("unused") protected boolean getBoleanValue() { // FIXME this is not consistent and will lead to errors. if ("argeo:enabled".equals(getCommand())) return trueChk.getSelection(); else return !trueChk.getSelection(); } @SuppressWarnings("unused") protected String getStringValue() { String value = null; if (valueTxt != null) { value = valueTxt.getText(); if ("".equals(value.trim())) value = null; } return value; } protected char[] getPwdValue() { // We do not directly reset the password text fields: There is no // need to over secure this process: setting a pwd to multi users // at the same time is anyhow a bad practice and should be used only // in test environment or for temporary access if (pwdTxt == null || pwdTxt.isDisposed()) return null; else return pwdTxt.getText().toCharArray(); } protected String getEmailValue() { // We do not directly reset the password text fields: There is no // need to over secure this process: setting a pwd to multi users // at the same time is anyhow a bad practice and should be used only // in test environment or for temporary access if (valueTxt == null || valueTxt.isDisposed()) return null; else return valueTxt.getText(); } } /** * Displays a list of users with a check box to be able to choose some of them */ private class ChooseUsersWizardPage extends WizardPage implements IPageChangedListener { private static final long serialVersionUID = 7651807402211214274L; private ChooseUserTableViewer userTableCmp; public ChooseUsersWizardPage() { super("Choose Users"); setTitle("Select users who will be impacted"); } @Override public void createControl(Composite parent) { Composite pageCmp = new Composite(parent, SWT.NONE); pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); // Define the displayed columns List columnDefs = new ArrayList(); columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); // Only show technical DN to admin if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); userTableCmp = new ChooseUserTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); userTableCmp.setLayoutData(EclipseUiUtils.fillAll()); userTableCmp.setColumnDefinitions(columnDefs); userTableCmp.populate(true, true); userTableCmp.refresh(); setControl(pageCmp); // Add listener to update message when shown final IWizardContainer wContainer = this.getContainer(); if (wContainer instanceof IPageChangeProvider) { ((IPageChangeProvider) wContainer).addPageChangedListener(this); } } @Override public void pageChanged(PageChangedEvent event) { if (event.getSelectedPage() == this) { String msg = "Chosen batch action: " + chooseCommandPage.getCommandLbl(); ((WizardPage) event.getSelectedPage()).setMessage(msg); } } protected List getSelectedUsers() { return userTableCmp.getSelectedUsers(); } private class ChooseUserTableViewer extends LdifUsersTable { private static final long serialVersionUID = 5080437561015853124L; private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.DN, LdapAttrs.cn.name(), LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() }; public ChooseUserTableViewer(Composite parent, int style) { super(parent, style); } @Override protected List listFilteredElements(String filter) { Role[] roles; try { StringBuilder builder = new StringBuilder(); StringBuilder tmpBuilder = new StringBuilder(); if (EclipseUiUtils.notEmpty(filter)) for (String prop : knownProps) { tmpBuilder.append("("); tmpBuilder.append(prop); tmpBuilder.append("=*"); tmpBuilder.append(filter); tmpBuilder.append("*)"); } if (tmpBuilder.length() > 1) { builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") .append(LdapObjs.inetOrgPerson.name()).append(")(|"); builder.append(tmpBuilder.toString()); builder.append("))"); } else builder.append("(").append(LdapAttrs.objectClass.name()).append("=") .append(LdapObjs.inetOrgPerson.name()).append(")"); roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); } catch (InvalidSyntaxException e) { throw new CmsException("Unable to get roles with filter: " + filter, e); } List users = new ArrayList(); for (Role role : roles) // Prevent current logged in user to perform batch on // himself if (!UserAdminUtils.isCurrentUser((User) role)) users.add((User) role); return users; } } } /** Summary of input data before launching the process */ private class ValidateAndLaunchWizardPage extends WizardPage implements IPageChangedListener { private static final long serialVersionUID = 7098918351451743853L; private ChosenUsersTableViewer userTableCmp; public ValidateAndLaunchWizardPage() { super("Validate and launch"); setTitle("Validate and launch"); } @Override public void createControl(Composite parent) { Composite pageCmp = new Composite(parent, SWT.NO_FOCUS); pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); List columnDefs = new ArrayList(); columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); // Only show technical DN to admin if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); userTableCmp = new ChosenUsersTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); userTableCmp.setLayoutData(EclipseUiUtils.fillAll()); userTableCmp.setColumnDefinitions(columnDefs); userTableCmp.populate(false, false); userTableCmp.refresh(); setControl(pageCmp); // Add listener to update message when shown final IWizardContainer wContainer = this.getContainer(); if (wContainer instanceof IPageChangeProvider) { ((IPageChangeProvider) wContainer).addPageChangedListener(this); } } @Override public void pageChanged(PageChangedEvent event) { if (event.getSelectedPage() == this) { @SuppressWarnings({ "unchecked", "rawtypes" }) Object[] values = ((ArrayList) userListPage.getSelectedUsers()) .toArray(new Object[userListPage.getSelectedUsers().size()]); userTableCmp.getTableViewer().setInput(values); String msg = "Following batch action: [" + chooseCommandPage.getCommandLbl() + "] will be perfomed on the users listed below.\n"; // + "Are you sure you want to proceed?"; setMessage(msg); } } private class ChosenUsersTableViewer extends LdifUsersTable { private static final long serialVersionUID = 7814764735794270541L; public ChosenUsersTableViewer(Composite parent, int style) { super(parent, style); } @Override protected List listFilteredElements(String filter) { return userListPage.getSelectedUsers(); } } } }