From 484dcb1507e4e35cc282e50522ea7eac7e99a7f9 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 2 Nov 2011 19:29:31 +0000 Subject: [PATCH] Big cleanup of the security layers git-svn-id: https://svn.argeo.org/commons/trunk@4872 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- demo/argeo_node_web.properties | 6 +- .../META-INF/spring/jcrsecuritydao.xml | 8 - ...ritydao-osgi.xml => security-jcr-osgi.xml} | 6 +- ...services.xml => security-jcr-services.xml} | 24 +- .../META-INF/spring/ldap-jcr.xml | 64 -- .../META-INF/spring/security-ldap-jcr.xml | 76 +++ .../{ldap-osgi.xml => security-ldap-osgi.xml} | 16 +- .../spring/security-ldap-services.xml | 52 ++ .../spring/{ldap.xml => security-ldap.xml} | 65 +- .../ldap.properties | 4 +- .../META-INF/MANIFEST.MF | 10 +- .../META-INF/spring/security-os-osgi.xml | 11 +- .../META-INF/spring/security-os.xml | 50 +- .../security.properties | 2 +- .../security/equinox/SpringLoginModule.java | 42 +- .../META-INF/spring/common.xml | 9 + .../build.properties | 3 +- .../security-admin.properties | 1 + .../ui/admin/editors/DefaultUserMainPage.java | 29 +- .../ui/admin/wizards/NewUserWizard.java | 22 +- .../security/NodeAuthenticationToken.java | 47 ++ .../argeo/security/OsAuthenticationToken.java | 19 +- .../security/SiteAuthenticationToken.java | 34 -- .../org/argeo/security/UserAdminService.java | 1 + .../core/OsAuthenticationProvider.java | 15 +- .../jcr/JcrAuthenticationProvider.java | 133 ---- .../security/jcr/JcrAuthenticationToken.java | 64 -- .../argeo/security/jcr/JcrUserDetails.java | 71 ++- .../jcr/OsJcrAuthenticationProvider.java | 138 ++--- .../jcr/RemoteJcrAuthenticationProvider.java | 108 ++++ .../jcr/SecureThreadBoundSession.java | 12 - .../org/argeo/security/jcr/SystemSession.java | 34 -- .../jackrabbit/ArgeoSecurityManager.java | 5 +- .../JackrabbitAuthenticationProvider.java | 82 --- .../org.argeo.security.ldap/.classpath | 2 +- .../ldap/jcr/JcrLdapSynchronizer.java | 568 ++++++++++++++++++ .../ldap/jcr/JcrUserDetailsContextMapper.java | 319 ---------- .../WEB-INF/applicationContext.xml | 2 +- .../WEB-INF/osgi.xml | 11 +- .../META-INF/spring/noderepo-osgi.xml | 11 +- .../META-INF/spring/noderepo.xml | 24 +- .../build.properties | 5 +- .../noderepo.properties | 6 +- .../org.argeo.node.repo.jackrabbit/pom.xml | 7 +- .../repository-h2.xml | 2 +- .../repository-postgresql.xml | 2 +- .../org.argeo.jcr.ui.explorer/.classpath | 4 +- .../ui/explorer/model/RepositoriesNode.java | 2 +- .../argeo/jackrabbit/JackrabbitContainer.java | 219 +++---- .../remote/SimpleSessionProvider.java | 8 +- .../main/java/org/argeo/jcr/ArgeoNames.java | 8 + .../src/main/java/org/argeo/jcr/JcrUtils.java | 289 +++++---- .../org/argeo/jcr/security/JcrKeyring.java | 4 - .../main/resources/org/argeo/jcr/argeo.cnd | 4 + 54 files changed, 1490 insertions(+), 1270 deletions(-) delete mode 100644 security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/jcrsecuritydao.xml rename security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/{jcrsecuritydao-osgi.xml => security-jcr-osgi.xml} (83%) rename security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/{services.xml => security-jcr-services.xml} (67%) delete mode 100644 security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-jcr.xml create mode 100644 security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-jcr.xml rename security/modules/org.argeo.security.dao.ldap/META-INF/spring/{ldap-osgi.xml => security-ldap-osgi.xml} (64%) create mode 100644 security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-services.xml rename security/modules/org.argeo.security.dao.ldap/META-INF/spring/{ldap.xml => security-ldap.xml} (78%) create mode 100644 security/plugins/org.argeo.security.ui.admin/security-admin.properties create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java delete mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SiteAuthenticationToken.java delete mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationProvider.java delete mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationToken.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java delete mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SystemSession.java delete mode 100644 security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/providers/JackrabbitAuthenticationProvider.java create mode 100644 security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java delete mode 100644 security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java diff --git a/demo/argeo_node_web.properties b/demo/argeo_node_web.properties index bece10cee..c94e740fa 100644 --- a/demo/argeo_node_web.properties +++ b/demo/argeo_node_web.properties @@ -1,6 +1,5 @@ argeo.osgi.start=\ org.springframework.osgi.extender,\ -org.argeo.security.services,\ org.argeo.node.repofactory.jackrabbit,\ org.argeo.node.repo.jackrabbit,\ org.argeo.security.dao.ldap,\ @@ -22,4 +21,7 @@ org.argeo.security.ui.initialPerspective=org.argeo.jcr.ui.explorer.perspective log4j.configuration=file:../../log4j.properties # Note default URL to access the webapp -# http://localhost:7070/org.argeo.rap.webapp/node \ No newline at end of file +# http://localhost:7070/org.argeo.rap.webapp/node + +# Useful low level debug information +com.sun.jndi.ldap.connect.pool.debug=true \ No newline at end of file diff --git a/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/jcrsecuritydao.xml b/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/jcrsecuritydao.xml deleted file mode 100644 index c09a53bf2..000000000 --- a/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/jcrsecuritydao.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/jcrsecuritydao-osgi.xml b/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-osgi.xml similarity index 83% rename from security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/jcrsecuritydao-osgi.xml rename to security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-osgi.xml index 5ab914764..53f66d558 100644 --- a/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/jcrsecuritydao-osgi.xml +++ b/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-osgi.xml @@ -10,11 +10,7 @@ http://www.springframework.org/schema/util/spring-util-2.5.xsd"> - - - + diff --git a/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/services.xml b/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml similarity index 67% rename from security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/services.xml rename to security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml index 00bb4538e..49ace03d0 100644 --- a/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/services.xml +++ b/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml @@ -19,19 +19,21 @@ - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-jcr.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-jcr.xml deleted file mode 100644 index d8a78c1b5..000000000 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-jcr.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - ${argeo.ldap.userClass} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-jcr.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-jcr.xml new file mode 100644 index 000000000..376a8e914 --- /dev/null +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-jcr.xml @@ -0,0 +1,76 @@ + + + + + + + /org/argeo/jcr/argeo.cnd + + + + + + + + + + + + ${argeo.ldap.userClass} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-osgi.xml similarity index 64% rename from security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml rename to security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-osgi.xml index f67cfb492..e8a6c0d86 100644 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-osgi.xml @@ -7,17 +7,17 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap.xml similarity index 78% rename from security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap.xml rename to security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap.xml index 1f2117d1a..8ce3081e4 100644 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap.xml +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap.xml @@ -13,36 +13,14 @@ - - - - - - - - - - - + - - - - - - - - - - @@ -56,6 +34,17 @@ + + + + + + + + + + + @@ -95,9 +84,37 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/security/modules/org.argeo.security.dao.ldap/ldap.properties b/security/modules/org.argeo.security.dao.ldap/ldap.properties index 5d7833bab..b96150b19 100644 --- a/security/modules/org.argeo.security.dao.ldap/ldap.properties +++ b/security/modules/org.argeo.security.dao.ldap/ldap.properties @@ -1,8 +1,10 @@ -argeo.node.repo.alias=node +argeo.node.repo.securityWorkspace=security argeo.security.defaultRole=ROLE_USER argeo.security.rolePrefix=ROLE_ +argeo.security.systemKey=argeo + argeo.ldap.rootdn=dc=demo,dc=argeo,dc=org argeo.ldap.protocol=ldap argeo.ldap.host=localhost diff --git a/security/modules/org.argeo.security.dao.os/META-INF/MANIFEST.MF b/security/modules/org.argeo.security.dao.os/META-INF/MANIFEST.MF index 1a72f4088..7168f03d8 100644 --- a/security/modules/org.argeo.security.dao.os/META-INF/MANIFEST.MF +++ b/security/modules/org.argeo.security.dao.os/META-INF/MANIFEST.MF @@ -5,13 +5,13 @@ Bundle-Name: Commons Security DAO OS Created-By: 1.6.0_20 (Sun Microsystems Inc.) Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-Vendor: Argeo -Bundle-Version: 0.3.4.SNAPSHOT-r20111029_170433 +Bundle-Version: 0.3.4.SNAPSHOT-r20111102_201754 Bundle-ManifestVersion: 2 Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt -Import-Package: javax.jcr,org.argeo.security,org.argeo.security.core,o - rg.argeo.security.jcr,org.springframework.beans.factory.config,org.sp - ringframework.security,org.springframework.security.adapters,org.spri - ngframework.security.providers +Import-Package: javax.jcr,org.argeo.jackrabbit,org.argeo.security,org. + argeo.security.core,org.argeo.security.jcr,org.springframework.beans. + factory.config,org.springframework.security,org.springframework.secur + ity.adapters,org.springframework.security.providers Bundle-SymbolicName: org.argeo.security.dao.os Bundle-DocURL: http://www.argeo.org diff --git a/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os-osgi.xml b/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os-osgi.xml index ba6f0bf87..4386b19c2 100644 --- a/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os-osgi.xml +++ b/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os-osgi.xml @@ -11,16 +11,7 @@ - - - - - - + filter="(argeo.jcr.repository.alias=node)" /> diff --git a/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml b/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml index 180f1fe8d..b33721883 100644 --- a/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml +++ b/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml @@ -11,31 +11,59 @@ - + + + + + /org/argeo/jcr/argeo.cnd + + + - - + - + + + + - - + + + + + + + + + + + + + - - - - + + + + + + \ No newline at end of file diff --git a/security/modules/org.argeo.security.dao.os/security.properties b/security/modules/org.argeo.security.dao.os/security.properties index beebcb5dc..ae77bf04d 100644 --- a/security/modules/org.argeo.security.dao.os/security.properties +++ b/security/modules/org.argeo.security.dao.os/security.properties @@ -1,2 +1,2 @@ argeo.security.systemKey=argeo -argeo.node.repo.alias=node +argeo.node.repo.securityWorkspace=security diff --git a/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java b/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java index dada34405..71ce5715b 100644 --- a/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java +++ b/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java @@ -11,7 +11,7 @@ import javax.security.auth.login.LoginException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.argeo.security.SiteAuthenticationToken; +import org.argeo.security.NodeAuthenticationToken; import org.springframework.security.Authentication; import org.springframework.security.AuthenticationManager; import org.springframework.security.BadCredentialsException; @@ -63,23 +63,30 @@ public class SpringLoginModule extends SecurityContextLoginModule { if (subject.getPublicCredentials() != null) subject.getPublicCredentials().clear(); + if (callbackHandler == null) + throw new LoginException("No call back handler available"); + // ask for username and password NameCallback nameCallback = new NameCallback("User"); PasswordCallback passwordCallback = new PasswordCallback( "Password", false); - - NameCallback urlCallback = new NameCallback("Site URL"); - - if (callbackHandler == null) - throw new LoginException("No call back handler available"); + final String defaultNodeUrl = "http://localhost:7070/org.argeo.jcr.webapp/remoting/node"; + final String defaultSecurityWorkspace = "security"; + NameCallback urlCallback = new NameCallback("Site URL", + defaultNodeUrl); + NameCallback securityWorkspaceCallback = new NameCallback( + "Security Workspace", defaultSecurityWorkspace); + + // handle callbacks if (remote) callbackHandler.handle(new Callback[] { nameCallback, - passwordCallback, urlCallback }); + passwordCallback, urlCallback, + securityWorkspaceCallback }); else callbackHandler.handle(new Callback[] { nameCallback, passwordCallback }); - // Set user name and password + // create credentials String username = nameCallback.getName(); if (username == null || username.trim().equals("")) return false; @@ -88,16 +95,15 @@ public class SpringLoginModule extends SecurityContextLoginModule { if (passwordCallback.getPassword() != null) password = String.valueOf(passwordCallback.getPassword()); - String url = remote ? urlCallback.getName() : null; - if (remote && (url == null || url.trim().equals(""))) - // for convenience, may be removed in the future - url = System.getProperty(NODE_REPO_URI); - - // TODO: set it via system properties - String workspace = null; - - SiteAuthenticationToken credentials = new SiteAuthenticationToken( - username, password, url, workspace); + NodeAuthenticationToken credentials; + if (remote) { + String url = urlCallback.getName(); + String workspace = securityWorkspaceCallback.getName(); + credentials = new NodeAuthenticationToken(username, password, + url, workspace); + } else { + credentials = new NodeAuthenticationToken(username, password); + } Authentication authentication; try { diff --git a/security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml b/security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml index 3a6f8141c..5b64119f8 100644 --- a/security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml +++ b/security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml @@ -3,7 +3,16 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> + + + + osgibundle:security-admin.properties + + + + \ No newline at end of file diff --git a/security/plugins/org.argeo.security.ui.admin/build.properties b/security/plugins/org.argeo.security.ui.admin/build.properties index b840a9456..c41623e1f 100644 --- a/security/plugins/org.argeo.security.ui.admin/build.properties +++ b/security/plugins/org.argeo.security.ui.admin/build.properties @@ -1,4 +1,5 @@ bin.includes = plugin.xml,\ - META-INF/ + META-INF/,\ + security-admin.properties source.. = src/main/java/ output.. = target/classes/ diff --git a/security/plugins/org.argeo.security.ui.admin/security-admin.properties b/security/plugins/org.argeo.security.ui.admin/security-admin.properties new file mode 100644 index 000000000..92cfc6371 --- /dev/null +++ b/security/plugins/org.argeo.security.ui.admin/security-admin.properties @@ -0,0 +1 @@ +argeo.node.repo.securityWorkspace=security diff --git a/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/editors/DefaultUserMainPage.java b/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/editors/DefaultUserMainPage.java index 5a20377ca..358135419 100644 --- a/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/editors/DefaultUserMainPage.java +++ b/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/editors/DefaultUserMainPage.java @@ -49,13 +49,6 @@ public class DefaultUserMainPage extends FormPage implements ArgeoNames { form.setText(getProperty(ARGEO_FIRST_NAME) + " " + getProperty(ARGEO_LAST_NAME)); GridLayout mainLayout = new GridLayout(1, true); - // ColumnLayout mainLayout = new ColumnLayout(); - // mainLayout.minNumColumns = 1; - // mainLayout.maxNumColumns = 4; - // mainLayout.topMargin = 0; - // mainLayout.bottomMargin = 5; - // mainLayout.leftMargin = mainLayout.rightMargin = - // mainLayout.horizontalSpacing = mainLayout.verticalSpacing = 10; form.getBody().setLayout(mainLayout); createGeneralPart(form.getBody()); @@ -78,15 +71,6 @@ public class DefaultUserMainPage extends FormPage implements ArgeoNames { body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); body.setLayout(layout); - // add widgets (view) - // final Text username; - // if (user.getUsername() != null) { - // tk.createLabel(body, "Username"); - // tk.createLabel(body, user.getUsername()); - // username = null; - // } else { - // username = createLT(body, "Username", ""); - // } final Text firstName = createLT(body, "First name", getProperty(ARGEO_FIRST_NAME)); final Text lastName = createLT(body, "Last name", @@ -99,16 +83,9 @@ public class DefaultUserMainPage extends FormPage implements ArgeoNames { // create form part (controller) AbstractFormPart part = new SectionPart(section) { public void commit(boolean onSave) { - // if (username != null) { - // ((SimpleArgeoUser) user).setUsername(username.getText()); - // username.setEditable(false); - // username.setEnabled(false); - // } - // simpleNature.setFirstName(firstName.getText()); - // simpleNature.setLastName(lastName.getText()); - // simpleNature.setEmail(email.getText()); - // simpleNature.setDescription(description.getText()); try { + userProfile.getSession().getWorkspace().getVersionManager() + .checkout(userProfile.getPath()); userProfile.setProperty(ARGEO_FIRST_NAME, firstName.getText()); userProfile @@ -118,6 +95,8 @@ public class DefaultUserMainPage extends FormPage implements ArgeoNames { userProfile.setProperty(Property.JCR_DESCRIPTION, description.getText()); userProfile.getSession().save(); + userProfile.getSession().getWorkspace().getVersionManager() + .checkin(userProfile.getPath()); super.commit(onSave); if (log.isTraceEnabled()) log.trace("General part committed"); diff --git a/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/wizards/NewUserWizard.java b/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/wizards/NewUserWizard.java index b99570c20..aa1351815 100644 --- a/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/wizards/NewUserWizard.java +++ b/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/wizards/NewUserWizard.java @@ -6,8 +6,7 @@ import javax.jcr.Session; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.argeo.eclipse.ui.dialogs.Error; -import org.argeo.jcr.ArgeoNames; +import org.argeo.eclipse.ui.ErrorFeedback; import org.argeo.jcr.JcrUtils; import org.argeo.security.UserAdminService; import org.argeo.security.jcr.JcrUserDetails; @@ -17,8 +16,6 @@ import org.springframework.security.GrantedAuthority; /** Wizard to create a new user */ public class NewUserWizard extends Wizard { private final static Log log = LogFactory.getLog(NewUserWizard.class); - - private String homeBasePath = "/home"; private Session session; private UserAdminService userAdminService; @@ -43,16 +40,17 @@ public class NewUserWizard extends Wizard { String username = mainUserInfo.getUsername(); try { - session.save(); - Node userHome = JcrUtils.createUserHome(session, homeBasePath, - username); - Node userProfile = userHome.getNode(ArgeoNames.ARGEO_PROFILE); + Node userProfile = JcrUtils.createUserProfile(session, username); + session.getWorkspace().getVersionManager() + .checkout(userProfile.getPath()); mainUserInfo.mapToProfileNode(userProfile); String password = mainUserInfo.getPassword(); - JcrUserDetails jcrUserDetails = new JcrUserDetails( - userHome.getPath(), username, password, true, true, true, - true, new GrantedAuthority[0]); + // 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) { @@ -68,7 +66,7 @@ public class NewUserWizard extends Wizard { + username, e1); } } - Error.show("Cannot create new user " + username, e); + ErrorFeedback.show("Cannot create new user " + username, e); return false; } } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java new file mode 100644 index 000000000..17b1d48bc --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java @@ -0,0 +1,47 @@ +package org.argeo.security; + +import org.springframework.security.GrantedAuthority; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; + +/** Credentials required for the authentication to a node. */ +public class NodeAuthenticationToken extends + UsernamePasswordAuthenticationToken { + private static final long serialVersionUID = 1955222132884795213L; + private final String url; + private final String securityWorkspace; + + /** Non authenticated local constructor */ + public NodeAuthenticationToken(Object principal, Object credentials) { + super(principal, credentials); + this.url = null; + this.securityWorkspace = null; + } + + /** Non authenticated remote constructor */ + public NodeAuthenticationToken(Object principal, Object credentials, + String url, String workspace) { + super(principal, credentials); + this.url = url; + this.securityWorkspace = workspace; + } + + /** Authenticated constructor */ + public NodeAuthenticationToken(NodeAuthenticationToken sat, + GrantedAuthority[] authorities) { + super(sat.getPrincipal(), sat.getCredentials(), authorities); + this.url = sat.getUrl(); + this.securityWorkspace = sat.getSecurityWorkspace(); + } + + public String getUrl() { + return url; + } + + public String getSecurityWorkspace() { + return securityWorkspace; + } + + public Boolean isRemote() { + return url != null; + } +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/OsAuthenticationToken.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/OsAuthenticationToken.java index 9fba6f054..61ec539c6 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/OsAuthenticationToken.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/OsAuthenticationToken.java @@ -4,7 +4,6 @@ import java.security.AccessController; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Set; @@ -76,6 +75,10 @@ public class OsAuthenticationToken implements Authentication { return getUser().getName(); } + /** + * Should not be called during authentication since group IDs are not yet + * available {@link Subject} has been set + */ public GrantedAuthority[] getAuthorities() { // grantedAuthorities should not be null at this stage List gas = new ArrayList( @@ -121,7 +124,7 @@ public class OsAuthenticationToken implements Authentication { } public Principal getUser() { - Subject subject = Subject.getSubject(AccessController.getContext()); + Subject subject = getSubject(); Set userPrincipals = subject .getPrincipals(osUserPrincipalClass); if (userPrincipals == null || userPrincipals.size() == 0) @@ -133,7 +136,7 @@ public class OsAuthenticationToken implements Authentication { } public Principal getUserId() { - Subject subject = Subject.getSubject(AccessController.getContext()); + Subject subject = getSubject(); Set userIdsPrincipals = subject .getPrincipals(osUserIdPrincipalClass); if (userIdsPrincipals == null || userIdsPrincipals.size() == 0) @@ -145,11 +148,19 @@ public class OsAuthenticationToken implements Authentication { } public Set getGroupsIds() { - Subject subject = Subject.getSubject(AccessController.getContext()); + Subject subject = getSubject(); return (Set) subject .getPrincipals(osGroupIdPrincipalClass); } + /** @return the subject always non null */ + protected Subject getSubject() { + Subject subject = Subject.getSubject(AccessController.getContext()); + if (subject == null) + throw new ArgeoException("No subject in JAAS context"); + return subject; + } + public Object getCredentials() { return ""; } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SiteAuthenticationToken.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SiteAuthenticationToken.java deleted file mode 100644 index d836b6f7a..000000000 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SiteAuthenticationToken.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.security; - -import org.springframework.security.GrantedAuthority; -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; - -public class SiteAuthenticationToken extends - UsernamePasswordAuthenticationToken { - private static final long serialVersionUID = 1955222132884795213L; - private final String url; - private final String workspace; - - public SiteAuthenticationToken(Object principal, Object credentials, - String url, String workspace) { - super(principal, credentials); - this.url = url; - this.workspace = workspace; - } - - public SiteAuthenticationToken(Object principal, Object credentials, - GrantedAuthority[] authorities, String url, String workspace) { - super(principal, credentials, authorities); - this.url = url; - this.workspace = workspace; - } - - public String getUrl() { - return url; - } - - public String getWorkspace() { - return workspace; - } - -} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/UserAdminService.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/UserAdminService.java index 8844534c3..01e7349cc 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/UserAdminService.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/UserAdminService.java @@ -4,6 +4,7 @@ import java.util.Set; import org.springframework.security.userdetails.UserDetailsManager; +/** Enrich {@link UserDetailsManager} in order to provide roles semantics. */ public interface UserAdminService extends UserDetailsManager { /** * Usernames must match this regexp pattern ({@value #USERNAME_PATTERN}). diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/OsAuthenticationProvider.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/OsAuthenticationProvider.java index 524e73f8f..3360b1eea 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/OsAuthenticationProvider.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/OsAuthenticationProvider.java @@ -10,7 +10,12 @@ import org.springframework.security.GrantedAuthority; import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.providers.AuthenticationProvider; -/** Validates an OS authentication. */ +/** + * Validates an OS authentication. The id is that it will always be + * authenticated since we are always runnign within an OS, but the fact that the + * {@link Authentication} works properly depends on the proper OS login module + * having been called as well. + */ public class OsAuthenticationProvider implements AuthenticationProvider { private String osUserRole = "ROLE_OS_USER"; private String userRole = "ROLE_USER"; @@ -20,16 +25,16 @@ public class OsAuthenticationProvider implements AuthenticationProvider { public Authentication authenticate(Authentication authentication) throws AuthenticationException { - if (!(authentication instanceof OsAuthenticationToken)) - return null; + return new OsAuthenticationToken(getBaseAuthorities()); + } + protected GrantedAuthority[] getBaseAuthorities() { List auths = new ArrayList(); auths.add(new GrantedAuthorityImpl(osUserRole)); auths.add(new GrantedAuthorityImpl(userRole)); if (isAdmin) auths.add(new GrantedAuthorityImpl(adminRole)); - return new OsAuthenticationToken( - auths.toArray(new GrantedAuthority[auths.size()])); + return auths.toArray(new GrantedAuthority[auths.size()]); } @SuppressWarnings("rawtypes") diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationProvider.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationProvider.java deleted file mode 100644 index c19e709a1..000000000 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationProvider.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.argeo.security.jcr; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.jcr.Credentials; -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; -import javax.jcr.Value; - -import org.argeo.ArgeoException; -import org.argeo.jcr.ArgeoJcrConstants; -import org.argeo.jcr.ArgeoNames; -import org.argeo.jcr.JcrUtils; -import org.argeo.security.SiteAuthenticationToken; -import org.springframework.security.Authentication; -import org.springframework.security.AuthenticationException; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.GrantedAuthorityImpl; -import org.springframework.security.providers.AuthenticationProvider; -import org.springframework.security.userdetails.UserDetails; - -/** Connects to a JCR repository and delegates authentication to it. */ -public class JcrAuthenticationProvider implements AuthenticationProvider { - public final static String ROLE_REMOTE_JCR_AUTHENTICATED = "ROLE_REMOTE_JCR_AUTHENTICATED"; - - private RepositoryFactory repositoryFactory; - - public Authentication authenticate(Authentication authentication) - throws AuthenticationException { - if (!(authentication instanceof SiteAuthenticationToken)) - return null; - SiteAuthenticationToken siteAuth = (SiteAuthenticationToken) authentication; - String url = siteAuth.getUrl(); - if (url == null) - return null; - - try { - SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(), - siteAuth.getCredentials().toString().toCharArray()); - // get repository - Repository repository = getRepository(url, sp); - if (repository == null) - return null; - - String workspace = siteAuth.getWorkspace(); - Session session; - if (workspace == null || workspace.trim().equals("")) - session = repository.login(sp); - else - session = repository.login(sp, workspace); - - Node userHome = JcrUtils.getUserHome(session); - - // retrieve remote roles - Node userProfile = JcrUtils.getUserProfile(session); - List authorities = new ArrayList(); - if (userProfile.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { - Value[] roles = userProfile.getProperty( - ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); - for (int i = 0; i < roles.length; i++) - authorities.add(new GrantedAuthorityImpl(roles[i] - .getString())); - } - JcrAuthenticationToken authen = new JcrAuthenticationToken( - siteAuth.getPrincipal(), - siteAuth.getCredentials(), - authorities.toArray(new GrantedAuthority[authorities.size()]), - url, userHome); - authen.setDetails(getUserDetails(userHome, authen)); - - return authen; - } catch (RepositoryException e) { - throw new ArgeoException( - "Unexpected exception when authenticating to " + url, e); - } - } - - protected Repository getRepository(String url, Credentials credentials) - throws RepositoryException { - Map parameters = new HashMap(); - parameters.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url); - return repositoryFactory.getRepository(parameters); - } - - /** - * By default, assigns only the role {@value #ROLE_REMOTE_JCR_AUTHENTICATED} - * . Should typically be overridden in order to assign more relevant roles. - */ - protected GrantedAuthority[] getGrantedAuthorities(Session session) { - return new GrantedAuthority[] { new GrantedAuthorityImpl( - ROLE_REMOTE_JCR_AUTHENTICATED) }; - } - - /** Builds user details based on the authentication and the user home. */ - protected UserDetails getUserDetails(Node userHome, Authentication authen) { - try { - // TODO: loads enabled, locked, etc. from the home node. - return new JcrUserDetails(userHome.getPath(), authen.getPrincipal() - .toString(), authen.getCredentials().toString(), - isEnabled(userHome), true, true, true, - authen.getAuthorities()); - } catch (Exception e) { - throw new ArgeoException("Cannot get user details for " + userHome, - e); - } - } - - protected Boolean isEnabled(Node userHome) { - return true; - } - - @SuppressWarnings("rawtypes") - public boolean supports(Class authentication) { - return SiteAuthenticationToken.class.isAssignableFrom(authentication); - } - - public void register(RepositoryFactory repositoryFactory, - Map parameters) { - this.repositoryFactory = repositoryFactory; - } - - public void unregister(RepositoryFactory repositoryFactory, - Map parameters) { - this.repositoryFactory = null; - } -} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationToken.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationToken.java deleted file mode 100644 index 27b7ee85b..000000000 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationToken.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.argeo.security.jcr; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.ArgeoException; -import org.argeo.security.SiteAuthenticationToken; -import org.springframework.security.GrantedAuthority; - -/** An authenticated authentication based on a JCR session. */ -public class JcrAuthenticationToken extends SiteAuthenticationToken { - private static final long serialVersionUID = -2736830165315486169L; - - private final transient Session session; - private final String userHomePath; - - public JcrAuthenticationToken(Object principal, Object credentials, - GrantedAuthority[] authorities, String url, Node userHome) { - super(principal, credentials, authorities, url, - extractWorkspace(userHome)); - try { - this.session = userHome.getSession(); - this.userHomePath = userHome.getPath(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot extract path from " + userHome, e); - } - } - - private static String extractWorkspace(Node userHome) { - try { - return userHome.getSession().getWorkspace().getName(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot extract workspace from " - + userHome, e); - } - } - - /** The path to the authenticated user home node. */ - public String getUserHomePath() { - return userHomePath; - } - - /** The session used to create this authentication. */ - public Session getSession() { - return session; - } - - @Override - public boolean isAuthenticated() { - if (session == null || !session.isLive()) - setAuthenticated(false); - return super.isAuthenticated(); - } - - @Override - public void setAuthenticated(boolean isAuthenticated) - throws IllegalArgumentException { - super.setAuthenticated(isAuthenticated); - if (!isAuthenticated && session != null) - session.logout(); - } - -} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java index 11e463d34..a59eabc0a 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java @@ -3,22 +3,63 @@ package org.argeo.security.jcr; import java.util.ArrayList; import java.util.List; +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.jcr.ArgeoNames; +import org.springframework.security.BadCredentialsException; +import org.springframework.security.DisabledException; import org.springframework.security.GrantedAuthority; import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.LockedException; import org.springframework.security.userdetails.User; -/** User details wrapping a home node. */ -public class JcrUserDetails extends User { - private static final long serialVersionUID = -3594542993773402380L; +/** User details based on a user profile node. */ +public class JcrUserDetails extends User implements ArgeoNames { + private static final long serialVersionUID = -8142764995842559646L; private final String homePath; + private final String securityWorkspace; - public JcrUserDetails(String homePath, String username, String password, - boolean enabled, boolean accountNonExpired, - boolean credentialsNonExpired, boolean accountNonLocked, - GrantedAuthority[] authorities) throws IllegalArgumentException { + protected JcrUserDetails(String securityWorkspace, String homePath, + String username, String password, boolean enabled, + boolean accountNonExpired, boolean credentialsNonExpired, + boolean accountNonLocked, GrantedAuthority[] authorities) + throws IllegalArgumentException { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); this.homePath = homePath; + this.securityWorkspace = securityWorkspace; + } + + public JcrUserDetails(Node userProfile, String password, + GrantedAuthority[] authorities) throws RepositoryException { + super( + userProfile.getProperty(ARGEO_USER_ID).getString(), + password, + userProfile.getProperty(ARGEO_ENABLED).getBoolean(), + userProfile.getProperty(ARGEO_ACCOUNT_NON_EXPIRED).getBoolean(), + userProfile.getProperty(ARGEO_CREDENTIALS_NON_EXPIRED) + .getBoolean(), userProfile.getProperty( + ARGEO_ACCOUNT_NON_LOCKED).getBoolean(), authorities); + // home is defined as the parent of the profile + homePath = userProfile.getParent().getPath(); + securityWorkspace = userProfile.getSession().getWorkspace().getName(); + } + + /** + * Check the account status in JCR, throwing the exceptions expected by + * Spring security if needed. + */ + public static void checkAccountStatus(Node userProfile) { + try { + if (!userProfile.getProperty(ARGEO_ENABLED).getBoolean()) + throw new DisabledException(userProfile.getPath() + + " is disabled"); + if (!userProfile.getProperty(ARGEO_ACCOUNT_NON_LOCKED).getBoolean()) + throw new LockedException(userProfile.getPath() + " is locked"); + } catch (RepositoryException e) { + throw new BadCredentialsException("Cannot check account status", e); + } } /** Clone immutable with new roles */ @@ -27,21 +68,25 @@ public class JcrUserDetails extends User { for (String role : roles) { authorities.add(new GrantedAuthorityImpl(role)); } - return new JcrUserDetails(homePath, getUsername(), getPassword(), - isEnabled(), isAccountNonExpired(), isAccountNonExpired(), - isAccountNonLocked(), + return new JcrUserDetails(securityWorkspace, homePath, getUsername(), + getPassword(), isEnabled(), isAccountNonExpired(), + isAccountNonExpired(), isAccountNonLocked(), authorities.toArray(new GrantedAuthority[authorities.size()])); } /** Clone immutable with new password */ public JcrUserDetails cloneWithNewPassword(String password) { - return new JcrUserDetails(homePath, getUsername(), password, - isEnabled(), isAccountNonExpired(), isAccountNonExpired(), - isAccountNonLocked(), getAuthorities()); + return new JcrUserDetails(securityWorkspace, homePath, getUsername(), + password, isEnabled(), isAccountNonExpired(), + isAccountNonExpired(), isAccountNonLocked(), getAuthorities()); } public String getHomePath() { return homePath; } + /** Not yet API */ + public String getSecurityWorkspace() { + return securityWorkspace; + } } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java index 9abac5972..e6f90b165 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java @@ -1,124 +1,78 @@ package org.argeo.security.jcr; -import java.util.Map; -import java.util.concurrent.Executor; - import javax.jcr.Node; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; +import javax.jcr.version.VersionManager; import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoNames; import org.argeo.jcr.JcrUtils; import org.argeo.security.OsAuthenticationToken; -import org.argeo.security.SystemExecutionService; import org.argeo.security.core.OsAuthenticationProvider; import org.springframework.security.Authentication; import org.springframework.security.AuthenticationException; -import org.springframework.security.userdetails.UserDetails; +/** Relies on OS to authenticate and additionaly setup JCR */ public class OsJcrAuthenticationProvider extends OsAuthenticationProvider { - private Executor systemExecutor; - private String homeBasePath = "/home"; private Repository repository; - private String workspace = null; + private String securityWorkspace = "security"; + private Session securitySession; + + public void init() { + try { + securitySession = repository.login(securityWorkspace); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot initialize", e); + } + } - private Long timeout = 5 * 60 * 1000l; + public void destroy() { + JcrUtils.logoutQuietly(securitySession); + } public Authentication authenticate(Authentication authentication) throws AuthenticationException { final OsAuthenticationToken authen = (OsAuthenticationToken) super .authenticate(authentication); - final Repository repository = getRepositoryBlocking(); - systemExecutor.execute(new Runnable() { - public void run() { - Session session = null; - try { - session = repository.login(workspace); - // WARNING: at this stage we assume that the java properties - // will have the same value - String userName = System.getProperty("user.name"); - Node userHome = JcrUtils.getUserHome(session, userName); - if (userHome == null) - userHome = JcrUtils.createUserHome(session, - homeBasePath, userName); - // authen.setDetails(getUserDetails(userHome, authen)); - } catch (RepositoryException e) { - JcrUtils.discardQuietly(session); - throw new ArgeoException( - "Unexpected exception when synchronizing OS and JCR security ", - e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - }); - return authen; - } - - /** Builds user details based on the authentication and the user home. */ - protected UserDetails getUserDetails(Node userHome, Authentication authen) { try { - // TODO: loads enabled, locked, etc. from the home node. - return new JcrUserDetails(userHome.getPath(), authen.getPrincipal() - .toString(), authen.getCredentials().toString(), - isEnabled(userHome), true, true, true, - authen.getAuthorities()); - } catch (Exception e) { - throw new ArgeoException("Cannot get user details for " + userHome, + // WARNING: at this stage we assume that the java properties + // will have the same value + String username = System.getProperty("user.name"); + Node userHome = JcrUtils.createUserHomeIfNeeded(securitySession, + username); + Node userProfile = userHome.hasNode(ArgeoNames.ARGEO_PROFILE) ? userHome + .getNode(ArgeoNames.ARGEO_PROFILE) : JcrUtils + .createUserProfile(securitySession, username); + if (securitySession.hasPendingChanges()) + securitySession.save(); + VersionManager versionManager = securitySession.getWorkspace() + .getVersionManager(); + if (versionManager.isCheckedOut(userProfile.getPath())) + versionManager.checkin(userProfile.getPath()); + + JcrUserDetails.checkAccountStatus(userProfile); + // user details + JcrUserDetails userDetails = new JcrUserDetails(userProfile, authen + .getCredentials().toString(), getBaseAuthorities()); + authen.setDetails(userDetails); + } catch (RepositoryException e) { + JcrUtils.discardQuietly(securitySession); + throw new ArgeoException( + "Unexpected exception when synchronizing OS and JCR security ", e); + } finally { + JcrUtils.logoutQuietly(securitySession); } + return authen; } - protected Boolean isEnabled(Node userHome) { - return true; - } - - protected Repository getRepositoryBlocking() { - long begin = System.currentTimeMillis(); - while (repository == null) { - synchronized (this) { - try { - wait(500); - } catch (InterruptedException e) { - // silent - } - } - if (System.currentTimeMillis() - begin > timeout) - throw new ArgeoException("No repository registered after " - + timeout + " ms"); - } - return repository; + public void setSecurityWorkspace(String securityWorkspace) { + this.securityWorkspace = securityWorkspace; } - public synchronized void register(Repository repository, - Map parameters) { + public void setRepository(Repository repository) { this.repository = repository; - notifyAll(); - } - - public synchronized void unregister(Repository repository, - Map parameters) { - this.repository = null; - notifyAll(); - } - - public void register(SystemExecutionService systemExecutor, - Map parameters) { - this.systemExecutor = systemExecutor; - } - - public void unregister(SystemExecutionService systemExecutor, - Map parameters) { - this.systemExecutor = null; - } - - public void setHomeBasePath(String homeBasePath) { - this.homeBasePath = homeBasePath; } - - public void setWorkspace(String workspace) { - this.workspace = workspace; - } - } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java new file mode 100644 index 000000000..76c1be2da --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java @@ -0,0 +1,108 @@ +package org.argeo.security.jcr; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.jcr.Credentials; +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; +import javax.jcr.Value; + +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.JcrUtils; +import org.argeo.security.NodeAuthenticationToken; +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationException; +import org.springframework.security.BadCredentialsException; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.providers.AuthenticationProvider; + +/** Connects to a JCR repository and delegates authentication to it. */ +public class RemoteJcrAuthenticationProvider implements AuthenticationProvider, + ArgeoNames { + private RepositoryFactory repositoryFactory; + + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication; + String url = siteAuth.getUrl(); + if (url == null) + return null; + Session session; + Node userProfile; + + try { + SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(), + siteAuth.getCredentials().toString().toCharArray()); + // get repository + Repository repository = getRepository(url, sp); + if (repository == null) + return null; + + String workspace = siteAuth.getSecurityWorkspace(); + session = repository.login(sp, workspace); + Node userHome = JcrUtils.getUserHome(session); + if (userHome == null || !userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) + throw new ArgeoException("No profile for user " + + siteAuth.getName() + " in security workspace " + + siteAuth.getSecurityWorkspace() + " of " + + siteAuth.getUrl()); + userProfile = userHome.getNode(ArgeoNames.ARGEO_PROFILE); + } catch (RepositoryException e) { + throw new BadCredentialsException( + "Cannot authenticate " + siteAuth, e); + } + + try { + JcrUserDetails.checkAccountStatus(userProfile); + // retrieve remote roles + List authoritiesList = new ArrayList(); + if (userProfile.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { + Value[] roles = userProfile.getProperty( + ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); + for (int i = 0; i < roles.length; i++) + authoritiesList.add(new GrantedAuthorityImpl(roles[i] + .getString())); + } + + // create authenticated objects + GrantedAuthority[] authorities = authoritiesList + .toArray(new GrantedAuthority[authoritiesList.size()]); + JcrUserDetails userDetails = new JcrUserDetails(userProfile, + siteAuth.getCredentials().toString(), authorities); + NodeAuthenticationToken authenticated = new NodeAuthenticationToken( + siteAuth, authorities); + authenticated.setDetails(userDetails); + return authenticated; + } catch (RepositoryException e) { + throw new ArgeoException( + "Unexpected exception when authenticating to " + url, e); + } + } + + protected Repository getRepository(String url, Credentials credentials) + throws RepositoryException { + Map parameters = new HashMap(); + parameters.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url); + return repositoryFactory.getRepository(parameters); + } + + @SuppressWarnings("rawtypes") + public boolean supports(Class authentication) { + return NodeAuthenticationToken.class.isAssignableFrom(authentication); + } + + public void setRepositoryFactory(RepositoryFactory repositoryFactory) { + this.repositoryFactory = repositoryFactory; + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java index 1220eb381..44b0fa345 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java @@ -33,18 +33,6 @@ public class SecureThreadBoundSession extends ThreadBoundSession { return login(); } } - // UserDetails userDetails = (UserDetails) - // authentication.getDetails(); - // if (userDetails != null) { - // String currentUserName = userDetails.getUsername(); - // if (!userID.equals(currentUserName)) { - // log.warn("Current session has user ID " + userID - // + " while logged is user is " + currentUserName - // + "(authentication=" + authentication + ")" - // + ". Re-login."); - // return login(); - // } - // } } return super.preCall(session); } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SystemSession.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SystemSession.java deleted file mode 100644 index 62b0f91aa..000000000 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SystemSession.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.security.jcr; - -import java.util.concurrent.Callable; - -import javax.jcr.Session; - -import org.argeo.ArgeoException; -import org.argeo.jcr.spring.ThreadBoundSession; -import org.argeo.security.SystemExecutionService; - -/** Thread bounded JCR session which logins as system authentication. */ -public class SystemSession extends ThreadBoundSession { - private SystemExecutionService systemExecutionService; - - @Override - protected Session login() { - try { - return systemExecutionService.submit(new Callable() { - public Session call() throws Exception { - return SystemSession.super.login(); - } - }).get(); - } catch (Exception e) { - throw new ArgeoException( - "Cannot login to JCR with system authentication", e); - } - } - - public void setSystemExecutionService( - SystemExecutionService systemExecutionService) { - this.systemExecutionService = systemExecutionService; - } - -} diff --git a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java index 8f2632d0f..2153e0e94 100644 --- a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java @@ -36,8 +36,6 @@ import org.springframework.security.GrantedAuthority; /** Intermediary class in order to have a consistent naming in config files. */ public class ArgeoSecurityManager extends DefaultSecurityManager { - public final static String HOME_BASE_PATH = "/home"; - private Log log = LogFactory.getLog(ArgeoSecurityManager.class); @Override @@ -142,8 +140,7 @@ public class ArgeoSecurityManager extends DefaultSecurityManager { try { userHome = JcrUtils.getUserHome(getSystemSession(), userId); if (userHome == null) { - userHome = JcrUtils.createUserHome(getSystemSession(), - HOME_BASE_PATH, userId); + userHome = JcrUtils.createUserHomeIfNeeded(getSystemSession(), userId); //log.warn("No home available for user "+userId); return; } diff --git a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/providers/JackrabbitAuthenticationProvider.java b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/providers/JackrabbitAuthenticationProvider.java deleted file mode 100644 index ea84a073e..000000000 --- a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/providers/JackrabbitAuthenticationProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.security.jackrabbit.providers; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Properties; - -import javax.jcr.Credentials; -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.apache.jackrabbit.api.JackrabbitSession; -import org.apache.jackrabbit.api.security.user.Group; -import org.apache.jackrabbit.api.security.user.User; -import org.apache.jackrabbit.api.security.user.UserManager; -import org.argeo.ArgeoException; -import org.argeo.jackrabbit.JackrabbitContainer; -import org.argeo.jcr.ArgeoJcrConstants; -import org.argeo.security.jcr.JcrAuthenticationProvider; -import org.osgi.framework.BundleContext; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.GrantedAuthorityImpl; - -public class JackrabbitAuthenticationProvider extends JcrAuthenticationProvider { - // @Override - // protected Repository getRepository(String url, Credentials credentials) - // throws RepositoryException { - // JackrabbitContainer repository = new JackrabbitContainer(); - // repository.setUri(url); - // repository.setRemoteSystemCredentials(credentials); - // repository.init(); - // if (bundleContext != null) { - // // FIXME check if not already a node - // Properties properties = new Properties(); - // properties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, - // ArgeoJcrConstants.ALIAS_NODE); - // bundleContext.registerService(Repository.class.getName(), - // repository, properties); - // } - // return repository; - // } - - @Override - protected GrantedAuthority[] getGrantedAuthorities(Session session) { - try { - if (!(session instanceof JackrabbitSession)) - return super.getGrantedAuthorities(session); - - JackrabbitSession jackrabbitSession = (JackrabbitSession) session; - UserManager userManager = jackrabbitSession.getUserManager(); - User user = (User) userManager.getAuthorizable(session.getUserID()); - List authorities = new ArrayList(); - for (Iterator it = user.memberOf(); it.hasNext();) - authorities.add(new GrantedAuthorityImpl(it.next().getID())); - return authorities - .toArray(new GrantedAuthority[authorities.size()]); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot retrieve authorities for " - + session.getUserID(), e); - } - } - - @Override - protected Boolean isEnabled(Node userHome) { - try { - if (!(userHome.getSession() instanceof JackrabbitSession)) - return super.isEnabled(userHome); - - UserManager userManager = ((JackrabbitSession) userHome - .getSession()).getUserManager(); - User user = (User) userManager.getAuthorizable(userHome - .getSession().getUserID()); - return !user.isDisabled(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot check whether " + userHome - + " is enabled", e); - } - } - -} diff --git a/security/runtime/org.argeo.security.ldap/.classpath b/security/runtime/org.argeo.security.ldap/.classpath index 92f19d2ff..8b978d9ed 100644 --- a/security/runtime/org.argeo.security.ldap/.classpath +++ b/security/runtime/org.argeo.security.ldap/.classpath @@ -1,7 +1,7 @@ + - diff --git a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java new file mode 100644 index 000000000..3a644a693 --- /dev/null +++ b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java @@ -0,0 +1,568 @@ +package org.argeo.security.ldap.jcr; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.SortedSet; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; +import javax.jcr.observation.EventListener; +import javax.jcr.query.Query; +import javax.naming.Binding; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.event.EventDirContext; +import javax.naming.event.NamespaceChangeListener; +import javax.naming.event.NamingEvent; +import javax.naming.event.NamingExceptionEvent; +import javax.naming.event.NamingListener; +import javax.naming.event.ObjectChangeListener; +import javax.naming.ldap.UnsolicitedNotification; +import javax.naming.ldap.UnsolicitedNotificationEvent; +import javax.naming.ldap.UnsolicitedNotificationListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.jcr.JcrUtils; +import org.argeo.security.jcr.JcrUserDetails; +import org.springframework.ldap.core.ContextExecutor; +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.ldap.LdapUsernameToDnMapper; +import org.springframework.security.providers.encoding.PasswordEncoder; +import org.springframework.security.userdetails.UserDetails; +import org.springframework.security.userdetails.ldap.UserDetailsContextMapper; + +/** Guarantees that LDAP and JCR are in line. */ +public class JcrLdapSynchronizer implements UserDetailsContextMapper, + ArgeoNames { + private final static Log log = LogFactory.getLog(JcrLdapSynchronizer.class); + + // LDAP + private LdapTemplate ldapTemplate; + /** + * LDAP template whose context source has an object factory set to null. see + * this + */ + private LdapTemplate rawLdapTemplate; + + private String userBase; + private String usernameAttribute; + private String passwordAttribute; + private String[] userClasses; + + private NamingListener ldapUserListener; + private SearchControls subTreeSearchControls; + private LdapUsernameToDnMapper usernameMapper; + + private PasswordEncoder passwordEncoder; + private final Random random; + + // JCR + /** Admin session on the security workspace */ + private Session securitySession; + private Repository repository; + + private String securityWorkspace = "security"; + + private JcrProfileListener jcrProfileListener; + + // Mapping + private Map propertyToAttributes = new HashMap(); + + public JcrLdapSynchronizer() { + random = createRandom(); + } + + public void init() { + try { + securitySession = repository.login(securityWorkspace); + + synchronize(); + + // LDAP + subTreeSearchControls = new SearchControls(); + subTreeSearchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + // LDAP listener + ldapUserListener = new LdapUserListener(); + rawLdapTemplate.executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws NamingException { + EventDirContext ectx = (EventDirContext) ctx.lookup(""); + ectx.addNamingListener(userBase, "(" + usernameAttribute + + "=*)", subTreeSearchControls, ldapUserListener); + return null; + } + }); + + // JCR + String[] nodeTypes = { ArgeoTypes.ARGEO_USER_PROFILE }; + jcrProfileListener = new JcrProfileListener(); + // noLocal is used so that we are not notified when we modify JCR + // from LDAP + securitySession + .getWorkspace() + .getObservationManager() + .addEventListener(jcrProfileListener, + Event.PROPERTY_CHANGED | Event.NODE_ADDED, "/", + true, null, nodeTypes, true); + } catch (Exception e) { + JcrUtils.logoutQuietly(securitySession); + throw new ArgeoException("Cannot initialize LDAP/JCR synchronizer", + e); + } + } + + public void destroy() { + JcrUtils.removeListenerQuietly(securitySession, jcrProfileListener); + JcrUtils.logoutQuietly(securitySession); + try { + rawLdapTemplate.executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) + throws NamingException { + EventDirContext ectx = (EventDirContext) ctx.lookup(""); + ectx.removeNamingListener(ldapUserListener); + return null; + } + }); + } catch (Exception e) { + // silent (LDAP server may have been shutdown already) + if (log.isTraceEnabled()) + log.trace("Cannot remove LDAP listener", e); + } + } + + /* + * LDAP TO JCR + */ + /** Full synchronization between LDAP and JCR. LDAP has priority. */ + protected void synchronize() { + try { + Name userBaseName = new DistinguishedName(userBase); + // TODO subtree search? + @SuppressWarnings("unchecked") + List userPaths = (List) ldapTemplate.listBindings( + userBaseName, new ContextMapper() { + public Object mapFromContext(Object ctxObj) { + return mapLdapToJcr((DirContextAdapter) ctxObj); + } + }); + + // disable accounts which are not in LDAP + Query query = securitySession + .getWorkspace() + .getQueryManager() + .createQuery( + "select * from [" + ArgeoTypes.ARGEO_USER_PROFILE + + "]", Query.JCR_SQL2); + NodeIterator it = query.execute().getNodes(); + while (it.hasNext()) { + Node userProfile = it.nextNode(); + String path = userProfile.getPath(); + if (!userPaths.contains(path)) { + userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, false); + } + } + } catch (Exception e) { + throw new ArgeoException("Cannot synchronized LDAP and JCR", e); + } + } + + /** Called during authentication in order to retrieve user details */ + public UserDetails mapUserFromContext(final DirContextOperations ctx, + final String username, GrantedAuthority[] authorities) { + if (ctx == null) + throw new ArgeoException("No LDAP information for user " + username); + Node userHome = JcrUtils.getUserHome(securitySession, username); + if (userHome == null) + throw new ArgeoException("No JCR information for user " + username); + + // password + SortedSet passwordAttributes = ctx + .getAttributeSortedStringSet(passwordAttribute); + String password; + if (passwordAttributes == null || passwordAttributes.size() == 0) { + throw new ArgeoException("No password found for user " + username); + } else { + byte[] arr = (byte[]) passwordAttributes.first(); + password = new String(arr); + // erase password + Arrays.fill(arr, (byte) 0); + } + + try { + return new JcrUserDetails(userHome.getNode(ARGEO_PROFILE), + password, authorities); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot retrieve user details for " + + username, e); + } + } + + /** + * Writes an LDAP context to the JCR user profile. + * + * @return path to user profile + */ + protected String mapLdapToJcr(DirContextAdapter ctx) { + Session session = securitySession; + try { + // process + String username = ctx.getStringAttribute(usernameAttribute); + Node userHome = JcrUtils.createUserHomeIfNeeded(session, username); + Node userProfile; // = userHome.getNode(ARGEO_PROFILE); + if (userHome.hasNode(ARGEO_PROFILE)) { + userProfile = userHome.getNode(ARGEO_PROFILE); + } else { + userProfile = JcrUtils.createUserProfile(securitySession, + username); + userProfile.getSession().save(); + userProfile.getSession().getWorkspace().getVersionManager() + .checkin(userProfile.getPath()); + } + + Map modifications = new HashMap(); + for (String jcrProperty : propertyToAttributes.keySet()) + ldapToJcr(userProfile, jcrProperty, ctx, modifications); + + // assign default values + // if (!userProfile.hasProperty(Property.JCR_DESCRIPTION) + // && !modifications.containsKey(Property.JCR_DESCRIPTION)) + // modifications.put(Property.JCR_DESCRIPTION, ""); + // if (!userProfile.hasProperty(Property.JCR_TITLE)) + // modifications.put(Property.JCR_TITLE, + // userProfile.getProperty(ARGEO_FIRST_NAME).getString() + // + " " + // + userProfile.getProperty(ARGEO_LAST_NAME) + // .getString()); + int modifCount = modifications.size(); + if (modifCount > 0) { + session.getWorkspace().getVersionManager() + .checkout(userProfile.getPath()); + for (String prop : modifications.keySet()) + userProfile.setProperty(prop, modifications.get(prop)); + JcrUtils.updateLastModified(userProfile); + session.save(); + session.getWorkspace().getVersionManager() + .checkin(userProfile.getPath()); + if (log.isDebugEnabled()) + log.debug("Mapped " + modifCount + " LDAP modification" + + (modifCount == 1 ? "" : "s") + " from " + + ctx.getDn() + " to " + userProfile); + } + return userProfile.getPath(); + } catch (Exception e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot synchronize JCR and LDAP", e); + } + } + + /** Maps an LDAP property to a JCR property */ + protected void ldapToJcr(Node userProfile, String jcrProperty, + DirContextOperations ctx, Map modifications) { + // TODO do we really need DirContextOperations? + try { + String ldapAttribute; + if (propertyToAttributes.containsKey(jcrProperty)) + ldapAttribute = propertyToAttributes.get(jcrProperty); + else + throw new ArgeoException( + "No LDAP attribute mapped for JCR proprty " + + jcrProperty); + + String value = ctx.getStringAttribute(ldapAttribute); + // if (value == null && Property.JCR_TITLE.equals(jcrProperty)) + // value = ""; + // if (value == null && + // Property.JCR_DESCRIPTION.equals(jcrProperty)) + // value = ""; + String jcrValue = userProfile.hasProperty(jcrProperty) ? userProfile + .getProperty(jcrProperty).getString() : null; + if (value != null && jcrValue != null) { + if (!value.equals(jcrValue)) + modifications.put(jcrProperty, value); + } else if (value != null && jcrValue == null) { + modifications.put(jcrProperty, value); + } else if (value == null && jcrValue != null) { + modifications.put(jcrProperty, value); + } + } catch (Exception e) { + throw new ArgeoException("Cannot map JCR property " + jcrProperty + + " from LDAP", e); + } + } + + /* + * JCR to LDAP + */ + + public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) { + if (!(user instanceof JcrUserDetails)) + throw new ArgeoException("Unsupported user details: " + + user.getClass()); + + ctx.setAttributeValues("objectClass", userClasses); + ctx.setAttributeValue(usernameAttribute, user.getUsername()); + ctx.setAttributeValue(passwordAttribute, + encodePassword(user.getPassword())); + + final JcrUserDetails jcrUserDetails = (JcrUserDetails) user; + try { + Node userProfile = securitySession.getNode( + jcrUserDetails.getHomePath()).getNode(ARGEO_PROFILE); + for (String jcrProperty : propertyToAttributes.keySet()) { + ModificationItem mi = jcrToLdap(jcrProperty, userProfile + .getProperty(jcrProperty).getString()); + ctx.setAttribute(mi.getAttribute()); + } + if (log.isTraceEnabled()) + log.trace("Mapped " + userProfile + " to " + ctx.getDn()); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot synchronize JCR and LDAP", e); + } + + } + + /** Maps a JCR property to an LDAP property */ + protected ModificationItem jcrToLdap(String jcrProperty, String value) { + // TODO do we really need DirContextOperations? + try { + String ldapAttribute; + if (propertyToAttributes.containsKey(jcrProperty)) + ldapAttribute = propertyToAttributes.get(jcrProperty); + else + return null; + + // fix issue with empty 'sn' in LDAP + if (ldapAttribute.equals("sn") && (value.trim().equals(""))) + return null; + // fix issue with empty 'description' in LDAP + if (ldapAttribute.equals("description") && value.trim().equals("")) + return null; + BasicAttribute attr = new BasicAttribute( + propertyToAttributes.get(jcrProperty), value); + ModificationItem mi = new ModificationItem( + DirContext.REPLACE_ATTRIBUTE, attr); + return mi; + } catch (Exception e) { + throw new ArgeoException("Cannot map JCR property " + jcrProperty + + " from LDAP", e); + } + } + + /* + * UTILITIES + */ + protected String encodePassword(String password) { + if (!password.startsWith("{")) { + byte[] salt = new byte[16]; + random.nextBytes(salt); + return passwordEncoder.encodePassword(password, salt); + } else { + return password; + } + } + + private static Random createRandom() { + try { + return SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + return new Random(System.currentTimeMillis()); + } + } + + /* + * DEPENDENCY INJECTION + */ + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public void setRawLdapTemplate(LdapTemplate rawLdapTemplate) { + this.rawLdapTemplate = rawLdapTemplate; + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setSecurityWorkspace(String securityWorkspace) { + this.securityWorkspace = securityWorkspace; + } + + public void setUserBase(String userBase) { + this.userBase = userBase; + } + + public void setUsernameAttribute(String usernameAttribute) { + this.usernameAttribute = usernameAttribute; + } + + public void setPropertyToAttributes(Map propertyToAttributes) { + this.propertyToAttributes = propertyToAttributes; + } + + public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) { + this.usernameMapper = usernameMapper; + } + + public void setPasswordAttribute(String passwordAttribute) { + this.passwordAttribute = passwordAttribute; + } + + public void setUserClasses(String[] userClasses) { + this.userClasses = userClasses; + } + + public void setPasswordEncoder(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + + /** Listen to LDAP */ + class LdapUserListener implements ObjectChangeListener, + NamespaceChangeListener, UnsolicitedNotificationListener { + + public void namingExceptionThrown(NamingExceptionEvent evt) { + evt.getException().printStackTrace(); + } + + public void objectChanged(NamingEvent evt) { + Binding user = evt.getNewBinding(); + // TODO find a way not to be called when JCR is the source of the + // modification + DirContextAdapter ctx = (DirContextAdapter) ldapTemplate + .lookup(user.getName()); + mapLdapToJcr(ctx); + } + + public void objectAdded(NamingEvent evt) { + Binding user = evt.getNewBinding(); + DirContextAdapter ctx = (DirContextAdapter) ldapTemplate + .lookup(user.getName()); + mapLdapToJcr(ctx); + } + + public void objectRemoved(NamingEvent evt) { + if (log.isDebugEnabled()) + log.debug(evt); + } + + public void objectRenamed(NamingEvent evt) { + if (log.isDebugEnabled()) + log.debug(evt); + } + + public void notificationReceived(UnsolicitedNotificationEvent evt) { + UnsolicitedNotification notification = evt.getNotification(); + NamingException ne = notification.getException(); + String msg = "LDAP notification " + "ID=" + notification.getID() + + ", referrals=" + notification.getReferrals(); + if (ne != null) { + if (log.isTraceEnabled()) + log.trace(msg + ", exception= " + ne, ne); + else + log.warn(msg + ", exception= " + ne); + } else if (log.isDebugEnabled()) { + log.debug("Unsollicited LDAP notification " + msg); + } + } + + } + + /** Listen to JCR */ + class JcrProfileListener implements EventListener { + + public void onEvent(EventIterator events) { + try { + final Map> modifications = new HashMap>(); + while (events.hasNext()) { + Event event = events.nextEvent(); + try { + if (Event.PROPERTY_CHANGED == event.getType()) { + Property property = (Property) securitySession + .getItem(event.getPath()); + String propertyName = property.getName(); + Node userProfile = property.getParent(); + String username = userProfile.getProperty( + ARGEO_USER_ID).getString(); + if (propertyToAttributes.containsKey(propertyName)) { + Name name = usernameMapper.buildDn(username); + if (!modifications.containsKey(name)) + modifications.put(name, + new ArrayList()); + String value = property.getString(); + ModificationItem mi = jcrToLdap(propertyName, + value); + if (mi != null) + modifications.get(name).add(mi); + } + } else if (Event.NODE_ADDED == event.getType()) { + Node userProfile = securitySession.getNode(event + .getPath()); + String username = userProfile.getProperty( + ARGEO_USER_ID).getString(); + Name name = usernameMapper.buildDn(username); + for (String propertyName : propertyToAttributes + .keySet()) { + if (!modifications.containsKey(name)) + modifications.put(name, + new ArrayList()); + String value = userProfile.getProperty( + propertyName).getString(); + ModificationItem mi = jcrToLdap(propertyName, + value); + if (mi != null) + modifications.get(name).add(mi); + } + } + } catch (RepositoryException e) { + throw new ArgeoException("Cannot process event " + + event, e); + } + } + + for (Name name : modifications.keySet()) { + List userModifs = modifications.get(name); + int modifCount = userModifs.size(); + ldapTemplate.modifyAttributes(name, userModifs + .toArray(new ModificationItem[modifCount])); + if (log.isDebugEnabled()) + log.debug("Mapped " + modifCount + " JCR modification" + + (modifCount == 1 ? "" : "s") + " to " + name); + } + } catch (Exception e) { + // if (log.isDebugEnabled()) + // e.printStackTrace(); + throw new ArgeoException("Cannot process JCR events (" + + e.getMessage() + ")", e); + } + } + + } +} diff --git a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java deleted file mode 100644 index b6657f0c5..000000000 --- a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java +++ /dev/null @@ -1,319 +0,0 @@ -package org.argeo.security.ldap.jcr; - -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; -import java.util.SortedSet; -import java.util.concurrent.Executor; - -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.jcr.ArgeoNames; -import org.argeo.jcr.JcrUtils; -import org.argeo.security.jcr.JcrUserDetails; -import org.springframework.ldap.core.DirContextAdapter; -import org.springframework.ldap.core.DirContextOperations; -import org.springframework.security.BadCredentialsException; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.providers.encoding.PasswordEncoder; -import org.springframework.security.userdetails.UserDetails; -import org.springframework.security.userdetails.ldap.UserDetailsContextMapper; - -/** - * Maps LDAP attributes and JCR properties. This class is meant to be robust, - * checks of which values should be mandatory should be performed at a higher - * level. - */ -public class JcrUserDetailsContextMapper implements UserDetailsContextMapper, - ArgeoNames { - private final static Log log = LogFactory - .getLog(JcrUserDetailsContextMapper.class); - - private String usernameAttribute; - private String passwordAttribute; - private String homeBasePath; - private String[] userClasses; - - private Map propertyToAttributes = new HashMap(); - private Executor systemExecutor; - private Session session; - - private PasswordEncoder passwordEncoder; - private final Random random; - - /** 0 is always sync */ - private Long syncLatency = 10 * 60 * 1000l; - - public JcrUserDetailsContextMapper() { - random = createRandom(); - } - - private static Random createRandom() { - try { - return SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - return new Random(System.currentTimeMillis()); - } - } - - public UserDetails mapUserFromContext(final DirContextOperations ctx, - final String username, GrantedAuthority[] authorities) { - if (ctx == null) - throw new ArgeoException("No LDAP information found for user " - + username); - - final StringBuffer userHomePathT = new StringBuffer(""); - Runnable action = new Runnable() { - public void run() { - String userHomepath = mapLdapToJcr(username, ctx); - userHomePathT.append(userHomepath); - } - }; - - if (SecurityContextHolder.getContext().getAuthentication() == null) { - // authentication - try { - systemExecutor.execute(action); - } finally { - JcrUtils.logoutQuietly(session); - } - } else { - // authenticated user - action.run(); - } - - // password - SortedSet passwordAttributes = ctx - .getAttributeSortedStringSet(passwordAttribute); - String password; - if (passwordAttributes == null || passwordAttributes.size() == 0) { - throw new ArgeoException("No password found for user " + username); - } else { - byte[] arr = (byte[]) passwordAttributes.first(); - password = new String(arr); - // erase password - Arrays.fill(arr, (byte) 0); - } - JcrUserDetails userDetails = new JcrUserDetails( - userHomePathT.toString(), username, password, true, true, true, - true, authorities); - return userDetails; - } - - /** @return path to the user home node */ - protected synchronized String mapLdapToJcr(String username, - DirContextOperations ctx) { - String usernameLdap = ctx.getStringAttribute(usernameAttribute); - // log.debug("username=" + username + ", usernameLdap=" + usernameLdap); - if (!username.equals(usernameLdap)) { - String msg = "Provided username '" + username - + "' is different from username stored in LDAP '" - + usernameLdap + "'"; - // we log it because the exception may not be displayed - log.error(msg); - throw new BadCredentialsException(msg); - } - - try { - - Node userHome = JcrUtils.getUserHome(session, username); - boolean justCreatedHome = false; - if (userHome == null) { - userHome = JcrUtils.createUserHome(session, homeBasePath, - username); - justCreatedHome = true; - } - String userHomePath = userHome.getPath(); - Node userProfile; // = userHome.getNode(ARGEO_PROFILE); - if (userHome.hasNode(ARGEO_PROFILE)) { - userProfile = userHome.getNode(ARGEO_PROFILE); - if (syncLatency != 0 && !justCreatedHome) { - Calendar lastModified = userProfile.getProperty( - Property.JCR_LAST_MODIFIED).getDate(); - long timeSinceLastUpdate = System.currentTimeMillis() - - lastModified.getTimeInMillis(); - if (timeSinceLastUpdate < syncLatency)// skip sync - return userHomePath; - } - } else { - throw new ArgeoException("We should never reach this point"); - // userProfile = userHome.addNode(ARGEO_PROFILE); - // userProfile.addMixin(NodeType.MIX_TITLE); - // userProfile.addMixin(NodeType.MIX_CREATED); - // userProfile.addMixin(NodeType.MIX_LAST_MODIFIED); - } - - session.getWorkspace().getVersionManager() - .checkout(userProfile.getPath()); - for (String jcrProperty : propertyToAttributes.keySet()) - ldapToJcr(userProfile, jcrProperty, ctx); - - // assign default values - if (!userProfile.hasProperty(Property.JCR_DESCRIPTION)) - userProfile.setProperty(Property.JCR_DESCRIPTION, ""); - if (!userProfile.hasProperty(Property.JCR_TITLE)) - userProfile.setProperty(Property.JCR_TITLE, userProfile - .getProperty(ARGEO_FIRST_NAME).getString() - + " " - + userProfile.getProperty(ARGEO_LAST_NAME).getString()); - JcrUtils.updateLastModified(userProfile); - session.save(); - session.getWorkspace().getVersionManager() - .checkin(userProfile.getPath()); - if (log.isTraceEnabled()) - log.trace("Mapped " + ctx.getDn() + " to " + userProfile); - return userHomePath; - } catch (Exception e) { - JcrUtils.discardQuietly(session); - throw new ArgeoException("Cannot synchronize JCR and LDAP", e); - } - } - - public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) { - if (!(user instanceof JcrUserDetails)) - throw new ArgeoException("Unsupported user details: " - + user.getClass()); - - ctx.setAttributeValues("objectClass", userClasses); - ctx.setAttributeValue(usernameAttribute, user.getUsername()); - ctx.setAttributeValue(passwordAttribute, - encodePassword(user.getPassword())); - - final JcrUserDetails jcrUserDetails = (JcrUserDetails) user; - try { - Node userProfile = session.getNode(jcrUserDetails.getHomePath() - + '/' + ARGEO_PROFILE); - for (String jcrProperty : propertyToAttributes.keySet()) - jcrToLdap(userProfile, jcrProperty, ctx); - - if (log.isTraceEnabled()) - log.trace("Mapped " + userProfile + " to " + ctx.getDn()); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot synchronize JCR and LDAP", e); - } - } - - protected String encodePassword(String password) { - if (!password.startsWith("{")) { - byte[] salt = new byte[16]; - random.nextBytes(salt); - return passwordEncoder.encodePassword(password, salt); - } else { - return password; - } - } - - protected void ldapToJcr(Node userProfile, String jcrProperty, - DirContextOperations ctx) { - try { - String ldapAttribute; - if (propertyToAttributes.containsKey(jcrProperty)) - ldapAttribute = propertyToAttributes.get(jcrProperty); - else - throw new ArgeoException( - "No LDAP attribute mapped for JCR proprty " - + jcrProperty); - - String value = ctx.getStringAttribute(ldapAttribute); - String jcrValue = userProfile.hasProperty(jcrProperty) ? userProfile - .getProperty(jcrProperty).getString() : null; - if (value != null && jcrValue != null) { - if (!value.equals(jcrValue)) - userProfile.setProperty(jcrProperty, value); - } else if (value != null && jcrValue == null) { - userProfile.setProperty(jcrProperty, value); - } else if (value == null && jcrValue != null) { - userProfile.setProperty(jcrProperty, value); - } - } catch (Exception e) { - throw new ArgeoException("Cannot map JCR property " + jcrProperty - + " from LDAP", e); - } - } - - protected void jcrToLdap(Node userProfile, String jcrProperty, - DirContextOperations ctx) { - try { - String ldapAttribute; - if (propertyToAttributes.containsKey(jcrProperty)) - ldapAttribute = propertyToAttributes.get(jcrProperty); - else - throw new ArgeoException( - "No LDAP attribute mapped for JCR proprty " - + jcrProperty); - - // fix issue with empty 'sn' in LDAP - if (ldapAttribute.equals("sn") - && (!userProfile.hasProperty(jcrProperty) || userProfile - .getProperty(jcrProperty).getString().trim() - .equals(""))) - userProfile.setProperty(jcrProperty, "empty"); - - if (ldapAttribute.equals("description")) { - String value = userProfile.getProperty(jcrProperty).getString(); - if (value.trim().equals("")) - return; - } - - if (!userProfile.hasProperty(jcrProperty)) - return; - String value = userProfile.getProperty(jcrProperty).getString(); - - ctx.setAttributeValue(ldapAttribute, value); - } catch (Exception e) { - throw new ArgeoException("Cannot map JCR property " + jcrProperty - + " from LDAP", e); - } - } - - public void setPropertyToAttributes(Map propertyToAttributes) { - this.propertyToAttributes = propertyToAttributes; - } - - public void setSystemExecutor(Executor systemExecutor) { - this.systemExecutor = systemExecutor; - } - - public void setHomeBasePath(String homeBasePath) { - this.homeBasePath = homeBasePath; - } - - public void setUsernameAttribute(String usernameAttribute) { - this.usernameAttribute = usernameAttribute; - } - - public void setPasswordAttribute(String passwordAttribute) { - this.passwordAttribute = passwordAttribute; - } - - public void setUserClasses(String[] userClasses) { - this.userClasses = userClasses; - } - - public void setPasswordEncoder(PasswordEncoder passwordEncoder) { - this.passwordEncoder = passwordEncoder; - } - - public void setSession(Session session) { - this.session = session; - } - - /** - * Time in ms during which the LDAP server is not checked. 0 is always sync. - */ - public void setSyncLatency(Long syncLatency) { - this.syncLatency = syncLatency; - } - -} diff --git a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/applicationContext.xml b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/applicationContext.xml index 6b6a54077..d56d0d478 100644 --- a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/applicationContext.xml +++ b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/applicationContext.xml @@ -16,7 +16,7 @@ - + diff --git a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/osgi.xml b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/osgi.xml index 08063ab39..99eb2e4c8 100644 --- a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/osgi.xml +++ b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/osgi.xml @@ -8,11 +8,12 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd"> - - - + + + + + + diff --git a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml index f6e210b7c..1c16f257d 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml @@ -9,19 +9,10 @@ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> - - - - - - - - + \ No newline at end of file diff --git a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml index 8e477fe71..3d7b30c6c 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml @@ -12,33 +12,17 @@ + init-method="init" destroy-method="destroy"> - - - - - - - - classpath:/org/argeo/jcr/argeo.cnd - - + - - + + - - - - - - \ No newline at end of file diff --git a/server/modules/org.argeo.node.repo.jackrabbit/build.properties b/server/modules/org.argeo.node.repo.jackrabbit/build.properties index 5f22cdd44..a275ae638 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/build.properties +++ b/server/modules/org.argeo.node.repo.jackrabbit/build.properties @@ -1 +1,4 @@ -bin.includes = META-INF/ +bin.includes = META-INF/,\ + noderepo.properties,\ + repository-h2.xml,\ + repository-postgresql.xml diff --git a/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties b/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties index 046e1790a..f50029b5d 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties +++ b/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties @@ -1,5 +1,6 @@ # Workspace used by the node session -argeo.node.repo.workspace=dev +argeo.node.repo.defaultWorkspace=main +#argeo.node.repo.securityWorkspace=security # Repository base directory argeo.node.repo.home=${osgi.instance.area}/node @@ -13,6 +14,9 @@ argeo.node.repo.dbpassword= ## Remote # Remote repository URI (overrides other configurations if not empty) argeo.node.repo.uri= +# may change in the near future: +argeo.node.repo.remoteSystemUser=root +argeo.node.repo.remoteSystemPassword=demo # ADVANCED argeo.node.repo.maxPoolSize=10 \ No newline at end of file diff --git a/server/modules/org.argeo.node.repo.jackrabbit/pom.xml b/server/modules/org.argeo.node.repo.jackrabbit/pom.xml index 32840cd19..2563d2f9a 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/pom.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/pom.xml @@ -21,18 +21,15 @@ *, com.mysql.jdbc;version="[5.0.0,6.0.0)";resolution:=optional, + org.h2;version="[1.0.0,2.0.0)";resolution:=optional, + org.postgresql;version="[8.0.0,9.0.0)";resolution:=optional, javax.jcr;version="[2.0.0,3.0.0)", org.apache.jackrabbit.core;version="[2.0.0,3.0.0)", org.apache.jackrabbit.core.config;version="[2.0.0,3.0.0)", org.apache.xalan.processor, org.argeo.jackrabbit, org.argeo.jcr, - org.argeo.security, - org.argeo.security.core, - org.h2;version="[1.0.0,2.0.0)";resolution:=optional, - org.postgresql;version="[8.0.0,9.0.0)";resolution:=optional, org.springframework.beans.factory.config, - org.springframework.security;version="2.0.6.RELEASE" diff --git a/server/modules/org.argeo.node.repo.jackrabbit/repository-h2.xml b/server/modules/org.argeo.node.repo.jackrabbit/repository-h2.xml index 9e7886d69..2d3343ec3 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/repository-h2.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/repository-h2.xml @@ -30,7 +30,7 @@ + defaultWorkspace="${argeo.node.repo.defaultWorkspace}" /> diff --git a/server/modules/org.argeo.node.repo.jackrabbit/repository-postgresql.xml b/server/modules/org.argeo.node.repo.jackrabbit/repository-postgresql.xml index 4c4f4fb41..4b2fe53fc 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/repository-postgresql.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/repository-postgresql.xml @@ -27,7 +27,7 @@ + defaultWorkspace="${argeo.node.repo.defaultWorkspace}" /> diff --git a/server/plugins/org.argeo.jcr.ui.explorer/.classpath b/server/plugins/org.argeo.jcr.ui.explorer/.classpath index d3d5c8095..0a607ba17 100644 --- a/server/plugins/org.argeo.jcr.ui.explorer/.classpath +++ b/server/plugins/org.argeo.jcr.ui.explorer/.classpath @@ -1,8 +1,8 @@ - - + + diff --git a/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/model/RepositoriesNode.java b/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/model/RepositoriesNode.java index a645809a1..6c5273aca 100644 --- a/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/model/RepositoriesNode.java +++ b/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/model/RepositoriesNode.java @@ -76,7 +76,7 @@ public class RepositoriesNode extends TreeParent implements ArgeoNames { throws RepositoryException { Session userSession = jcrKeyring.getSession(); Node userHome = JcrUtils.getUserHome(userSession); - if (userHome.hasNode(ARGEO_REMOTE)) { + if (userHome != null && userHome.hasNode(ARGEO_REMOTE)) { NodeIterator it = userHome.getNode(ARGEO_REMOTE).getNodes(); while (it.hasNext()) { Node remoteNode = it.nextNode(); diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java index c2e0034ff..10c7af1d0 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java @@ -16,12 +16,12 @@ package org.argeo.jackrabbit; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -51,7 +51,6 @@ import org.apache.jackrabbit.api.JackrabbitRepository; import org.apache.jackrabbit.commons.NamespaceHelper; import org.apache.jackrabbit.commons.cnd.CndImporter; import org.apache.jackrabbit.core.RepositoryImpl; -import org.apache.jackrabbit.core.TransientRepository; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; @@ -59,9 +58,7 @@ import org.argeo.ArgeoException; import org.argeo.jcr.ArgeoNames; import org.argeo.jcr.JcrUtils; import org.argeo.security.SystemAuthentication; -import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; import org.springframework.security.Authentication; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; @@ -72,22 +69,24 @@ import org.xml.sax.InputSource; * Wrapper around a Jackrabbit repository which allows to configure it in Spring * and expose it as a {@link Repository}. */ -public class JackrabbitContainer implements Repository, ResourceLoaderAware { +public class JackrabbitContainer implements Repository { private Log log = LogFactory.getLog(JackrabbitContainer.class); + // remote + private String uri = null; + private Credentials remoteSystemCredentials = null; + + // local private Resource configuration; + private RepositoryConfig repositoryConfig; private File homeDirectory; private Resource variables; - private Boolean inMemory = false; // wrapped repository private Repository repository; - private RepositoryConfig repositoryConfig; - - // CND - private ResourceLoader resourceLoader; + // data model /** Node type definitions in CND format */ private List cndFiles = new ArrayList(); @@ -101,10 +100,6 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { private Executor systemExecutor; - // remote - private String uri = null; - private Credentials remoteSystemCredentials = null; - /** * Empty constructor, {@link #init()} should be called after properties have * been set @@ -122,10 +117,11 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { init(); } + /** Initializes */ public void init() { if (repository != null) { // we are just wrapping another repository - importNodeTypeDefinitions(); + prepareDataModel(); return; } @@ -135,15 +131,15 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { // apply new CND files after migration if (cndFiles != null && cndFiles.size() > 0) - importNodeTypeDefinitions(); + prepareDataModel(); } - /** Actually creates a new repository. */ + /** Actually creates the new repository. */ protected void createJackrabbitRepository() { long begin = System.currentTimeMillis(); + InputStream configurationIn = null; try { - // remote repository - if (uri != null && !uri.trim().equals("")) { + if (uri != null && !uri.trim().equals("")) {// remote Map params = new HashMap(); params.put( org.apache.jackrabbit.commons.JcrUtils.REPOSITORY_URI, @@ -155,53 +151,52 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { + " not found"); log.info("Initialized Jackrabbit repository " + repository + " from URI " + uri); - // do not perform further initialization since we assume that - // the - // remote repository has been properly configured - return; - } else { + // we assume that the remote repository has been properly + // configured + } else {// local // reset uri to null in order to optimize isRemote() uri = null; - } - // local repository - if (inMemory && getHomeDirectory().exists()) { - FileUtils.deleteDirectory(getHomeDirectory()); - log.warn("Deleted Jackrabbit home directory " - + getHomeDirectory()); - } + // temporary + if (inMemory && getHomeDirectory().exists()) { + FileUtils.deleteDirectory(getHomeDirectory()); + log.warn("Deleted Jackrabbit home directory " + + getHomeDirectory()); + } - Properties vars = getConfigurationProperties(); - InputStream in = configuration.getInputStream(); - try { + // process configuration file + Properties vars = getConfigurationProperties(); + configurationIn = configuration.getInputStream(); vars.put( RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, getHomeDirectory().getCanonicalPath()); - repositoryConfig = RepositoryConfig.create(new InputSource(in), - vars); - } catch (Exception e) { - throw new RuntimeException("Cannot read configuration", e); - } finally { - IOUtils.closeQuietly(in); - } + repositoryConfig = RepositoryConfig.create(new InputSource( + configurationIn), vars); - if (inMemory) - repository = new TransientRepository(repositoryConfig); - else + // + // Actual repository creation + // repository = RepositoryImpl.create(repositoryConfig); - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - log.info("Initialized Jackrabbit repository in " + duration - + " s, home: " + getHomeDirectory() + ", config: " - + configuration); + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; + log.info("Initialized Jackrabbit repository in " + duration + + " s, home: " + getHomeDirectory() + ", config: " + + configuration); + } } catch (Exception e) { throw new ArgeoException("Cannot create Jackrabbit repository " + getHomeDirectory(), e); + } finally { + IOUtils.closeQuietly(configurationIn); } } /** Executes migrations, if needed. */ protected void migrate() { + // Remote migration not supported + if (isRemote()) + return; + // No migration to perform if (dataModelMigrations.size() == 0) return; @@ -273,7 +268,7 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { + File.separator + "jackrabbit-" + UUID.randomUUID()); homeDirectory.mkdirs(); - // will it work if directory is not empty? + // will it work if directory is not empty?? homeDirectory.deleteOnExit(); } } @@ -285,47 +280,39 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { } } - public void dispose() throws Exception { - long begin = System.currentTimeMillis(); - if (repository != null) { - if (repository instanceof JackrabbitRepository) - ((JackrabbitRepository) repository).shutdown(); - else if (repository instanceof RepositoryImpl) - ((RepositoryImpl) repository).shutdown(); - else if (repository instanceof TransientRepository) - ((TransientRepository) repository).shutdown(); - } - - if (inMemory) - if (getHomeDirectory().exists()) { - FileUtils.deleteDirectory(getHomeDirectory()); - if (log.isDebugEnabled()) - log.debug("Deleted Jackrabbit home directory " - + getHomeDirectory()); - } - - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - if (uri != null && !uri.trim().equals("")) - log.info("Destroyed Jackrabbit repository with uri " + uri); - else + /** Shutdown the repository */ + public void destroy() throws Exception { + if (repository != null && repository instanceof RepositoryImpl) { + long begin = System.currentTimeMillis(); + ((RepositoryImpl) repository).shutdown(); + if (inMemory) + if (getHomeDirectory().exists()) { + FileUtils.deleteDirectory(getHomeDirectory()); + if (log.isDebugEnabled()) + log.debug("Deleted Jackrabbit home directory " + + getHomeDirectory()); + } + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; log.info("Destroyed Jackrabbit repository in " + duration + " s, home: " + getHomeDirectory() + ", config " + configuration); + } } /** - * @deprecated explicitly declare {@link #dispose()} as destroy-method + * @deprecated explicitly declare {@link #destroy()} as destroy-method * instead. */ - public void destroy() throws Exception { - log.error("## Declare destroy-method=\"dispose\". in the Jackrabbit container bean"); + public void dispose() throws Exception { + log.error("## Declare destroy-method=\"destroy\". in the Jackrabbit container bean"); + destroy(); } - /** @deprecated explicitly declare {@link #init()} as init-method instead. */ - public void afterPropertiesSet() throws Exception { - log.error("## Declare init-method=\"init\". in the Jackrabbit container bean"); - } + /* + * UTILITIES + */ + /** Generates the properties to use in the configuration. */ protected Properties getConfigurationProperties() { InputStream propsIn = null; Properties vars; @@ -360,38 +347,56 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { } /** - * Import declared node type definitions, trying to update them if they have - * changed. In case of failures an error will be logged but no exception - * will be thrown. + * Import declared node type definitions and register namespaces. Tries to + * update the node definitions if they have changed. In case of failures an + * error will be logged but no exception will be thrown. */ - protected void importNodeTypeDefinitions() { + protected void prepareDataModel() { // importing node def on remote si currently not supported if (isRemote()) return; Runnable action = new Runnable() { public void run() { - Reader reader = null; Session session = null; try { session = login(); - // processNewSession(session); - // Load cnds as resources + // register namespaces + if (namespaces.size() > 0) { + NamespaceHelper namespaceHelper = new NamespaceHelper( + session); + namespaceHelper.registerNamespaces(namespaces); + } + // load CND files from classpath or as URL for (String resUrl : cndFiles) { - Resource res = resourceLoader.getResource(resUrl); - byte[] arr = IOUtils.toByteArray(res.getInputStream()); - reader = new InputStreamReader( - new ByteArrayInputStream(arr)); - CndImporter.registerNodeTypes(reader, session, true); + boolean classpath; + if (resUrl.startsWith("classpath:")) { + resUrl = resUrl.substring("classpath:".length()); + classpath = true; + } else if (resUrl.indexOf(':') < 0) { + classpath = true; + } else { + classpath = false; + } + + URL url = classpath ? getClass().getClassLoader() + .getResource(resUrl) : new URL(resUrl); + + Reader reader = null; + try { + reader = new InputStreamReader(url.openStream()); + CndImporter + .registerNodeTypes(reader, session, true); + } finally { + IOUtils.closeQuietly(reader); + } } - session.save(); } catch (Exception e) { log.error( "Cannot import node type definitions " + cndFiles, e); JcrUtils.discardQuietly(session); } finally { - IOUtils.closeQuietly(reader); JcrUtils.logoutQuietly(session); } } @@ -403,7 +408,10 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { action.run(); } - // JCR REPOSITORY (delegated) + /* + * DELEGATED JCR REPOSITORY METHODS + */ + public String getDescriptor(String key) { return getRepository().getDescriptor(key); } @@ -460,6 +468,10 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { return login(null, workspaceName); } + /** Called after a session has been created, does nothing by default. */ + protected void processNewSession(Session session) { + } + public Boolean isRemote() { return uri != null; } @@ -475,15 +487,6 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { return repository; } - protected synchronized void processNewSession(Session session) { - try { - NamespaceHelper namespaceHelper = new NamespaceHelper(session); - namespaceHelper.registerNamespaces(namespaces); - } catch (Exception e) { - throw new ArgeoException("Cannot process new session", e); - } - } - /** * Logs in to the default workspace, creates the required workspace, logs * out, logs in to the required workspace. @@ -498,10 +501,6 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { return getRepository().login(credentials, workspaceName); } - public void setResourceLoader(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; - } - public boolean isStandardDescriptor(String key) { return getRepository().isStandardDescriptor(key); } @@ -518,7 +517,10 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { return getRepository().getDescriptorValues(key); } - // BEANS METHODS + /* + * FIELDS ACCESS + */ + public void setHomeDirectory(File homeDirectory) { this.homeDirectory = homeDirectory; } @@ -563,5 +565,4 @@ public class JackrabbitContainer implements Repository, ResourceLoaderAware { Set dataModelMigrations) { this.dataModelMigrations = dataModelMigrations; } - } diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java index 8e8b1d945..74b96e9ed 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java @@ -42,6 +42,8 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { private Boolean openSessionInView = true; + private String securityWorkspace = "security"; + public Session getSession(HttpServletRequest request, Repository rep, String workspace) throws LoginException, ServletException, RepositoryException { @@ -84,6 +86,9 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { protected void writeRemoteRoles(JackrabbitSession session) throws RepositoryException { + if (!session.getWorkspace().getName().equals(securityWorkspace)) + return; + // retrieve roles String userId = session.getUserID(); UserManager userManager = session.getUserManager(); @@ -98,7 +103,8 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { userGroupIds.add(it.next().getID()); // write roles if needed - Node userProfile = JcrUtils.getUserProfile(session); + Node userProfile = JcrUtils.getUserHome(session).getNode( + ArgeoNames.ARGEO_PROFILE); boolean writeRoles = false; if (userProfile.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { Value[] roles = userProfile.getProperty( diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java index 7f060ef68..9f19fef85 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java @@ -16,6 +16,14 @@ public interface ArgeoNames { // user profile public final static String ARGEO_PROFILE = "argeo:profile"; + + // spring security + public final static String ARGEO_ENABLED = "argeo:enabled"; + public final static String ARGEO_ACCOUNT_NON_EXPIRED = "argeo:accountNonExpired"; + public final static String ARGEO_ACCOUNT_NON_LOCKED = "argeo:accountNonLocked"; + public final static String ARGEO_CREDENTIALS_NON_EXPIRED = "argeo:credentialsNonExpired"; + + // personal details public final static String ARGEO_FIRST_NAME = "argeo:firstName"; public final static String ARGEO_LAST_NAME = "argeo:lastName"; public final static String ARGEO_PRIMARY_EMAIL = "argeo:primaryEmail"; diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java index facd475ca..66c6a9388 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java @@ -51,11 +51,6 @@ import javax.jcr.nodetype.NodeType; import javax.jcr.observation.EventListener; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; -import javax.jcr.query.qom.Constraint; -import javax.jcr.query.qom.DynamicOperand; -import javax.jcr.query.qom.QueryObjectModelFactory; -import javax.jcr.query.qom.Selector; -import javax.jcr.query.qom.StaticOperand; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; @@ -925,34 +920,30 @@ public class JcrUtils implements ArgeoJcrConstants { } } - /** Returns the home node of the session user or null if none was found. */ - public static Node getUserHome(Session session) { - String userID = session.getUserID(); - return getUserHome(session, userID); - } - - /** - * Returns user home has path, embedding exceptions. Contrary to - * {@link #getUserHome(Session)}, it never returns null but throws and - * exception if not found. - */ - public static String getUserHomePath(Session session) { - String userID = session.getUserID(); + /** Removes a listener without throwing exception */ + public static void removeListenerQuietly(Session session, + EventListener listener) { + if (session == null || !session.isLive()) + return; try { - Node userHome = getUserHome(session, userID); - if (userHome != null) - return userHome.getPath(); - else - throw new ArgeoException("No home registered for " + userID); + session.getWorkspace().getObservationManager() + .removeEventListener(listener); } catch (RepositoryException e) { - throw new ArgeoException("Cannot find user home path", e); + // silent } } - /** Get the profile of the user attached to this session. */ - public static Node getUserProfile(Session session) { + /** Returns the home node of the session user or null if none was found. */ + public static Node getUserHome(Session session) { String userID = session.getUserID(); - return getUserProfile(session, userID); + return getUserHome(session, userID); + } + + /** User home path is NOT configurable */ + public static String getUserHomePath(String username) { + String homeBasePath = "/home"; + return homeBasePath + '/' + firstCharsToPath(username, 2) + '/' + + username; } /** @@ -967,102 +958,190 @@ public class JcrUtils implements ArgeoJcrConstants { */ public static Node getUserHome(Session session, String username) { try { - QueryObjectModelFactory qomf = session.getWorkspace() - .getQueryManager().getQOMFactory(); - - // query the user home for this user id - Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME, - "userHome"); - DynamicOperand userIdDop = qomf.propertyValue("userHome", - ArgeoNames.ARGEO_USER_ID); - StaticOperand userIdSop = qomf.literal(session.getValueFactory() - .createValue(username)); - Constraint constraint = qomf.comparison(userIdDop, - QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); - Query query = qomf.createQuery(userHomeSel, constraint, null, null); - Node userHome = JcrUtils.querySingleNode(query); - return userHome; + String homePath = getUserHomePath(username); + return session.itemExists(homePath) ? session.getNode(homePath) + : null; + // kept for example of QOM queries + // QueryObjectModelFactory qomf = session.getWorkspace() + // .getQueryManager().getQOMFactory(); + // Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME, + // "userHome"); + // DynamicOperand userIdDop = qomf.propertyValue("userHome", + // ArgeoNames.ARGEO_USER_ID); + // StaticOperand userIdSop = qomf.literal(session.getValueFactory() + // .createValue(username)); + // Constraint constraint = qomf.comparison(userIdDop, + // QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); + // Query query = qomf.createQuery(userHomeSel, constraint, null, + // null); + // Node userHome = JcrUtils.querySingleNode(query); } catch (RepositoryException e) { throw new ArgeoException("Cannot find home for user " + username, e); } } - public static Node getUserProfile(Session session, String username) { + /** + * Creates an Argeo user home, does nothing if it already exists. Session is + * NOT saved. + */ + public static Node createUserHomeIfNeeded(Session session, String username) { + try { + String homePath = getUserHomePath(username); + if (session.itemExists(homePath)) + return session.getNode(homePath); + else { + Node userHome = JcrUtils.mkdirs(session, homePath); + userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); + return userHome; + } + } catch (RepositoryException e) { + discardQuietly(session); + throw new ArgeoException("Cannot create home for " + username + + " in workspace " + session.getWorkspace().getName(), e); + } + } + + /** + * Creates a user profile in the home of this user. Creates the home if + * needed, but throw an exception if a profile already exists. The session + * is not saved and the node is in a checkedOut state (that is, it requires + * a subsequent checkin after saving the session). + */ + public static Node createUserProfile(Session session, String username) { try { - QueryObjectModelFactory qomf = session.getWorkspace() - .getQueryManager().getQOMFactory(); - Selector sel = qomf.selector(ArgeoTypes.ARGEO_USER_PROFILE, - "userProfile"); - DynamicOperand userIdDop = qomf.propertyValue("userProfile", - ArgeoNames.ARGEO_USER_ID); - StaticOperand userIdSop = qomf.literal(session.getValueFactory() - .createValue(username)); - Constraint constraint = qomf.comparison(userIdDop, - QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); - Query query = qomf.createQuery(sel, constraint, null, null); - Node userProfile = JcrUtils.querySingleNode(query); + Node userHome = createUserHomeIfNeeded(session, username); + if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) + throw new ArgeoException( + "There is already a user profile under " + userHome); + Node userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE); + userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); + userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); + userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true); + userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, + true); return userProfile; } catch (RepositoryException e) { - throw new ArgeoException( - "Cannot find profile for user " + username, e); + discardQuietly(session); + throw new ArgeoException("Cannot create home for " + username + + " in workspace " + session.getWorkspace().getName(), e); } } /** Creates an Argeo user home. */ - public static Node createUserHome(Session session, String homeBasePath, - String username) { + // public static Node createUserHome(Session session, String homeBasePath, + // String username) { + // try { + // if (session == null) + // throw new ArgeoException("Session is null"); + // if (session.hasPendingChanges()) + // throw new ArgeoException( + // "Session has pending changes, save them first"); + // + // String homePath = getUserHomePath(username); + // + // if (session.itemExists(homePath)) { + // try { + // throw new ArgeoException( + // "Trying to create a user home that already exists"); + // } catch (Exception e) { + // // we use this workaround to be sure to get the stack trace + // // to identify the sink of the bug. + // log.warn("trying to create an already existing userHome at path:" + // + homePath + ". Stack trace : "); + // e.printStackTrace(); + // } + // } + // + // Node userHome = JcrUtils.mkdirs(session, homePath); + // Node userProfile; + // if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) { + // log.warn("userProfile node already exists for userHome path: " + // + homePath + ". We do not add a new one"); + // } else { + // userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE); + // userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); + // // session.getWorkspace().getVersionManager() + // // .checkout(userProfile.getPath()); + // userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); + // session.save(); + // session.getWorkspace().getVersionManager() + // .checkin(userProfile.getPath()); + // // we need to save the profile before adding the user home type + // } + // userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + // // see + // // + // http://jackrabbit.510166.n4.nabble.com/Jackrabbit-2-0-beta-6-Problem-adding-a-Mixin-type-with-mandatory-properties-after-setting-propertiesn-td1290332.html + // userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); + // session.save(); + // return userHome; + // } catch (RepositoryException e) { + // discardQuietly(session); + // throw new ArgeoException("Cannot create home node for user " + // + username, e); + // } + // } + + /** + * Returns user home has path, embedding exceptions. Contrary to + * {@link #getUserHome(Session)}, it never returns null but throws and + * exception if not found. + * + * @deprecated use getUserHome() instead, throwing an exception if it + * returns null + */ + @Deprecated + public static String getUserHomePath(Session session) { + String userID = session.getUserID(); try { - if (session == null) - throw new ArgeoException("Session is null"); - if (session.hasPendingChanges()) - throw new ArgeoException( - "Session has pending changes, save them first"); - - String homePath = homeBasePath + '/' - + firstCharsToPath(username, 2) + '/' + username; - - if (session.itemExists(homePath)) { - try { - throw new ArgeoException( - "Trying to create a user home that already exists"); - } catch (Exception e) { - // we use this workaround to be sure to get the stack trace - // to identify the sink of the bug. - log.warn("trying to create an already existing userHome at path:" - + homePath + ". Stack trace : "); - e.printStackTrace(); - } - } + String homePath = getUserHomePath(userID); + if (session.itemExists(homePath)) + return homePath; + else + throw new ArgeoException("No home registered for " + userID); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot find user home path", e); + } + } - Node userHome = JcrUtils.mkdirs(session, homePath); - Node userProfile; - if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) { - log.warn("userProfile node already exists for userHome path: " - + homePath + ". We do not add a new one"); - } else { - userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE); - userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); - // session.getWorkspace().getVersionManager() - // .checkout(userProfile.getPath()); - userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); - session.save(); - session.getWorkspace().getVersionManager() - .checkin(userProfile.getPath()); - // we need to save the profile before adding the user home type - } - userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); - // see - // http://jackrabbit.510166.n4.nabble.com/Jackrabbit-2-0-beta-6-Problem-adding-a-Mixin-type-with-mandatory-properties-after-setting-propertiesn-td1290332.html - userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); - session.save(); - return userHome; + /** + * @return null if not found * + * @deprecated will soon be removed. Call instead + * getUserHome().getNode(ARGEO_PROFILE) on the security + * workspace. + */ + @Deprecated + public static Node getUserProfile(Session session, String username) { + try { + Node userHome = getUserHome(session, username); + if (userHome == null) + return null; + if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) + return userHome.getNode(ArgeoNames.ARGEO_PROFILE); + else + return null; } catch (RepositoryException e) { - discardQuietly(session); - throw new ArgeoException("Cannot create home node for user " - + username, e); + throw new ArgeoException( + "Cannot find profile for user " + username, e); } } + /** + * Get the profile of the user attached to this session. + * + * @deprecated will soon be removed. Call instead + * getUserHome().getNode(ARGEO_PROFILE) on the security + * workspace. + */ + @Deprecated + public static Node getUserProfile(Session session) { + String userID = session.getUserID(); + return getUserProfile(session, userID); + } + /** * Quietly unregisters an {@link EventListener} from the udnerlying * workspace of this node. diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrKeyring.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrKeyring.java index 7383b39ad..2a323f8b7 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrKeyring.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrKeyring.java @@ -1,15 +1,11 @@ package org.argeo.jcr.security; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.io.OutputStream; -import java.security.AlgorithmParameters; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.jcr.Binary; diff --git a/server/runtime/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd b/server/runtime/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd index dbf9a927f..2bd38ca5a 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd +++ b/server/runtime/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd @@ -21,6 +21,10 @@ mixin [argeo:userProfile] > mix:created, mix:lastModified, mix:title, mix:versionable mixin - argeo:userID (STRING) m +- argeo:enabled (BOOLEAN) +- argeo:accountNonExpired (BOOLEAN) +- argeo:accountNonLocked (BOOLEAN) +- argeo:credentialsNonExpired (BOOLEAN) - argeo:remoteRoles (STRING) * [argeo:preferenceNode] > mix:lastModified, mix:versionable -- 2.39.2