From 050d54bb859aaed19777b32d7c9e677c532ef52f Mon Sep 17 00:00:00 2001 From: Bruno Sinou Date: Tue, 16 Dec 2014 02:03:32 +0000 Subject: [PATCH] Work on user management and generic forms in CMS - Work In Progress. git-svn-id: https://svn.argeo.org/commons/trunk@7577 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../src/org/argeo/cms/users/UserPage.java | 75 +++++ .../src/org/argeo/cms/users/UserPart.java | 205 +++++++++++++ .../org/argeo/cms/users/UserRolesPart.java | 283 ++++++++++++++++++ .../src/org/argeo/cms/users/UserStyles.java | 10 + .../src/org/argeo/cms/users/UserViewer.java | 104 +++++++ .../org/argeo/cms/users/UserViewerOld.java | 233 ++++++++++++++ .../src/org/argeo/cms/users/Users.java | 143 ++++++++- .../argeo/cms/viewers/AbstractPageViewer.java | 19 ++ org.argeo.eclipse.ui/bnd.bnd | 1 + org.argeo.eclipse.ui/pom.xml | 8 + .../ui/dialogs/UserCreationWizard.java | 213 +++++++++++++ org.argeo.security.equinox/pom.xml | 7 + .../admin/wizards/MainUserInfoWizardPage.java | 1 + 13 files changed, 1294 insertions(+), 8 deletions(-) create mode 100644 org.argeo.cms/src/org/argeo/cms/users/UserPage.java create mode 100644 org.argeo.cms/src/org/argeo/cms/users/UserPart.java create mode 100644 org.argeo.cms/src/org/argeo/cms/users/UserRolesPart.java create mode 100644 org.argeo.cms/src/org/argeo/cms/users/UserStyles.java create mode 100644 org.argeo.cms/src/org/argeo/cms/users/UserViewer.java create mode 100644 org.argeo.cms/src/org/argeo/cms/users/UserViewerOld.java create mode 100644 org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/UserCreationWizard.java diff --git a/org.argeo.cms/src/org/argeo/cms/users/UserPage.java b/org.argeo.cms/src/org/argeo/cms/users/UserPage.java new file mode 100644 index 000000000..2f4616d33 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/users/UserPage.java @@ -0,0 +1,75 @@ +package org.argeo.cms.users; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsEditable; +import org.argeo.cms.CmsUiProvider; +import org.argeo.cms.CmsUtils; +import org.argeo.cms.viewers.JcrVersionCmsEditable; +import org.argeo.cms.widgets.ScrolledPage; +import org.argeo.security.UserAdminService; +import org.argeo.security.jcr.JcrSecurityModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +/** Enable management of a given user */ +public class UserPage implements CmsUiProvider { + + // Enable user CRUD // INJECTED + private UserAdminService userAdminService; + private JcrSecurityModel jcrSecurityModel; + + // public UserPage(UserAdminService userAdminService, + // JcrSecurityModel jcrSecurityModel) { + // this.userAdminService = userAdminService; + // this.jcrSecurityModel = jcrSecurityModel; + // } + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + CmsEditable cmsEditable = new JcrVersionCmsEditable(context); + Composite page = createPage(parent); + UserViewer userViewer = new UserViewer(page, SWT.NONE, context, + cmsEditable); + + Control control = userViewer.getControl(); + Composite par = control.getParent(); + + new Label(par, SWT.NONE).setText("Work in progress terminate."); + + UserRolesPart rolesPart = new UserRolesPart(par, SWT.NO_FOCUS, context, + true); + rolesPart.setUserAdminService(userAdminService); + rolesPart.createControl(rolesPart, UserStyles.USER_FORM_TEXT); + rolesPart.refresh(); + + return page; + } + + protected Composite createPage(Composite parent) { + parent.setLayout(CmsUtils.noSpaceGridLayout()); + ScrolledPage scrolled = new ScrolledPage(parent, SWT.NONE); + scrolled.setLayoutData(CmsUtils.fillAll()); + scrolled.setLayout(CmsUtils.noSpaceGridLayout()); + // TODO manage style + // CmsUtils.style(scrolled, "maintenance_user_form"); + + Composite page = new Composite(scrolled, SWT.NONE); + page.setLayout(CmsUtils.noSpaceGridLayout()); + page.setBackgroundMode(SWT.INHERIT_NONE); + page.setLayoutData(CmsUtils.fillAll()); + return page; + } + + public void setUserAdminService(UserAdminService userAdminService) { + this.userAdminService = userAdminService; + } + + public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) { + this.jcrSecurityModel = jcrSecurityModel; + } +} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/users/UserPart.java b/org.argeo.cms/src/org/argeo/cms/users/UserPart.java new file mode 100644 index 000000000..a422521e1 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/users/UserPart.java @@ -0,0 +1,205 @@ +package org.argeo.cms.users; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.argeo.ArgeoException; +import org.argeo.cms.CmsUtils; +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.viewers.NodePart; +import org.argeo.cms.widgets.StyledControl; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.jcr.ArgeoNames; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Display a single user main info once it has been created. */ +public class UserPart extends StyledControl implements EditablePart, NodePart, + FocusListener { + private static final long serialVersionUID = -2883661960366940505L; + // private final static Log log = LogFactory.getLog(UserPart.class); + + // A static list of supported properties. + private List texts; + private final static String KEY_PROP_NAME = "jcr:propertyName"; + + // TODO implement to provide user creation ability for anonymous user? + // public UserPart(Composite parent, int swtStyle) { + // super(parent, swtStyle); + // } + + public UserPart(Composite parent, int style, Item item) + throws RepositoryException { + this(parent, style, item, true); + } + + public UserPart(Composite parent, int style, Item item, + boolean cacheImmediately) throws RepositoryException { + super(parent, style, item, cacheImmediately); + } + + @Override + public Item getItem() throws RepositoryException { + return getNode(); + } + + // Experimental, remove + public void setMouseListener(MouseListener mouseListener) { + super.setMouseListener(mouseListener); + + for (Text txt : texts) + txt.addMouseListener(mouseListener); + + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing()) + return createEditLayout(box, style); + else + return createROLayout(box, style); + } + + protected Composite createROLayout(Composite parent, String style) { + Composite body = new Composite(parent, SWT.NO_FOCUS); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + GridLayout layout = new GridLayout(2, false); + body.setLayout(layout); + + createTexts(body, UserStyles.USER_FORM_TEXT); + CmsUtils.style(body, UserStyles.USER_FORM_TEXT); + return body; + } + + private void createTexts(Composite parent, String style) { + texts = new ArrayList(); + texts.add(createLT(parent, style, "Displayed Name", Property.JCR_TITLE)); + texts.add(createLT(parent, style, "First name", + ArgeoNames.ARGEO_FIRST_NAME)); + texts.add(createLT(parent, style, "Last name", + ArgeoNames.ARGEO_LAST_NAME)); + texts.add(createLT(parent, style, "Email", + ArgeoNames.ARGEO_PRIMARY_EMAIL)); + texts.add(createLMT(parent, style, "Description", + Property.JCR_DESCRIPTION)); + } + + protected Composite createEditLayout(Composite parent, String style) { + Composite body = new Composite(parent, SWT.NO_FOCUS); + GridLayout layout = new GridLayout(2, false); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + body.setLayout(layout); + + createTexts(body, UserStyles.USER_FORM_TEXT); + + for (Text txt : texts) + txt.addFocusListener(this); + CmsUtils.style(body, UserStyles.USER_FORM_TEXT); + return body; + } + + void refresh() { + for (Text txt : texts) { + txt.setText(get(getNode(), (String) txt.getData(KEY_PROP_NAME))); + txt.setEditable(isEditing()); + } + } + + // THE LISTENER + @Override + public void focusGained(FocusEvent e) { + // Do nothing + } + + @Override + public void focusLost(FocusEvent e) { + // Save change if needed + Text text = (Text) e.getSource(); + set(getNode(), (String) text.getData(KEY_PROP_NAME), text.getText()); + } + + // HELPERS + /** Creates label and text. */ + protected Text createLT(Composite body, String style, String label, + String propName) { + Label lbl = new Label(body, SWT.NONE); + lbl.setText(label); + lbl.setFont(EclipseUiUtils.getBoldFont(body)); + lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + Text text = new Text(body, SWT.BORDER); + text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + CmsUtils.style(text, style); + text.setData(KEY_PROP_NAME, propName); + return text; + } + + /** Creates label and multiline text. */ + protected Text createLMT(Composite body, String style, String label, + String propName) { + Label lbl = new Label(body, SWT.NONE); + lbl.setText(label); + lbl.setFont(EclipseUiUtils.getBoldFont(body)); + GridData gd = new GridData(SWT.RIGHT, SWT.TOP, false, false); + gd.verticalIndent = 0; + lbl.setLayoutData(gd); + Text text = new Text(body, SWT.BORDER | SWT.MULTI | SWT.WRAP); + gd = new GridData(SWT.FILL, SWT.CENTER, true, true); + gd.heightHint = 100; + text.setLayoutData(gd); + CmsUtils.style(text, style); + text.setData(KEY_PROP_NAME, propName); + return text; + } + + /** + * Concisely get the string value of a property. Returns an empty String + * rather than null if this node doesn't have this property or if the + * corresponding property is an empty string. + */ + private String get(Node node, String propertyName) { + try { + if (!node.hasProperty(propertyName)) + return ""; + else + return node.getProperty(propertyName).getString(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot get property " + propertyName + + " of " + node, e); + } + } + + private boolean set(Node node, String propName, String value) { + try { + if ("".equals(value) + && (!node.hasProperty(propName) || node + .hasProperty(propName) + && "".equals(node.getProperty(propName).getString()))) + return false; + else if (node.hasProperty(propName) + && node.getProperty(propName).getString() + .equals((String) value)) + return false; + else { + node.setProperty(propName, (String) value); + node.getSession().save(); + return true; + } + } catch (RepositoryException e) { + throw new ArgeoException("Cannot property " + propName + " on " + + node + " with value " + value, e); + } + } +} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/users/UserRolesPart.java b/org.argeo.cms/src/org/argeo/cms/users/UserRolesPart.java new file mode 100644 index 000000000..be13ed8a2 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/users/UserRolesPart.java @@ -0,0 +1,283 @@ +package org.argeo.cms.users; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Item; +import javax.jcr.RepositoryException; + +import org.argeo.ArgeoException; +import org.argeo.cms.CmsUtils; +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.viewers.NodePart; +import org.argeo.cms.widgets.StyledControl; +import org.argeo.jcr.ArgeoNames; +import org.argeo.security.UserAdminService; +import org.argeo.security.jcr.JcrSecurityModel; +import org.argeo.security.jcr.JcrUserDetails; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; +import org.springframework.security.GrantedAuthority; + +/** Display a single user main info once it has been created. */ +public class UserRolesPart extends StyledControl implements EditablePart, + NodePart, FocusListener { + + // A static list of supported properties. + private List texts; + private final static String KEY_PROP_NAME = "jcr:propertyName"; + + private CheckboxTableViewer rolesViewer; + private JcrUserDetails userDetails; + private UserAdminService userAdminService; + private List roles; + + // FIXME + // private final Image checked; + + // TODO implement to provide user creation ability for anonymous user? + // public UserPart(Composite parent, int swtStyle) { + // super(parent, swtStyle); + // } + + public UserRolesPart(Composite parent, int style, Item item, + UserAdminService userAdminService, JcrSecurityModel jcrSecurityModel) + throws RepositoryException { + this(parent, style, item, true); + } + + public UserRolesPart(Composite parent, int style, Item item, + boolean cacheImmediately) throws RepositoryException { + super(parent, style, item, cacheImmediately); + + // checked = new Image(parent, imageData); + + } + + @Override + public Item getItem() throws RepositoryException { + return getNode(); + } + + // Experimental, remove + public void setMouseListener(MouseListener mouseListener) { + super.setMouseListener(mouseListener); + + for (Text txt : texts) + txt.addMouseListener(mouseListener); + + } + + @Override + protected Control createControl(Composite box, String style) { + Table table = new Table(box, SWT.CHECK | SWT.MULTI | SWT.H_SCROLL + | SWT.V_SCROLL); + table.setLinesVisible(true); + table.setHeaderVisible(false); + CmsUtils.style(table, style); + + rolesViewer = new CheckboxTableViewer(table); + + TableViewerColumn column; + + // check column + // TableViewerColumn column = createTableViewerColumn(rolesViewer, + // "checked", 20); + // column.setLabelProvider(new ColumnLabelProvider() { + // public String getText(Object element) { + // return null; + // } + + // public Image getImage(Object element) { + // String role = element.toString(); + // if (roles.contains(role)) { + // + // return ROLE_CHECKED; + // } else { + // return null; + // } + // + // } + // ); + // column.setEditingSupport(new RoleEditingSupport(rolesViewer, part)); + + // role column + column = createTableViewerColumn(rolesViewer, "Role", 400); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -7334412925967366255L; + + public String getText(Object element) { + return element.toString(); + } + }); + rolesViewer.setContentProvider(new RolesContentProvider()); + rolesViewer.setInput(userAdminService.listEditableRoles().toArray()); + + rolesViewer.addCheckStateListener(new ICheckStateListener() { + + @Override + public void checkStateChanged(CheckStateChangedEvent event) { + String name = (String) event.getElement(); + boolean contained = roles.contains(name); + boolean checked = event.getChecked(); + if (checked != contained) { + if (contained) + roles.add(name); + else + roles.remove(name); + userDetails = userDetails.cloneWithNewRoles(roles); + userAdminService.updateUser(userDetails); + } + } + }); + return table; + } + + // void refresh() { + // for (Text txt : texts) { + // } + // } + + // THE LISTENER + @Override + public void focusGained(FocusEvent e) { + // Do nothing + } + + @Override + public void focusLost(FocusEvent e) { + // Save change if needed + // Text text = (Text) e.getSource(); + } + + // private final static Image ROLE_CHECKED = SecurityAdminPlugin + // .getImageDescriptor("icons/security.gif").createImage(); + + public void setUserAdminService(UserAdminService userAdminService) { + this.userAdminService = userAdminService; + + try { + String username = getNode().getProperty(ArgeoNames.ARGEO_USER_ID) + .getString(); + // ; + + if (userAdminService.userExists(username)) { + JcrUserDetails userDetails = (JcrUserDetails) userAdminService + .loadUserByUsername(username); + setUserDetails(userDetails); + } + } catch (Exception e) { + throw new ArgeoException("Cannot retrieve userDetails for "// + + // username + , e); + } + + } + + public void setUserDetails(JcrUserDetails userDetails) { + this.userDetails = userDetails; + + this.roles = new ArrayList(); + for (GrantedAuthority ga : userDetails.getAuthorities()) + roles.add(ga.getAuthority()); + if (rolesViewer != null) + rolesViewer.refresh(); + } + + protected TableViewerColumn createTableViewerColumn(TableViewer viewer, + String title, int bound) { + final TableViewerColumn viewerColumn = new TableViewerColumn(viewer, + SWT.NONE); + final TableColumn column = viewerColumn.getColumn(); + column.setText(title); + column.setWidth(bound); + column.setResizable(true); + column.setMoveable(true); + return viewerColumn; + + } + + public List getRoles() { + return roles; + } + + public void refresh() { + + // return roles.toArray(); + rolesViewer.setCheckedElements(roles.toArray()); // setSelection(1); + // rolesViewer.setInput(roles); + rolesViewer.refresh(); + } + + private class RolesContentProvider implements IStructuredContentProvider { + private static final long serialVersionUID = 4119915828862214310L; + + public Object[] getElements(Object inputElement) { + return userAdminService.listEditableRoles().toArray(); + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + viewer.refresh(); + } + } + + // /** Select the columns by editing the checkbox in the first column */ + // class RoleEditingSupport extends EditingSupport { + // + // private final TableViewer viewer; + // + // public RoleEditingSupport(TableViewer viewer) { + // super(viewer); + // this.viewer = viewer; + // } + // + // @Override + // protected CellEditor getCellEditor(Object element) { + // return new CheckboxCellEditor(null, SWT.CHECK | SWT.READ_ONLY); + // + // } + // + // @Override + // protected boolean canEdit(Object element) { + // return true; + // } + // + // @Override + // protected Object getValue(Object element) { + // String role = element.toString(); + // return roles.contains(role); + // + // } + // + // @Override + // protected void setValue(Object element, Object value) { + // Boolean inRole = (Boolean) value; + // String role = element.toString(); + // if (inRole && !roles.contains(role)) { + // roles.add(role); + // } else if (!inRole && roles.contains(role)) { + // roles.remove(role); + // } + // viewer.refresh(); + // } + // } + +} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/users/UserStyles.java b/org.argeo.cms/src/org/argeo/cms/users/UserStyles.java new file mode 100644 index 000000000..18501b238 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/users/UserStyles.java @@ -0,0 +1,10 @@ +package org.argeo.cms.users; + +/** Some tries with the style */ +public interface UserStyles { + + /** A text in a form */ + public final static String USER_FORM_TEXT = "user_form_text"; + + public final static String USER_FORM_TEXT_ALT = "user_form_text_alt"; +} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/users/UserViewer.java b/org.argeo.cms/src/org/argeo/cms/users/UserViewer.java new file mode 100644 index 000000000..a520da1bd --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/users/UserViewer.java @@ -0,0 +1,104 @@ +package org.argeo.cms.users; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsEditable; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsUtils; +import org.argeo.cms.viewers.AbstractPageViewer; +import org.argeo.cms.viewers.EditablePart; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +public class UserViewer extends AbstractPageViewer { + private static final long serialVersionUID = -717973369525981931L; + private UserPart userPart; + + public UserViewer(Composite parent, int style, Node userNode, + CmsEditable cmsEditable) throws RepositoryException { + this(new UserPart(parent, SWT.NO_BACKGROUND, userNode), style, + userNode, cmsEditable); + } + + private UserViewer(UserPart userPart, int style, Node userNode, + CmsEditable cmsEditable) throws RepositoryException { + super(userPart, style, cmsEditable); + this.userPart = userPart; + userPart.createControl(userPart,"cms_test"); + userPart.setStyle("cms_test"); + refresh(); + userPart.setLayoutData(CmsUtils.fillWidth()); + userPart.setMouseListener(getMouseListener()); + + // Add other parts +// userRolesPart.createControl(userPart,"cms_test"); +// userPart.setStyle("cms_test"); +// refresh(); +// userPart.setLayoutData(CmsUtils.fillWidth()); +// userPart.setMouseListener(getMouseListener()); + + + } + + // private JcrComposite createParents(Composite parent, Node userNode) + // throws RepositoryException { + // this.parent = ; + // return this.parent; + // } + + @Override + public Control getControl() { + return userPart; + } + + // MOUSE LISTENER + @Override + protected MouseListener createMouseListener() { + return new ML(); + } + + private class ML extends MouseAdapter { + private static final long serialVersionUID = 8526890859876770905L; + + @Override + public void mouseDoubleClick(MouseEvent e) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + if (getCmsEditable().canEdit()) { + getCmsEditable().startEditing(); + EditablePart composite = findDataParent(source); + Point point = new Point(e.x, e.y); + edit(composite, source.toDisplay(point)); + } + } + } + } + + protected void updateContent(EditablePart part) throws RepositoryException { + if (part instanceof UserPart) + ((UserPart) part).refresh(); + } + + + private EditablePart findDataParent(Control parent) { + if (parent instanceof EditablePart) { + return (EditablePart) parent; + } + if (parent.getParent() != null) + return findDataParent(parent.getParent()); + else + throw new CmsException("No data parent found"); + } + + protected void refresh(Control control) throws RepositoryException { + if (control instanceof UserPart) + ((UserPart) control).refresh(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/users/UserViewerOld.java b/org.argeo.cms/src/org/argeo/cms/users/UserViewerOld.java new file mode 100644 index 000000000..ad0b62f45 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/users/UserViewerOld.java @@ -0,0 +1,233 @@ +package org.argeo.cms.users; + +import java.util.Observable; +import java.util.Observer; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.cms.CmsEditable; +import org.argeo.cms.CmsException; +import org.argeo.cms.CmsUtils; +import org.argeo.cms.viewers.EditablePart; +import org.argeo.cms.viewers.JcrVersionCmsEditable; +import org.eclipse.jface.viewers.ContentViewer; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Widget; + +/** Manage edition of the users */ +public class UserViewerOld extends ContentViewer implements Observer { + private static final long serialVersionUID = -5055220955004863645L; + + private final static Log log = LogFactory.getLog(UserViewerOld.class); + + // private UserPart userPart = new UserPart(); + + private CmsEditable cmsEditable; + private Node currentDisplayed; + + /** The basis for the layouts, typically the body of a ScrolledPage. */ + private final Composite page; + + private MouseListener mouseListener; + + private EditablePart edited; + private ISelection selection = StructuredSelection.EMPTY; + + protected UserViewerOld(Composite composite) { + page = composite; + } + + void setDisplayedUser(Node node) { + try { + + if (!hasChanged(node)) + return; + + CmsUtils.clear(page); + // userPart.createUi(page, node); + + page.layout(); + page.getParent().layout(); + + // update + cmsEditable = new JcrVersionCmsEditable(node); + + if (this.cmsEditable instanceof Observable) + ((Observable) this.cmsEditable).addObserver(this); + // // if (cmsEditable.canEdit()) { + // // mouseListener = createMouseListener(); + // // refresh(); + // // } + } catch (RepositoryException re) { + throw new ArgeoException("Unable to display " + node, re); + } + } + + private boolean hasChanged(Node node) throws RepositoryException { + if (currentDisplayed == null && node == null) + return false; + else if (currentDisplayed == null || node == null) + return true; + else + return node.getIdentifier() + .equals(currentDisplayed.getIdentifier()); + } + + /** Create (retrieve) the MouseListener to use. */ + protected MouseListener createMouseListener() { + return new MouseAdapter() { + private static final long serialVersionUID = 1L; + }; + } + + @Override + public void update(Observable o, Object arg) { + if (o == cmsEditable) + editingStateChanged(cmsEditable); + } + + /** To be overridden in order to provide the actual refresh */ + protected void refresh(Control control) throws RepositoryException { + } + + /** To be overridden.Save the edited part. */ + protected void save(EditablePart part) throws RepositoryException { + } + + /** Prepare the edited part */ + protected void prepare(EditablePart part, Object caretPosition) { + } + + /** Notified when the editing state changed. Does nothing, to be overridden */ + protected void editingStateChanged(CmsEditable cmsEditable) { + } + + @Override + public Control getControl() { + return null; + } + + @Override + public void refresh() { + try { + // if (cmsEditable.canEdit() && !readOnly) + // mouseListener = createMouseListener(); + // else + // mouseListener = null; + refresh(getControl()); + layout(getControl()); + } catch (RepositoryException e) { + throw new CmsException("Cannot refresh", e); + } + } + + @Override + public void setSelection(ISelection selection, boolean reveal) { + this.selection = selection; + } + + protected void updateContent(EditablePart part) throws RepositoryException { + } + + // LOW LEVEL EDITION + protected void edit(EditablePart part, Object caretPosition) { + try { + if (edited == part) + return; + + if (edited != null && edited != part) + stopEditing(true); + + part.startEditing(); + updateContent(part); + prepare(part, caretPosition); + edited = part; + layout(part.getControl()); + } catch (RepositoryException e) { + throw new CmsException("Cannot edit " + part, e); + } + } + + private void stopEditing(Boolean save) throws RepositoryException { + if (edited instanceof Widget && ((Widget) edited).isDisposed()) { + edited = null; + return; + } + + assert edited != null; + if (edited == null) { + if (log.isTraceEnabled()) + log.warn("Told to stop editing while not editing anything"); + return; + } + + if (save) + save(edited); + + edited.stopEditing(); + updateContent(edited); + layout(((EditablePart) edited).getControl()); + edited = null; + } + + // METHODS AVAILABLE TO EXTENDING CLASSES + protected void saveEdit() { + try { + if (edited != null) + stopEditing(true); + } catch (RepositoryException e) { + throw new CmsException("Cannot stop editing", e); + } + } + + protected void cancelEdit() { + try { + if (edited != null) + stopEditing(false); + } catch (RepositoryException e) { + + throw new CmsException("Cannot cancel editing", e); + } + } + + /** Layout this controls from the related base page. */ + public void layout(Control... controls) { + page.layout(controls); + } + + // UTILITIES + /** Check whether the edited part is in a proper state */ + protected void checkEdited() { + if (edited == null || (edited instanceof Widget) + && ((Widget) edited).isDisposed()) + throw new CmsException( + "Edited should not be null or disposed at this stage"); + } + + // GETTERS / SETTERS + protected EditablePart getEdited() { + return edited; + } + + public MouseListener getMouseListener() { + return mouseListener; + } + + public CmsEditable getCmsEditable() { + return cmsEditable; + } + + @Override + public ISelection getSelection() { + return selection; + } +} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/users/Users.java b/org.argeo.cms/src/org/argeo/cms/users/Users.java index c8a9cc497..e7f09440d 100644 --- a/org.argeo.cms/src/org/argeo/cms/users/Users.java +++ b/org.argeo.cms/src/org/argeo/cms/users/Users.java @@ -6,43 +6,141 @@ import javax.jcr.Session; import javax.jcr.security.AccessControlManager; import javax.jcr.security.Privilege; +import org.argeo.ArgeoException; import org.argeo.cms.CmsUiProvider; import org.argeo.cms.CmsUtils; import org.argeo.cms.maintenance.NonAdminPage; +import org.argeo.eclipse.ui.dialogs.UserCreationWizard; import org.argeo.eclipse.ui.parts.UsersTable; import org.argeo.jcr.JcrUtils; +import org.argeo.security.UserAdminService; +import org.argeo.security.jcr.JcrSecurityModel; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.wizard.WizardDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +/** + * Simple page to manage users of a given repository. We still rely on Argeo + * model; with user stored in the main workspace + */ public class Users implements CmsUiProvider { + // Enable user CRUD // INJECTED + private UserAdminService userAdminService; + private JcrSecurityModel jcrSecurityModel; + private String userWkspName; + + // Local UI Providers NonAdminPage nap = new NonAdminPage(); + UserPage userPage = new UserPage(); + // Manage authorization @Override public Control createUi(Composite parent, Node context) throws RepositoryException { - if (isAdmin(context)) { Session session = context.getSession().getRepository() - .login("main"); - return createUsersTable(parent, session); + .login(userWkspName); + return createMainLayout(parent, session); } else nap.createUi(parent, context); return null; + } + + // Main layout + // Left: User Table - Right User Details Edition + private Control createMainLayout(Composite parent, final Session session) + throws RepositoryException { + + Composite layoutCmp = new Composite(parent, SWT.NO_FOCUS); + layoutCmp.setLayoutData(CmsUtils.fillAll()); + layoutCmp + .setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(2, true))); + + Composite left = new Composite(layoutCmp, SWT.NO_FOCUS); + left.setLayoutData(CmsUtils.fillAll()); + UsersTable table = createUsersTable(left, session); + + final Composite right = new Composite(layoutCmp, SWT.NO_FOCUS); + right.setLayoutData(CmsUtils.fillAll()); + // Composite innerPage = createUserPage(right); + // final UserViewerOld userViewer = new UserViewerOld(innerPage); + final TableViewer viewer = table.getTableViewer(); + viewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) viewer + .getSelection(); + if (selection.isEmpty()) { + // Should we clean the right column? + return; + } else { + Node context = (Node) selection.getFirstElement(); + try { + CmsUtils.clear(right); + userPage.createUi(right, context); + right.layout(); + right.getParent().layout(); + } catch (RepositoryException e) { + e.printStackTrace(); + throw new ArgeoException("unable to create " + + "editor for user " + context, e); + } + } + } + }); + return left; } - private Control createUsersTable(Composite parent, final Session session) + private UsersTable createUsersTable(Composite parent, final Session session) throws RepositoryException { + parent.setLayout(CmsUtils.noSpaceGridLayout()); + + // Add user CRUD buttons + Composite buttonCmp = new Composite(parent, SWT.NO_FOCUS); + buttonCmp.setLayoutData(CmsUtils.fillWidth()); + buttonCmp.setLayout(new GridLayout(2, false)); + // Delete + final Button deleteBtn = new Button(buttonCmp, SWT.PUSH); + deleteBtn.setText("Delete selected"); + // Add + final Button addBtn = new Button(buttonCmp, SWT.PUSH); + addBtn.setText("Create"); + addBtn.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 9214984636836267786L; + + @Override + public void widgetSelected(SelectionEvent e) { + UserCreationWizard newUserWizard = new UserCreationWizard( + session, userAdminService, jcrSecurityModel); + WizardDialog dialog = new WizardDialog(addBtn.getShell(), + newUserWizard); + dialog.open(); + // TODO refresh list if user has been created + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + // Create the composite that displays the list and a filter UsersTable userTableCmp = new UsersTable(parent, SWT.NO_FOCUS, session); userTableCmp.populate(true, false); - userTableCmp.setLayoutData(CmsUtils.fillAll()); - userTableCmp.addDisposeListener(new DisposeListener() { private static final long serialVersionUID = -8854052549807709846L; @@ -73,8 +171,23 @@ public class Users implements CmsUiProvider { return userTableCmp; } + // protected Composite createUserPage(Composite parent) { + // parent.setLayout(CmsUtils.noSpaceGridLayout()); + // ScrolledPage scrolled = new ScrolledPage(parent, SWT.NONE); + // scrolled.setLayoutData(CmsUtils.fillAll()); + // scrolled.setLayout(CmsUtils.noSpaceGridLayout()); + // // TODO manage style + // // CmsUtils.style(scrolled, "maintenance_user_form"); + // + // Composite page = new Composite(scrolled, SWT.NONE); + // page.setLayout(CmsUtils.noSpaceGridLayout()); + // page.setBackgroundMode(SWT.INHERIT_NONE); + // + // return page; + // } + private boolean isAdmin(Node node) throws RepositoryException { - // FIXME clean this check one new user management policy has been + // FIXME clean this once new user management policy has been // implemented. AccessControlManager acm = node.getSession().getAccessControlManager(); Privilege[] privs = new Privilege[1]; @@ -142,4 +255,18 @@ public class Users implements CmsUiProvider { // } // } -} + /* DEPENDENCY INJECTION */ + public void setWorkspaceName(String workspaceName) { + this.userWkspName = workspaceName; + } + + public void setUserAdminService(UserAdminService userAdminService) { + this.userAdminService = userAdminService; + userPage.setUserAdminService(userAdminService); + } + + public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) { + this.jcrSecurityModel = jcrSecurityModel; + userPage.setJcrSecurityModel(jcrSecurityModel); + } +} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/viewers/AbstractPageViewer.java b/org.argeo.cms/src/org/argeo/cms/viewers/AbstractPageViewer.java index 5e6de3709..e779a7993 100644 --- a/org.argeo.cms/src/org/argeo/cms/viewers/AbstractPageViewer.java +++ b/org.argeo.cms/src/org/argeo/cms/viewers/AbstractPageViewer.java @@ -10,6 +10,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsEditable; import org.argeo.cms.CmsException; +import org.argeo.cms.widgets.JcrComposite; import org.argeo.cms.widgets.ScrolledPage; import org.eclipse.jface.viewers.ContentViewer; import org.eclipse.jface.viewers.ISelection; @@ -38,6 +39,24 @@ public abstract class AbstractPageViewer extends ContentViewer implements private EditablePart edited; private ISelection selection = StructuredSelection.EMPTY; + // FIXME Added by BSinou to manage non-section Composite. + // Is it the correct method? + protected AbstractPageViewer(Composite parent, int style, + CmsEditable cmsEditable) { + // read only at UI level + readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); + + this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE + : cmsEditable; + if (this.cmsEditable instanceof Observable) + ((Observable) this.cmsEditable).addObserver(this); + + if (cmsEditable.canEdit()) { + mouseListener = createMouseListener(); + } + page = findPage(parent); + } + protected AbstractPageViewer(Section parent, int style, CmsEditable cmsEditable) { // read only at UI level diff --git a/org.argeo.eclipse.ui/bnd.bnd b/org.argeo.eclipse.ui/bnd.bnd index c7b5f88f1..665f180f3 100644 --- a/org.argeo.eclipse.ui/bnd.bnd +++ b/org.argeo.eclipse.ui/bnd.bnd @@ -7,6 +7,7 @@ Import-Package: org.eclipse.core.commands,\ org.osgi.util.tracker;version="[1.4,2)",\ org.springframework.beans.factory,\ org.springframework.core.io.support,\ + org.springframework.dao,\ * # Was: diff --git a/org.argeo.eclipse.ui/pom.xml b/org.argeo.eclipse.ui/pom.xml index 26a46c867..116174d0e 100644 --- a/org.argeo.eclipse.ui/pom.xml +++ b/org.argeo.eclipse.ui/pom.xml @@ -50,6 +50,14 @@ org.springframework.context + + + org.argeo.commons + org.argeo.security.core + 2.1.12-SNAPSHOT + + + org.argeo.tp diff --git a/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/UserCreationWizard.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/UserCreationWizard.java new file mode 100644 index 000000000..98531d79f --- /dev/null +++ b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/UserCreationWizard.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.eclipse.ui.dialogs; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.UserJcrUtils; +import org.argeo.security.UserAdminService; +import org.argeo.security.jcr.JcrSecurityModel; +import org.argeo.security.jcr.JcrUserDetails; +import org.eclipse.jface.dialogs.MessageDialog; +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.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.userdetails.UserDetails; +import org.springframework.security.userdetails.UsernameNotFoundException; + +/** Wizard to create a new user */ +public class UserCreationWizard extends Wizard { + private final static Log log = LogFactory.getLog(UserCreationWizard.class); + private Session session; + private UserAdminService userAdminService; + private JcrSecurityModel jcrSecurityModel; + + // pages + private MainUserInfoWizardPage mainUserInfo; + + public UserCreationWizard(Session session, UserAdminService userAdminService, + JcrSecurityModel jcrSecurityModel) { + this.session = session; + this.userAdminService = userAdminService; + this.jcrSecurityModel = jcrSecurityModel; + } + + @Override + public void addPages() { + mainUserInfo = new MainUserInfoWizardPage(userAdminService); + addPage(mainUserInfo); + } + + @Override + public boolean performFinish() { + if (!canFinish()) + return false; + + String username = mainUserInfo.getUsername(); + try { + Node userProfile = jcrSecurityModel.sync(session, username, null); + session.getWorkspace().getVersionManager() + .checkout(userProfile.getPath()); + mainUserInfo.mapToProfileNode(userProfile); + String password = mainUserInfo.getPassword(); + // TODO add roles + JcrUserDetails jcrUserDetails = new JcrUserDetails(userProfile, + password, new GrantedAuthority[0]); + session.save(); + session.getWorkspace().getVersionManager() + .checkin(userProfile.getPath()); + userAdminService.createUser(jcrUserDetails); + return true; + } catch (Exception e) { + JcrUtils.discardQuietly(session); + Node userHome = UserJcrUtils.getUserHome(session, username); + if (userHome != null) { + try { + userHome.remove(); + session.save(); + } catch (RepositoryException e1) { + JcrUtils.discardQuietly(session); + log.warn("Error when trying to clean up failed new user " + + username, e1); + } + } + MessageDialog.openError(getShell(), "Eroor", + "Cannot create new user " + username); + return false; + } + } + + /** First page, collect all main info and check their validity */ + protected class MainUserInfoWizardPage extends WizardPage implements + ModifyListener, ArgeoNames { + private static final long serialVersionUID = -3367329974808698649L; + private Text username, firstName, lastName, primaryEmail, password1, + password2; + private UserAdminService userAdminService; + + public MainUserInfoWizardPage(UserAdminService userAdminService) { + super("Main"); + this.userAdminService = userAdminService; + setTitle("Required Information"); + } + + @Override + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + username = EclipseUiUtils.createGridLT(composite, "Username", this); + primaryEmail = EclipseUiUtils + .createGridLT(composite, "Email", this); + firstName = EclipseUiUtils.createGridLT(composite, "First name", + this); + lastName = EclipseUiUtils + .createGridLT(composite, "Last name", this); + password1 = EclipseUiUtils + .createGridLP(composite, "Password", this); + password2 = EclipseUiUtils.createGridLP(composite, + "Repeat password", this); + setControl(composite); + + // Initialize buttons + setPageComplete(false); + getContainer().updateButtons(); + } + + @Override + public void modifyText(ModifyEvent event) { + String message = checkComplete(); + if (message != null) { + setMessage(message, WizardPage.ERROR); + setPageComplete(false); + } else { + setMessage("Complete", WizardPage.INFORMATION); + setPageComplete(true); + } + getContainer().updateButtons(); + } + + /** @return error message or null if complete */ + protected String checkComplete() { + // if + // (!username.getText().matches(UserAdminService.USERNAME_PATTERN)) + // return + // "Wrong user name format, should be lower case, between 3 and 64 characters with only '_' an '@' as acceptable special character."; + + if (username.getText().trim().equals("")) + return "User name must not be empty"; + + try { + UserDetails userDetails = userAdminService + .loadUserByUsername(username.getText()); + return "User " + userDetails.getUsername() + " already exists"; + } catch (UsernameNotFoundException e) { + // silent + } + if (!primaryEmail.getText().matches(UserAdminService.EMAIL_PATTERN)) + return "Not a valid email address"; + if (firstName.getText().trim().equals("")) + return "Specify a first name"; + if (lastName.getText().trim().equals("")) + return "Specify a last name"; + if (password1.getText().trim().equals("")) + return "Specify a password"; + if (password2.getText().trim().equals("")) + return "Repeat the password"; + if (!password2.getText().equals(password1.getText())) + return "Passwords are different"; + return null; + } + + public String getUsername() { + return username.getText(); + } + + public String getPassword() { + return password1.getText(); + } + + public void mapToProfileNode(Node up) { + try { + up.setProperty(ARGEO_PRIMARY_EMAIL, primaryEmail.getText()); + up.setProperty(ARGEO_FIRST_NAME, firstName.getText()); + up.setProperty(ARGEO_LAST_NAME, lastName.getText()); + + // derived values + // TODO add wizard pages to do it + up.setProperty(Property.JCR_TITLE, firstName.getText() + " " + + lastName.getText()); + up.setProperty(Property.JCR_DESCRIPTION, ""); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot map to " + up, e); + } + } + } +} \ No newline at end of file diff --git a/org.argeo.security.equinox/pom.xml b/org.argeo.security.equinox/pom.xml index 184035a25..93d0bb2d5 100644 --- a/org.argeo.security.equinox/pom.xml +++ b/org.argeo.security.equinox/pom.xml @@ -48,6 +48,13 @@ org.eclipse.osgi + + + org.argeo.commons + org.argeo.eclipse.ui.workbench + 2.1.12-SNAPSHOT + + org.argeo.commons diff --git a/org.argeo.security.ui.admin/src/org/argeo/security/ui/admin/wizards/MainUserInfoWizardPage.java b/org.argeo.security.ui.admin/src/org/argeo/security/ui/admin/wizards/MainUserInfoWizardPage.java index ad3662b6f..1bd8e8f1c 100644 --- a/org.argeo.security.ui.admin/src/org/argeo/security/ui/admin/wizards/MainUserInfoWizardPage.java +++ b/org.argeo.security.ui.admin/src/org/argeo/security/ui/admin/wizards/MainUserInfoWizardPage.java @@ -35,6 +35,7 @@ import org.springframework.security.userdetails.UsernameNotFoundException; public class MainUserInfoWizardPage extends WizardPage implements ModifyListener, ArgeoNames { + private static final long serialVersionUID = -3367329974808698649L; private Text username, firstName, lastName, primaryEmail, password1, password2; private UserAdminService userAdminService; -- 2.30.2