Move security model to CMS
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 14 Feb 2015 13:31:59 +0000 (13:31 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 14 Feb 2015 13:31:59 +0000 (13:31 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@7868 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

63 files changed:
demo/log4j.properties
org.argeo.cms/bnd.bnd
org.argeo.cms/src/org/argeo/cms/auth/ArgeoLoginContext.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/LoginCanceledException.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/AbstractLoginModule.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/AnonymousLoginModule.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/EndUserLoginModule.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/GrantedAuthorityPrincipal.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/OsJcrAuthenticationProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteJcrAuthenticationProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/SystemLoginModule.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg
org.argeo.cms/src/org/argeo/cms/internal/useradmin/JcrUserAdmin.java
org.argeo.cms/src/org/argeo/cms/internal/useradmin/OsJcrUserAdminService.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/useradmin/SimpleJcrSecurityModel.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitSecurityModel.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitUserAdminService.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/ScopedSessionProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoLdapShaPasswordEncoder.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoLdapUserDetailsManager.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoUserAdminDaoLdap.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/JcrLdapSynchronizer.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/JcrUserDetailsContextMapper.java [new file with mode: 0644]
org.argeo.security.core/bnd.bnd
org.argeo.security.core/src/org/argeo/security/SecurityUtils.java
org.argeo.security.core/src/org/argeo/security/core/AbstractSystemExecution.java
org.argeo.security.core/src/org/argeo/security/jcr/JcrSecurityModel.java
org.argeo.security.core/src/org/argeo/security/jcr/OsJcrAuthenticationProvider.java [deleted file]
org.argeo.security.core/src/org/argeo/security/jcr/OsJcrUserAdminService.java [deleted file]
org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java [deleted file]
org.argeo.security.core/src/org/argeo/security/jcr/SecureThreadBoundSession.java
org.argeo.security.core/src/org/argeo/security/jcr/SimpleJcrSecurityModel.java [deleted file]
org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/JackrabbitSecurityModel.java [deleted file]
org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/JackrabbitUserAdminService.java [deleted file]
org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/ScopedSessionProvider.java [deleted file]
org.argeo.security.core/src/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java [deleted file]
org.argeo.security.core/src/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java [deleted file]
org.argeo.security.core/src/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java [deleted file]
org.argeo.security.core/src/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java [deleted file]
org.argeo.security.core/src/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java [deleted file]
org.argeo.security.core/src/org/argeo/security/login/AbstractSpringLoginModule.java [deleted file]
org.argeo.security.core/src/org/argeo/security/login/AnonymousLoginModule.java [deleted file]
org.argeo.security.core/src/org/argeo/security/login/BundleContextCallback.java [deleted file]
org.argeo.security.core/src/org/argeo/security/login/BundleContextCallbackHandler.java [deleted file]
org.argeo.security.core/src/org/argeo/security/login/ConsoleCallbackHandler.java [deleted file]
org.argeo.security.core/src/org/argeo/security/login/EndUserLoginModule.java [deleted file]
org.argeo.security.core/src/org/argeo/security/login/GrantedAuthorityPrincipal.java [deleted file]
org.argeo.security.core/src/org/argeo/security/login/LoginCanceledException.java [deleted file]
org.argeo.security.core/src/org/argeo/security/login/SystemLoginModule.java [deleted file]
org.argeo.security.ui.rap/.classpath
org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java
org.argeo.security.ui/src/org/argeo/security/ui/SecurityUiPlugin.java
org.argeo.security.ui/src/org/argeo/security/ui/auth/AbstractLoginDialog.java [new file with mode: 0644]
org.argeo.security.ui/src/org/argeo/security/ui/auth/CompositeCallbackHandler.java [new file with mode: 0644]
org.argeo.security.ui/src/org/argeo/security/ui/auth/DefaultLoginDialog.java [new file with mode: 0644]
org.argeo.security.ui/src/org/argeo/security/ui/dialogs/AbstractLoginDialog.java [deleted file]
org.argeo.security.ui/src/org/argeo/security/ui/dialogs/DefaultLoginDialog.java [deleted file]
org.argeo.server.jcr/ext/test/org/argeo/jcr/MapperTest.java
org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitWrapper.java
org.argeo.server.jcr/src/org/argeo/jcr/JcrUtils.java

index 13f949ff5e53e4d8d4c056883e161741f405b14d..53e2c2f1939ca6f61b8db9ef2c54f450c8fa2e4b 100644 (file)
@@ -2,21 +2,9 @@ log4j.rootLogger=WARN, development
 
 ## Levels
 log4j.logger.org.argeo=DEBUG
-log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=WARN
-log4j.logger.org.argeo.server.webextender.TomcatDeployer=INFO
 
-#log4j.logger.org.springframework.security=DEBUG
-#log4j.logger.org.apache.commons.exec=DEBUG
-#log4j.logger.org.apache.jackrabbit.webdav=DEBUG
-#log4j.logger.org.apache.jackrabbit.remote=DEBUG
-#log4j.logger.org.apache.jackrabbit.core.observation=DEBUG
-
-log4j.logger.org.apache.catalina=INFO
-log4j.logger.org.apache.coyote=INFO
-
-log4j.logger.org.apache.directory=INFO
-log4j.logger.org.apache.directory.server=ERROR
 log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR
+log4j.logger.org.apache.jackrabbit.core.config.ConfigurationErrorHandler=ERROR
 
 ## Appenders
 # console is set to be a ConsoleAppender.
index bdcfd61d0b589e0d9097737a59400826d42487d2..711fe437a474a055007ed4cc84e6fac82a4fbc78 100644 (file)
@@ -9,9 +9,12 @@ org.xml.sax;version="0.0.0",\
 org.eclipse.swt.widgets;version="0.0.0",\
 org.argeo.jcr,\
 org.springframework.context,\
+org.springframework.security.authentication.jaas,\
 org.apache.jackrabbit.api,\
 org.apache.jackrabbit.commons,\
-org.eclipse.rap.rwt.osgi,\
 org.h2;resolution:=optional,\
 org.apache.commons.vfs2.*;resolution:=optional,\
+org.apache.jackrabbit.*;resolution:=optional,\
+org.springframework.ldap.*;resolution:=optional,\
+org.springframework.security.ldap.*;resolution:=optional,\
 *
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/ArgeoLoginContext.java b/org.argeo.cms/src/org/argeo/cms/auth/ArgeoLoginContext.java
new file mode 100644 (file)
index 0000000..3a53667
--- /dev/null
@@ -0,0 +1,53 @@
+package org.argeo.cms.auth;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+/** Integrates JAAS with the Argeo platform */
+public class ArgeoLoginContext extends LoginContext {
+       private static ThreadLocal<ClassLoader> currentContextClassLoader = new ThreadLocal<ClassLoader>() {
+               @Override
+               protected ClassLoader initialValue() {
+                       return Thread.currentThread().getContextClassLoader();
+               }
+
+               @Override
+               public void set(ClassLoader value) {
+                       throw new IllegalAccessError("Current class loader is read-only");
+               }
+       };
+
+       public ArgeoLoginContext(String name, Subject subject,
+                       CallbackHandler callbackHandler) throws LoginException {
+               super(setContextClassLoaderForName(name), subject, callbackHandler);
+               // reset current context classloader
+               Thread.currentThread().setContextClassLoader(
+                               currentContextClassLoader.get());
+               currentContextClassLoader.remove();
+       }
+
+       /**
+        * Set the context classloader
+        * 
+        * @return the passed name, in order to chain calls in the constructor
+        */
+       private static String setContextClassLoaderForName(String name) {
+               // store current context class loader;
+               currentContextClassLoader.get();
+               Thread.currentThread().setContextClassLoader(
+                               ArgeoLoginContext.class.getClassLoader());
+               return name;
+       }
+
+       @Override
+       public void login() throws LoginException {
+               super.login();
+       }
+
+       @Override
+       public void logout() throws LoginException {
+               super.logout();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/LoginCanceledException.java b/org.argeo.cms/src/org/argeo/cms/auth/LoginCanceledException.java
new file mode 100644 (file)
index 0000000..731cdd1
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.cms.auth;
+
+import javax.security.auth.login.LoginException;
+
+public class LoginCanceledException extends LoginException {
+       private static final long serialVersionUID = 8289162094013471043L;
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/AbstractLoginModule.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/AbstractLoginModule.java
new file mode 100644 (file)
index 0000000..36d5e0f
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.argeo.ArgeoException;
+import org.argeo.cms.internal.kernel.Activator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/** Login module which caches one subject per thread. */
+public abstract class AbstractLoginModule implements LoginModule {
+       // private final static Log log = LogFactory
+       // .getLog(AbstractSpringLoginModule.class);
+       private CallbackHandler callbackHandler;
+       private Subject subject;
+
+       private Authentication authentication;
+
+       // state
+       private BundleContext bundleContext;
+       private ServiceReference<AuthenticationManager> authenticationManager;
+
+       protected abstract Authentication processLogin(
+                       CallbackHandler callbackHandler) throws LoginException,
+                       UnsupportedCallbackException, IOException, InterruptedException;
+
+       @Override
+       public void initialize(Subject subject, CallbackHandler callbackHandler,
+                       Map<String, ?> sharedState, Map<String, ?> options) {
+               this.callbackHandler = callbackHandler;
+               this.subject = subject;
+               this.bundleContext = Activator.getBundleContext();
+               this.authenticationManager = bundleContext
+                               .getServiceReference(AuthenticationManager.class);
+       }
+
+       @Override
+       public boolean login() throws LoginException {
+               try {
+                       // thread already logged in
+                       Authentication currentAuth = SecurityContextHolder.getContext()
+                                       .getAuthentication();
+                       if (currentAuth != null) {
+                               if (subject.getPrincipals(Authentication.class).size() == 0) {
+                                       throw new LoginException(
+                                                       "Security context set but not Authentication principal");
+                               } else {
+                                       Authentication principal = subject
+                                                       .getPrincipals(Authentication.class).iterator()
+                                                       .next();
+                                       if (principal != currentAuth)
+                                               throw new LoginException(
+                                                               "Already authenticated with a different auth");
+                               }
+                               return true;
+                       }
+
+                       if (callbackHandler == null)
+                               throw new LoginException("No callback handler available");
+
+                       authentication = processLogin(callbackHandler);
+                       if (authentication != null) {
+                               SecurityContextHolder.getContext().setAuthentication(
+                                               authentication);
+                               return true;
+                       } else {
+                               throw new LoginException("No authentication returned");
+                       }
+               } catch (LoginException e) {
+                       throw e;
+               } catch (ThreadDeath e) {
+                       LoginException le = new LoginException(
+                                       "Spring Security login thread died");
+                       le.initCause(e);
+                       throw le;
+               } catch (Exception e) {
+                       LoginException le = new LoginException(
+                                       "Spring Security login failed");
+                       le.initCause(e);
+                       throw le;
+               }
+       }
+
+       @Override
+       public boolean logout() throws LoginException {
+               SecurityContextHolder.getContext().setAuthentication(null);
+               return true;
+       }
+
+       @Override
+       public boolean commit() throws LoginException {
+               return true;
+       }
+
+       @Override
+       public boolean abort() throws LoginException {
+               SecurityContextHolder.getContext().setAuthentication(null);
+               return true;
+       }
+
+       /**
+        * Return the related {@link BundleContext} (never null), or throws an
+        * Exception if the login module was not properly initialised.
+        */
+       protected BundleContext getBundleContext() {
+               if (bundleContext == null)
+                       throw new ArgeoException("No bundle context provided");
+               return bundleContext;
+       }
+
+       AuthenticationManager getAuthenticationManager() {
+               BundleContext bc = getBundleContext();
+               assert authenticationManager != null;
+               return bc.getService(authenticationManager);
+       }
+
+       // protected UserAdmin getUserAdmin(BundleContextCallback
+       // bundleContextCallback) {
+       // BundleContext bc = bundleContextCallback.getBundleContext();
+       // return bc.getService(bc.getServiceReference(UserAdmin.class));
+       // }
+
+       protected Subject getSubject() {
+               return subject;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/AnonymousLoginModule.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/AnonymousLoginModule.java
new file mode 100644 (file)
index 0000000..6078b8f
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.util.LocaleCallback;
+import org.argeo.util.LocaleUtils;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+/** Login module which caches one subject per thread. */
+public class AnonymousLoginModule extends AbstractLoginModule {
+       private String anonymousRole = "ROLE_ANONYMOUS";
+       /** Comma separated list of locales */
+       private String availableLocales = null;
+
+       @Override
+       protected Authentication processLogin(CallbackHandler callbackHandler)
+                       throws LoginException, UnsupportedCallbackException, IOException,
+                       InterruptedException {
+               Locale selectedLocale = null;
+               // multi locale
+               if (availableLocales != null && !availableLocales.trim().equals("")) {
+                       LocaleCallback localeCallback = new LocaleCallback(availableLocales);
+                       callbackHandler.handle(new Callback[] { localeCallback });
+                       selectedLocale = localeCallback.getSelectedLocale();
+               } else {
+                       callbackHandler.handle(new Callback[] {});
+               }
+
+               List<SimpleGrantedAuthority> authorities = Collections
+                               .singletonList(new SimpleGrantedAuthority(anonymousRole));
+               AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken(
+                               Activator.getSystemKey(), null, authorities);
+
+               Authentication auth = getAuthenticationManager().authenticate(
+                               anonymousToken);
+
+               if (selectedLocale != null)
+                       LocaleUtils.threadLocale.set(selectedLocale);
+               return auth;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java
new file mode 100644 (file)
index 0000000..7ea9c07
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.cms.internal.auth;
+
+import java.io.Console;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Locale;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.ArgeoException;
+import org.argeo.util.LocaleCallback;
+
+/** Callback handler to be used with a command line UI. */
+public class ConsoleCallbackHandler implements CallbackHandler {
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException,
+                       UnsupportedCallbackException {
+               Console console = System.console();
+               if (console == null)
+                       throw new ArgeoException("No console available");
+
+               PrintWriter writer = console.writer();
+               for (int i = 0; i < callbacks.length; i++) {
+                       if (callbacks[i] instanceof TextOutputCallback) {
+                               TextOutputCallback callback = (TextOutputCallback) callbacks[i];
+                               writer.write(callback.getMessage());
+                       } else if (callbacks[i] instanceof NameCallback) {
+                               NameCallback callback = (NameCallback) callbacks[i];
+                               writer.write(callback.getPrompt());
+                               if (callback.getDefaultName() != null)
+                                       writer.write(" (" + callback.getDefaultName() + ")");
+                               writer.write(" : ");
+                               String answer = console.readLine();
+                               if (callback.getDefaultName() != null
+                                               && answer.trim().equals(""))
+                                       callback.setName(callback.getDefaultName());
+                               else
+                                       callback.setName(answer);
+                       } else if (callbacks[i] instanceof PasswordCallback) {
+                               PasswordCallback callback = (PasswordCallback) callbacks[i];
+                               writer.write(callback.getPrompt());
+                               char[] answer = console.readPassword();
+                               callback.setPassword(answer);
+                               Arrays.fill(answer, ' ');
+                       } else if (callbacks[i] instanceof LocaleCallback) {
+                               LocaleCallback callback = (LocaleCallback) callbacks[i];
+                               writer.write(callback.getPrompt());
+                               writer.write("\n");
+                               for (int j = 0; j < callback.getAvailableLocales().size(); j++) {
+                                       Locale locale = callback.getAvailableLocales().get(j);
+                                       writer.print(j + " : " + locale.getDisplayName() + "\n");
+                               }
+                               writer.write("(" + callback.getDefaultIndex() + ") : ");
+                               String answer = console.readLine();
+                               if (answer.trim().equals(""))
+                                       callback.setSelectedIndex(callback.getDefaultIndex());
+                               else
+                                       callback.setSelectedIndex(new Integer(answer.trim()));
+                       }
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/EndUserLoginModule.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/EndUserLoginModule.java
new file mode 100644 (file)
index 0000000..de2a007
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.CredentialNotFoundException;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.security.NodeAuthenticationToken;
+import org.argeo.util.LocaleCallback;
+import org.argeo.util.LocaleUtils;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+
+/** Authenticates an end user */
+public class EndUserLoginModule extends AbstractLoginModule {
+       final static String NODE_REPO_URI = "argeo.node.repo.uri";
+
+       private Long waitBetweenFailedLoginAttempts = 5 * 1000l;
+
+       private Boolean remote = false;
+       /** Comma separated list of locales */
+       private String availableLocales = "";
+
+       @Override
+       protected Authentication processLogin(CallbackHandler callbackHandler)
+                       throws LoginException, UnsupportedCallbackException, IOException,
+                       InterruptedException {
+               // ask for username and password
+               NameCallback nameCallback = new NameCallback("User");
+               PasswordCallback passwordCallback = new PasswordCallback("Password",
+                               false);
+               final String defaultNodeUrl = System.getProperty(NODE_REPO_URI,
+                               "http://localhost:7070/org.argeo.jcr.webapp/remoting/node");
+               NameCallback urlCallback = new NameCallback("Site URL", defaultNodeUrl);
+               LocaleCallback localeCallback = new LocaleCallback(availableLocales);
+               // handle callbacks
+               if (remote)
+                       callbackHandler.handle(new Callback[] { nameCallback,
+                                       passwordCallback, urlCallback, localeCallback });
+               else
+                       callbackHandler.handle(new Callback[] { nameCallback,
+                                       passwordCallback, localeCallback });
+
+               Locale selectedLocale = localeCallback.getSelectedLocale();
+
+               // create credentials
+               final String username = nameCallback.getName();
+               if (username == null || username.trim().equals(""))
+                       throw new CredentialNotFoundException("No credentials provided");
+
+               char[] password = {};
+               if (passwordCallback.getPassword() != null)
+                       password = passwordCallback.getPassword();
+               else
+                       throw new CredentialNotFoundException("No credentials provided");
+
+               NodeAuthenticationToken credentials;
+               if (remote) {
+                       String url = urlCallback.getName();
+                       credentials = new NodeAuthenticationToken(username, password, url);
+               } else {
+                       credentials = new NodeAuthenticationToken(username, password);
+               }
+
+               Authentication auth;
+               try {
+                       auth = getAuthenticationManager().authenticate(credentials);
+               } catch (BadCredentialsException e) {
+                       // wait between failed login attempts
+                       Thread.sleep(waitBetweenFailedLoginAttempts);
+                       throw e;
+               }
+
+               if (selectedLocale != null)
+                       LocaleUtils.threadLocale.set(selectedLocale);
+
+               return auth;
+       }
+
+       @Override
+       public boolean commit() throws LoginException {
+               return super.commit();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/GrantedAuthorityPrincipal.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/GrantedAuthorityPrincipal.java
new file mode 100644 (file)
index 0000000..a0622da
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.springframework.security.core.GrantedAuthority;
+
+/**
+ * A {@link Principal} which is also a {@link GrantedAuthority}, so that the
+ * Spring Security can be used to quickly populate a {@link Subject} principals.
+ */
+public final class GrantedAuthorityPrincipal implements Principal,
+               GrantedAuthority {
+       private static final long serialVersionUID = 6768044196343543328L;
+       private final String authority;
+
+       public GrantedAuthorityPrincipal(String authority) {
+               this.authority = authority;
+       }
+
+       @Override
+       public String getAuthority() {
+               return authority;
+       }
+
+       @Override
+       public String getName() {
+               return authority;
+       }
+
+       @Override
+       public int hashCode() {
+               return getName().hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof GrantedAuthorityPrincipal))
+                       return false;
+               return getName().equals(((GrantedAuthorityPrincipal) obj).getName());
+       }
+
+       @Override
+       public String toString() {
+               return "Granted Authority " + getName();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/OsJcrAuthenticationProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/OsJcrAuthenticationProvider.java
new file mode 100644 (file)
index 0000000..61ed7ba
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.util.Collection;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.ArgeoException;
+import org.argeo.cms.internal.useradmin.SimpleJcrSecurityModel;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.security.OsAuthenticationToken;
+import org.argeo.security.SecurityUtils;
+import org.argeo.security.core.OsAuthenticationProvider;
+import org.argeo.security.jcr.JcrSecurityModel;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/** Relies on OS to authenticate and additionally setup JCR */
+public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
+       private Repository repository;
+       private Session nodeSession;
+
+       private UserDetails userDetails;
+       private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
+
+       private final static String JVM_OSUSER = System.getProperty("user.name");
+
+       public void init() {
+               try {
+                       nodeSession = repository.login();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot initialize", e);
+               }
+       }
+
+       public void destroy() {
+               JcrUtils.logoutQuietly(nodeSession);
+       }
+
+       public Authentication authenticate(Authentication authentication)
+                       throws AuthenticationException {
+               if (authentication instanceof UsernamePasswordAuthenticationToken) {
+                       // deal with remote access to internal server
+                       // FIXME very primitive and unsecure at this sSession adminSession
+                       // =tage
+                       // consider using the keyring for username / password authentication
+                       // or certificate
+                       UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication;
+                       if (!upat.getPrincipal().toString().equals(JVM_OSUSER))
+                               throw new BadCredentialsException("Wrong credentials");
+                       UsernamePasswordAuthenticationToken authen = new UsernamePasswordAuthenticationToken(
+                                       authentication.getPrincipal(),
+                                       authentication.getCredentials(), getBaseAuthorities());
+                       authen.setDetails(userDetails);
+                       return authen;
+               } else if (authentication instanceof OsAuthenticationToken) {
+                       OsAuthenticationToken authen = (OsAuthenticationToken) super
+                                       .authenticate(authentication);
+                       try {
+                               // WARNING: at this stage we assume that the java properties
+                               // will have the same value
+                               Collection<? extends GrantedAuthority> authorities = getBaseAuthorities();
+                               String username = JVM_OSUSER;
+                               Node userProfile = jcrSecurityModel.sync(nodeSession, username,
+                                               SecurityUtils.authoritiesToStringList(authorities));
+                               JcrUserDetails.checkAccountStatus(userProfile);
+
+                               userDetails = new JcrUserDetails(userProfile, authen
+                                               .getCredentials().toString(), authorities);
+                               authen.setDetails(userDetails);
+                               return authen;
+                       } catch (RepositoryException e) {
+                               JcrUtils.discardQuietly(nodeSession);
+                               throw new ArgeoException(
+                                               "Unexpected exception when synchronizing OS and JCR security ",
+                                               e);
+                       }
+               } else {
+                       throw new ArgeoException("Unsupported authentication "
+                                       + authentication.getClass());
+               }
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
+               this.jcrSecurityModel = jcrSecurityModel;
+       }
+
+       @SuppressWarnings("rawtypes")
+       public boolean supports(Class authentication) {
+               return OsAuthenticationToken.class.isAssignableFrom(authentication)
+                               || UsernamePasswordAuthenticationToken.class
+                                               .isAssignableFrom(authentication);
+       }
+}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteJcrAuthenticationProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteJcrAuthenticationProvider.java
new file mode 100644 (file)
index 0000000..b9ebaf7
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+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.UserJcrUtils;
+import org.argeo.security.NodeAuthenticationToken;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.argeo.security.jcr.RemoteJcrRepositoryWrapper;
+import org.osgi.framework.BundleContext;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+/** Connects to a JCR repository and delegates authentication to it. */
+public class RemoteJcrAuthenticationProvider implements AuthenticationProvider,
+               ArgeoNames {
+       private RepositoryFactory repositoryFactory;
+       private BundleContext bundleContext;
+
+       public final static String ROLE_REMOTE = "ROLE_REMOTE";
+
+       public Authentication authenticate(Authentication authentication)
+                       throws AuthenticationException {
+               NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication;
+               String url = siteAuth.getUrl();
+               if (url == null)// TODO? login on own node
+                       throw new ArgeoException("No url set in " + siteAuth);
+               Session session;
+
+               Node userProfile;
+               try {
+                       SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
+                                       siteAuth.getCredentials().toString().toCharArray());
+                       // get repository
+                       Repository repository = new RemoteJcrRepositoryWrapper(
+                                       repositoryFactory, url, sp);
+                       if (bundleContext != null) {
+                               Dictionary<String, String> serviceProperties = new Hashtable<String, String>();
+                               serviceProperties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS,
+                                               ArgeoJcrConstants.ALIAS_NODE);
+                               serviceProperties
+                                               .put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url);
+                               bundleContext.registerService(Repository.class.getName(),
+                                               repository, serviceProperties);
+                       }
+                       // Repository repository = ArgeoJcrUtils.getRepositoryByUri(
+                       // repositoryFactory, url);
+                       // if (repository == null)
+                       // throw new ArgeoException("Cannot connect to " + url);
+
+                       session = repository.login(sp, null);
+
+                       userProfile = UserJcrUtils.getUserProfile(session, sp.getUserID());
+                       JcrUserDetails.checkAccountStatus(userProfile);
+
+                       // Node userHome = UserJcrUtils.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 {
+                       // Node userHome = UserJcrUtils.getUserHome(session);
+                       // retrieve remote roles
+                       List<GrantedAuthority> authoritiesList = new ArrayList<GrantedAuthority>();
+                       if (userProfile != null
+                                       && 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 SimpleGrantedAuthority(roles[i]
+                                                       .getString()));
+                       }
+                       authoritiesList.add(new SimpleGrantedAuthority(ROLE_REMOTE));
+
+                       // create authenticated objects
+                       // GrantedAuthority[] authorities = authoritiesList
+                       // .toArray(new GrantedAuthority[authoritiesList.size()]);
+                       JcrUserDetails userDetails = new JcrUserDetails(userProfile,
+                                       siteAuth.getCredentials().toString(), authoritiesList);
+                       NodeAuthenticationToken authenticated = new NodeAuthenticationToken(
+                                       siteAuth, authoritiesList);
+                       authenticated.setDetails(userDetails);
+                       return authenticated;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException(
+                                       "Unexpected exception when authenticating to " + url, e);
+               }
+       }
+
+       @SuppressWarnings("rawtypes")
+       public boolean supports(Class authentication) {
+               return NodeAuthenticationToken.class.isAssignableFrom(authentication);
+       }
+
+       public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
+               this.repositoryFactory = repositoryFactory;
+       }
+
+       public void setBundleContext(BundleContext bundleContext) {
+               this.bundleContext = bundleContext;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/SystemLoginModule.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/SystemLoginModule.java
new file mode 100644 (file)
index 0000000..5e8587d
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.security.core.InternalAuthentication;
+import org.springframework.security.core.Authentication;
+
+/** Login module which caches one subject per thread. */
+public class SystemLoginModule extends AbstractLoginModule {
+       @Override
+       protected Authentication processLogin(CallbackHandler callbackHandler)
+                       throws LoginException, UnsupportedCallbackException, IOException,
+                       InterruptedException {
+               InternalAuthentication token = new InternalAuthentication(
+                               Activator.getSystemKey());
+               return getAuthenticationManager().authenticate(token);
+       }
+}
index 4d166eaf63db619ed0b761034299a154f30cfd39..5ec9f5087649474b1fc9284a502a8956070412ef 100644 (file)
@@ -1,15 +1,27 @@
 package org.argeo.cms.internal.kernel;
 
+import java.util.UUID;
+
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
+/**
+ * Activates the {@link Kernel} from the provided {@link BundleContext}. Gives
+ * access to kernel information for the rest of the bundle (and only it)
+ */
 public class Activator implements BundleActivator {
+       private final static String systemKey = UUID.randomUUID().toString();
+
+       private static BundleContext bundleContext;
        private Kernel kernel;
 
        @Override
        public void start(BundleContext context) throws Exception {
+               assert bundleContext == null;
                assert kernel == null;
-               kernel = new Kernel(context);
+
+               bundleContext = context;
+               kernel = new Kernel(bundleContext);
                kernel.init();
        }
 
@@ -17,6 +29,23 @@ public class Activator implements BundleActivator {
        public void stop(BundleContext context) throws Exception {
                kernel.destroy();
                kernel = null;
+               bundleContext = null;
+       }
+
+       /**
+        * Singleton interface to the {@link BundleContext} related to the calling
+        * thread. Can be used only within the CMS bundle.
+        */
+       public static BundleContext getBundleContext() {
+               return bundleContext;
+       }
+
+       /**
+        * @return a String which is guaranteed to be unique between and constant
+        *         within a Java static context (typically a VM launch)
+        */
+       public final static String getSystemKey() {
+               return systemKey;
        }
 
 }
index d0aec2023cbcaccbf32eca9594d00dfec0d77d0c..13f48f369438f1b6191b29a5e83639e7a60b4550 100644 (file)
@@ -8,12 +8,11 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.cms.CmsException;
 import org.argeo.cms.internal.useradmin.JcrUserAdmin;
-import org.argeo.security.SecurityUtils;
+import org.argeo.cms.internal.useradmin.SimpleJcrSecurityModel;
+import org.argeo.cms.internal.useradmin.jackrabbit.JackrabbitUserAdminService;
 import org.argeo.security.UserAdminService;
 import org.argeo.security.core.InternalAuthentication;
 import org.argeo.security.core.InternalAuthenticationProvider;
-import org.argeo.security.jcr.SimpleJcrSecurityModel;
-import org.argeo.security.jcr.jackrabbit.JackrabbitUserAdminService;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.useradmin.UserAdmin;
@@ -52,9 +51,9 @@ class NodeSecurity implements AuthenticationManager {
                this.bundleContext = bundleContext;
 
                internalAuth = new InternalAuthenticationProvider(
-                               SecurityUtils.getStaticKey());
+                               Activator.getSystemKey());
                anonymousAuth = new AnonymousAuthenticationProvider(
-                               SecurityUtils.getStaticKey());
+                               Activator.getSystemKey());
 
                // user admin
                userAdminService = new JackrabbitUserAdminService();
index 55194eab828d01e2632327b075350570485f82ca..cc1a07499c135d0f077e5f91c47cd3471ac60d3f 100644 (file)
@@ -1,15 +1,15 @@
 USER {
-    org.argeo.security.login.EndUserLoginModule requisite;
+    org.argeo.cms.internal.auth.EndUserLoginModule requisite;
     org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
 };
 
 ANONYMOUS {
-    org.argeo.security.login.AnonymousLoginModule requisite;
+    org.argeo.cms.internal.auth.AnonymousLoginModule requisite;
     org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
 };
 
 SYSTEM {
-    org.argeo.security.login.SystemLoginModule requisite;
+    org.argeo.cms.internal.auth.SystemLoginModule requisite;
     org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
 };
 
index 94051d92eb0401f79d3fbe45bbffb8a969139dff..ecaf5e0aff2e64d9253ebee55a032f96c2eb136e 100644 (file)
@@ -15,7 +15,6 @@ import org.argeo.jcr.JcrUtils;
 import org.argeo.security.UserAdminService;
 import org.argeo.security.jcr.JcrSecurityModel;
 import org.argeo.security.jcr.JcrUserDetails;
-import org.argeo.security.jcr.SimpleJcrSecurityModel;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/OsJcrUserAdminService.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/OsJcrUserAdminService.java
new file mode 100644 (file)
index 0000000..4ad2ad1
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.ArgeoException;
+import org.argeo.cms.internal.auth.OsJcrAuthenticationProvider;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.security.UserAdminService;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+/**
+ * Dummy user service to be used when running as a single OS user (typically
+ * desktop). TODO integrate with JCR user / groups
+ */
+public class OsJcrUserAdminService implements UserAdminService {
+       private Repository repository;
+
+       /** In memory roles provided by applications. */
+       private List<String> roles = new ArrayList<String>();
+
+       // private Session adminSession;
+
+       public void init() {
+               // try {
+               // adminSession = repository.login();
+               // } catch (RepositoryException e) {
+               // throw new ArgeoException("Cannot initialize", e);
+               // }
+       }
+
+       public void destroy() {
+               // JcrUtils.logoutQuietly(adminSession);
+       }
+
+       /** <b>Unsupported</b> */
+       public void createUser(UserDetails user) {
+               throw new UnsupportedOperationException();
+       }
+
+       /** Does nothing */
+       public void updateUser(UserDetails user) {
+
+       }
+
+       /** <b>Unsupported</b> */
+       public void deleteUser(String username) {
+               throw new UnsupportedOperationException();
+       }
+
+       /** <b>Unsupported</b> */
+       public void changePassword(String oldPassword, String newPassword) {
+               throw new UnsupportedOperationException();
+       }
+
+       public boolean userExists(String username) {
+               if (getSPropertyUsername().equals(username))
+                       return true;
+               else
+                       return false;
+       }
+
+       public UserDetails loadUserByUsername(String username)
+                       throws UsernameNotFoundException, DataAccessException {
+               if (getSPropertyUsername().equals(username)) {
+                       UserDetails userDetails;
+                       if (repository != null) {
+                               Session adminSession = null;
+                               try {
+                                       adminSession = repository.login();
+                                       Node userProfile = UserJcrUtils.getUserProfile(
+                                                       adminSession, username);
+                                       userDetails = new JcrUserDetails(userProfile, "",
+                                                       OsJcrAuthenticationProvider.getBaseAuthorities());
+                               } catch (RepositoryException e) {
+                                       throw new ArgeoException(
+                                                       "Cannot retrieve user profile for " + username, e);
+                               } finally {
+                                       JcrUtils.logoutQuietly(adminSession);
+                               }
+                       } else {
+                               userDetails = new User(username, "", true, true, true, true,
+                                               OsJcrAuthenticationProvider.getBaseAuthorities());
+                       }
+                       return userDetails;
+               } else {
+                       throw new UnsupportedOperationException();
+               }
+       }
+
+       protected final String getSPropertyUsername() {
+               return System.getProperty("user.name");
+       }
+
+       public Set<String> listUsers() {
+               Set<String> set = new HashSet<String>();
+               set.add(getSPropertyUsername());
+               return set;
+       }
+
+       public Set<String> listUsersInRole(String role) {
+               Set<String> set = new HashSet<String>();
+               set.add(getSPropertyUsername());
+               return set;
+       }
+
+       /** Does nothing */
+       public void synchronize() {
+       }
+
+       /** <b>Unsupported</b> */
+       public void newRole(String role) {
+               roles.add(role);
+       }
+
+       public Set<String> listEditableRoles() {
+               return new HashSet<String>(roles);
+       }
+
+       /** <b>Unsupported</b> */
+       public void deleteRole(String role) {
+               roles.remove(role);
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/SimpleJcrSecurityModel.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/SimpleJcrSecurityModel.java
new file mode 100644 (file)
index 0000000..029719c
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin;
+
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.VersionManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoJcrConstants;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.security.jcr.JcrSecurityModel;
+
+/**
+ * Manages data expected by the Argeo security model, such as user home and
+ * profile.
+ */
+public class SimpleJcrSecurityModel implements JcrSecurityModel {
+       private final static Log log = LogFactory
+                       .getLog(SimpleJcrSecurityModel.class);
+       // ArgeoNames not implemented as interface in order to ease derivation by
+       // Jackrabbit bundles
+
+       /** The home base path. */
+       private String homeBasePath = "/home";
+
+       public synchronized Node sync(Session session, String username,
+                       List<String> roles) {
+               // TODO check user name validity (e.g. should not start by ROLE_)
+
+               try {
+                       Node userHome = UserJcrUtils.getUserHome(session, username);
+                       if (userHome == null) {
+                               String homePath = generateUserPath(homeBasePath, username);
+                               userHome = JcrUtils.mkdirs(session, homePath);
+                               // userHome = JcrUtils.mkfolders(session, homePath);
+                               userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
+                               userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
+                               session.save();
+
+                               JcrUtils.clearAccessControList(session, homePath, username);
+                               JcrUtils.addPrivilege(session, homePath, username,
+                                               Privilege.JCR_ALL);
+                       } else {
+                               // for backward compatibility with pre 1.0 security model
+                               if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) {
+                                       userHome.getNode(ArgeoNames.ARGEO_PROFILE).remove();
+                                       userHome.getSession().save();
+                               }
+                       }
+
+                       // Remote roles
+                       if (roles != null) {
+                               // writeRemoteRoles(userHome, roles);
+                       }
+
+                       Node userProfile = UserJcrUtils.getUserProfile(session, username);
+                       if (userProfile == null) {
+                               String personPath = generateUserPath(
+                                               ArgeoJcrConstants.PEOPLE_BASE_PATH, username);
+                               Node personBase = JcrUtils.mkdirs(session, personPath);
+                               userProfile = personBase.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);
+                               session.save();
+
+                               JcrUtils.clearAccessControList(session, userProfile.getPath(),
+                                               username);
+                               JcrUtils.addPrivilege(session, userProfile.getPath(), username,
+                                               Privilege.JCR_READ);
+
+                               VersionManager versionManager = session.getWorkspace()
+                                               .getVersionManager();
+                               if (versionManager.isCheckedOut(userProfile.getPath()))
+                                       versionManager.checkin(userProfile.getPath());
+
+                       }
+
+                       // Remote roles
+                       if (roles != null) {
+                               writeRemoteRoles(userProfile, roles);
+                       }
+                       return userProfile;
+               } catch (RepositoryException e) {
+                       JcrUtils.discardQuietly(session);
+                       throw new ArgeoException("Cannot sync node security model for "
+                                       + username, e);
+               }
+       }
+
+       /** Generate path for a new user home */
+       protected String generateUserPath(String base, String username) {
+               int atIndex = username.indexOf('@');
+               if (atIndex > 0) {
+                       String domain = username.substring(0, atIndex);
+                       String name = username.substring(atIndex + 1);
+                       return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/'
+                                       + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/'
+                                       + name;
+               } else if (atIndex == 0 || atIndex == (username.length() - 1)) {
+                       throw new ArgeoException("Unsupported username " + username);
+               } else {
+                       return base + '/' + JcrUtils.firstCharsToPath(username, 2) + '/'
+                                       + username;
+               }
+       }
+
+       /** Write remote roles used by remote access in the home directory */
+       protected void writeRemoteRoles(Node userHome, List<String> roles)
+                       throws RepositoryException {
+               boolean writeRoles = false;
+               if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
+                       Value[] remoteRoles = userHome.getProperty(
+                                       ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
+                       if (remoteRoles.length != roles.size())
+                               writeRoles = true;
+                       else
+                               for (int i = 0; i < remoteRoles.length; i++)
+                                       if (!remoteRoles[i].getString().equals(roles.get(i)))
+                                               writeRoles = true;
+               } else
+                       writeRoles = true;
+
+               if (writeRoles) {
+                       userHome.getSession().getWorkspace().getVersionManager()
+                                       .checkout(userHome.getPath());
+                       String[] roleIds = roles.toArray(new String[roles.size()]);
+                       userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds);
+                       JcrUtils.updateLastModified(userHome);
+                       userHome.getSession().save();
+                       userHome.getSession().getWorkspace().getVersionManager()
+                                       .checkin(userHome.getPath());
+                       if (log.isDebugEnabled())
+                               log.debug("Wrote remote roles " + roles + " for "
+                                               + userHome.getProperty(ArgeoNames.ARGEO_USER_ID));
+               }
+
+       }
+
+       public void setHomeBasePath(String homeBasePath) {
+               this.homeBasePath = homeBasePath;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitSecurityModel.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitSecurityModel.java
new file mode 100644 (file)
index 0000000..de7f724
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.jackrabbit;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+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.cms.internal.useradmin.SimpleJcrSecurityModel;
+import org.argeo.jcr.ArgeoNames;
+
+/** Make sure that user authorizable exists before syncing user directories. */
+public class JackrabbitSecurityModel extends SimpleJcrSecurityModel {
+       private final static Log log = LogFactory
+                       .getLog(JackrabbitSecurityModel.class);
+
+       @Override
+       public synchronized Node sync(Session session, String username,
+                       List<String> roles) {
+               if (!(session instanceof JackrabbitSession))
+                       return super.sync(session, username, roles);
+
+               try {
+                       UserManager userManager = ((JackrabbitSession) session)
+                                       .getUserManager();
+                       User user = (User) userManager.getAuthorizable(username);
+                       if (user != null) {
+                               String principalName = user.getPrincipal().getName();
+                               if (!principalName.equals(username)) {
+                                       log.warn("Jackrabbit principal is '" + principalName
+                                                       + "' but username is '" + username
+                                                       + "'. Recreating...");
+                                       user.remove();
+                                       user = userManager.createUser(username, "");
+                               }
+                       } else {
+                               // create new principal
+                               user = userManager.createUser(username, "");
+                               log.info(username + " added as Jackrabbit user " + user);
+                       }
+
+                       // generic JCR sync
+                       Node userProfile = super.sync(session, username, roles);
+
+                       Boolean enabled = userProfile.getProperty(ArgeoNames.ARGEO_ENABLED)
+                                       .getBoolean();
+                       if (enabled && user.isDisabled())
+                               user.disable(null);
+                       else if (!enabled && !user.isDisabled())
+                               user.disable(userProfile.getPath() + " is disabled");
+
+                       // Sync Jackrabbit roles
+                       if (roles != null)
+                               syncRoles(userManager, user, roles);
+
+                       return userProfile;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException(
+                                       "Cannot perform Jackrabbit specific operations", e);
+               }
+       }
+
+       /** Make sure Jackrabbit roles are in line with authentication */
+       void syncRoles(UserManager userManager, User user, List<String> roles)
+                       throws RepositoryException {
+               List<String> userGroupIds = new ArrayList<String>();
+               for (String role : roles) {
+                       Group group = (Group) userManager.getAuthorizable(role);
+                       if (group == null) {
+                               group = userManager.createGroup(role);
+                               log.info(role + " added as " + group);
+                       }
+                       if (!group.isMember(user))
+                               group.addMember(user);
+                       userGroupIds.add(role);
+               }
+
+               // check if user has not been removed from some groups
+               for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
+                       Group group = it.next();
+                       if (!userGroupIds.contains(group.getID()))
+                               group.removeMember(user);
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitUserAdminService.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitUserAdminService.java
new file mode 100644 (file)
index 0000000..f846e1c
--- /dev/null
@@ -0,0 +1,362 @@
+package org.argeo.cms.internal.useradmin.jackrabbit;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+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.apache.jackrabbit.core.security.authentication.CryptedSimpleCredentials;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.security.NodeAuthenticationToken;
+import org.argeo.security.UserAdminService;
+import org.argeo.cms.internal.auth.GrantedAuthorityPrincipal;
+import org.argeo.security.jcr.JcrSecurityModel;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+/**
+ * An implementation of {@link UserAdminService} which closely wraps Jackrabbits
+ * implementation. Roles are implemented with Groups.
+ */
+public class JackrabbitUserAdminService implements UserAdminService,
+               AuthenticationProvider {
+       final static String userRole = "ROLE_USER";
+       final static String adminRole = "ROLE_ADMIN";
+
+       private Repository repository;
+       private JcrSecurityModel securityModel;
+
+       private JackrabbitSession adminSession = null;
+
+       private String superUsername = "root";
+       private String superUserInitialPassword = "demo";
+
+       public void init() throws RepositoryException {
+               Authentication authentication = SecurityContextHolder.getContext()
+                               .getAuthentication();
+               authentication.getName();
+               adminSession = (JackrabbitSession) repository.login();
+               Authorizable adminGroup = getUserManager().getAuthorizable(adminRole);
+               if (adminGroup == null) {
+                       adminGroup = getUserManager().createGroup(adminRole);
+                       adminSession.save();
+               }
+               Authorizable superUser = getUserManager()
+                               .getAuthorizable(superUsername);
+               if (superUser == null) {
+                       superUser = getUserManager().createUser(superUsername,
+                                       superUserInitialPassword);
+                       ((Group) adminGroup).addMember(superUser);
+                       securityModel.sync(adminSession, superUsername, null);
+                       adminSession.save();
+               }
+       }
+
+       public void destroy() throws RepositoryException {
+               JcrUtils.logoutQuietly(adminSession);
+       }
+
+       private UserManager getUserManager() throws RepositoryException {
+               return adminSession.getUserManager();
+       }
+
+       @Override
+       public void createUser(UserDetails user) {
+               try {
+                       // FIXME workaround for issue in new user wizard where
+                       // security model is hardcoded and it already exists
+                       if (getUserManager().getAuthorizable(user.getUsername()) == null) {
+                               getUserManager().createUser(user.getUsername(),
+                                               user.getPassword());
+                               securityModel.sync(adminSession, user.getUsername(), null);
+                       }
+                       updateUser(user);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot create user " + user, e);
+               }
+       }
+
+       @Override
+       public void updateUser(UserDetails userDetails) {
+               try {
+                       User user = (User) getUserManager().getAuthorizable(
+                                       userDetails.getUsername());
+                       if (user == null)
+                               throw new ArgeoException("No user " + userDetails.getUsername());
+
+                       // new password
+                       String newPassword = userDetails.getPassword();
+                       if (!newPassword.trim().equals("")) {
+                               SimpleCredentials sp = new SimpleCredentials(
+                                               userDetails.getUsername(), newPassword.toCharArray());
+                               CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
+                                               .getCredentials();
+                               if (!credentials.matches(sp))
+                                       user.changePassword(new String(newPassword));
+                       }
+
+                       List<String> roles = new ArrayList<String>();
+                       for (GrantedAuthority ga : userDetails.getAuthorities()) {
+                               if (ga.getAuthority().equals(userRole))
+                                       continue;
+                               roles.add(ga.getAuthority());
+                       }
+
+                       for (Iterator<Group> it = user.memberOf(); it.hasNext();) {
+                               Group group = it.next();
+                               if (roles.contains(group.getPrincipal().getName()))
+                                       roles.remove(group.getPrincipal().getName());
+                               else
+                                       group.removeMember(user);
+                       }
+
+                       // remaining (new ones)
+                       for (String role : roles) {
+                               Group group = (Group) getUserManager().getAuthorizable(role);
+                               if (group == null)
+                                       throw new ArgeoException("Group " + role
+                                                       + " does not exist,"
+                                                       + " whereas it was granted to user " + userDetails);
+                               group.addMember(user);
+                       }
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot update user details", e);
+               }
+
+       }
+
+       @Override
+       public void deleteUser(String username) {
+               try {
+                       getUserManager().getAuthorizable(username).remove();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot remove user " + username, e);
+               }
+       }
+
+       @Override
+       public void changePassword(String oldPassword, String newPassword) {
+               Authentication authentication = SecurityContextHolder.getContext()
+                               .getAuthentication();
+               String username = authentication.getName();
+               try {
+                       SimpleCredentials sp = new SimpleCredentials(username,
+                                       oldPassword.toCharArray());
+                       User user = (User) getUserManager().getAuthorizable(username);
+                       CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
+                                       .getCredentials();
+                       if (credentials.matches(sp))
+                               user.changePassword(newPassword);
+                       else
+                               throw new BadCredentialsException("Bad credentials provided");
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot change password for user "
+                                       + username, e);
+               }
+       }
+
+       @Override
+       public boolean userExists(String username) {
+               try {
+                       Authorizable authorizable = getUserManager().getAuthorizable(
+                                       username);
+                       if (authorizable != null && authorizable instanceof User)
+                               return true;
+                       return false;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot check whether user " + username
+                                       + " exists ", e);
+               }
+       }
+
+       @Override
+       public Set<String> listUsers() {
+               LinkedHashSet<String> res = new LinkedHashSet<String>();
+               try {
+                       Iterator<Authorizable> users = getUserManager().findAuthorizables(
+                                       "rep:principalName", null, UserManager.SEARCH_TYPE_USER);
+                       while (users.hasNext()) {
+                               res.add(users.next().getPrincipal().getName());
+                       }
+                       return res;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot list users", e);
+               }
+       }
+
+       @Override
+       public Set<String> listUsersInRole(String role) {
+               LinkedHashSet<String> res = new LinkedHashSet<String>();
+               try {
+                       Group group = (Group) getUserManager().getAuthorizable(role);
+                       Iterator<Authorizable> users = group.getMembers();
+                       // NB: not recursive
+                       while (users.hasNext()) {
+                               res.add(users.next().getPrincipal().getName());
+                       }
+                       return res;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot list users in role " + role, e);
+               }
+       }
+
+       @Override
+       public void synchronize() {
+       }
+
+       @Override
+       public void newRole(String role) {
+               try {
+                       getUserManager().createGroup(role);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot create role " + role, e);
+               }
+       }
+
+       @Override
+       public Set<String> listEditableRoles() {
+               LinkedHashSet<String> res = new LinkedHashSet<String>();
+               try {
+                       Iterator<Authorizable> groups = getUserManager().findAuthorizables(
+                                       "rep:principalName", null, UserManager.SEARCH_TYPE_GROUP);
+                       while (groups.hasNext()) {
+                               res.add(groups.next().getPrincipal().getName());
+                       }
+                       return res;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot list groups", e);
+               }
+       }
+
+       @Override
+       public void deleteRole(String role) {
+               try {
+                       getUserManager().getAuthorizable(role).remove();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot remove role " + role, e);
+               }
+       }
+
+       @Override
+       public UserDetails loadUserByUsername(String username)
+                       throws UsernameNotFoundException, DataAccessException {
+               try {
+                       User user = (User) getUserManager().getAuthorizable(username);
+                       if (user == null)
+                               throw new UsernameNotFoundException("User " + username
+                                               + " cannot be found");
+                       return loadJcrUserDetails(adminSession, username);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot load user " + username, e);
+               }
+       }
+
+       protected JcrUserDetails loadJcrUserDetails(Session session, String username)
+                       throws RepositoryException {
+               if (username == null)
+                       username = session.getUserID();
+               User user = (User) getUserManager().getAuthorizable(username);
+               ArrayList<GrantedAuthorityPrincipal> authorities = new ArrayList<GrantedAuthorityPrincipal>();
+               // FIXME make it more generic
+               authorities.add(new GrantedAuthorityPrincipal("ROLE_USER"));
+               Iterator<Group> groups = user.declaredMemberOf();
+               while (groups.hasNext()) {
+                       Group group = groups.next();
+                       // String role = "ROLE_"
+                       // + group.getPrincipal().getName().toUpperCase();
+                       String role = group.getPrincipal().getName();
+                       authorities.add(new GrantedAuthorityPrincipal(role));
+               }
+
+               Node userProfile = UserJcrUtils.getUserProfile(session, username);
+               JcrUserDetails userDetails = new JcrUserDetails(userProfile, "",
+                               authorities);
+               return userDetails;
+       }
+
+       // AUTHENTICATION PROVIDER
+       public synchronized Authentication authenticate(
+                       Authentication authentication) throws AuthenticationException {
+               NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication;
+               String username = siteAuth.getName();
+               if (!(siteAuth.getCredentials() instanceof char[]))
+                       throw new ArgeoException("Only char array passwords are supported");
+               char[] password = (char[]) siteAuth.getCredentials();
+               try {
+                       SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
+                                       password);
+                       User user = (User) getUserManager().getAuthorizable(username);
+                       if (user == null)
+                               throw new BadCredentialsException("Bad credentials");
+                       CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
+                                       .getCredentials();
+                       // String providedPassword = siteAuth.getCredentials().toString();
+                       if (!credentials.matches(sp))
+                               throw new BadCredentialsException("Bad credentials");
+
+                       // session = repository.login(sp, null);
+
+                       Node userProfile = UserJcrUtils.getUserProfile(adminSession,
+                                       username);
+                       JcrUserDetails.checkAccountStatus(userProfile);
+               } catch (BadCredentialsException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new BadCredentialsException(
+                                       "Cannot authenticate " + siteAuth, e);
+               } finally {
+                       Arrays.fill(password, '*');
+               }
+
+               try {
+                       JcrUserDetails userDetails = loadJcrUserDetails(adminSession,
+                                       username);
+                       NodeAuthenticationToken authenticated = new NodeAuthenticationToken(
+                                       siteAuth, userDetails.getAuthorities());
+                       authenticated.setDetails(userDetails);
+                       return authenticated;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException(
+                                       "Unexpected exception when authenticating " + siteAuth, e);
+               }
+       }
+
+       @SuppressWarnings("rawtypes")
+       public boolean supports(Class authentication) {
+               return UsernamePasswordAuthenticationToken.class
+                               .isAssignableFrom(authentication);
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       public void setSecurityModel(JcrSecurityModel securityModel) {
+               this.securityModel = securityModel;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/ScopedSessionProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/ScopedSessionProvider.java
new file mode 100644 (file)
index 0000000..dcb1399
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.jackrabbit;
+
+import java.io.Serializable;
+
+import javax.jcr.LoginException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.server.SessionProvider;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoJcrConstants;
+import org.argeo.jcr.JcrUtils;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/**
+ * Session provider assuming a single workspace and a short life cycle,
+ * typically a Spring bean of scope (web) 'session'.
+ */
+public class ScopedSessionProvider implements SessionProvider, Serializable {
+       private static final long serialVersionUID = 6589775984177317058L;
+       private static final Log log = LogFactory
+                       .getLog(ScopedSessionProvider.class);
+       private transient HttpSession httpSession = null;
+       private transient Session jcrSession = null;
+
+       private transient String currentRepositoryName = null;
+       private transient String currentWorkspaceName = null;
+       private transient String currentJcrUser = null;
+
+       // private transient String anonymousUserId = "anonymous";
+
+       public Session getSession(HttpServletRequest request, Repository rep,
+                       String workspace) throws LoginException, ServletException,
+                       RepositoryException {
+
+               Authentication authentication = SecurityContextHolder.getContext()
+                               .getAuthentication();
+               if (authentication == null)
+                       throw new ArgeoException(
+                                       "Request not authenticated by Spring Security");
+               String springUser = authentication.getName();
+
+               // HTTP
+               String requestJcrRepository = (String) request
+                               .getAttribute(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
+
+               // HTTP session
+               if (httpSession != null
+                               && !httpSession.getId().equals(request.getSession().getId()))
+                       throw new ArgeoException(
+                                       "Only session scope is supported in this mode");
+               if (httpSession == null)
+                       httpSession = request.getSession();
+
+               // Initializes current values
+               if (currentRepositoryName == null)
+                       currentRepositoryName = requestJcrRepository;
+               if (currentWorkspaceName == null)
+                       currentWorkspaceName = workspace;
+               if (currentJcrUser == null)
+                       currentJcrUser = springUser;
+
+               // logout if there was a change in session coordinates
+               if (jcrSession != null)
+                       if (!currentRepositoryName.equals(requestJcrRepository)) {
+                               if (log.isDebugEnabled())
+                                       log.debug(getHttpSessionId() + " Changed from repository '"
+                                                       + currentRepositoryName + "' to '"
+                                                       + requestJcrRepository
+                                                       + "', logging out cached JCR session.");
+                               logout();
+                       } else if (!currentWorkspaceName.equals(workspace)) {
+                               if (log.isDebugEnabled())
+                                       log.debug(getHttpSessionId() + " Changed from workspace '"
+                                                       + currentWorkspaceName + "' to '" + workspace
+                                                       + "', logging out cached JCR session.");
+                               logout();
+                       } else if (!currentJcrUser.equals(springUser)) {
+                               if (log.isDebugEnabled())
+                                       log.debug(getHttpSessionId() + " Changed from user '"
+                                                       + currentJcrUser + "' to '" + springUser
+                                                       + "', logging out cached JCR session.");
+                               logout();
+                       }
+
+               // login if needed
+               if (jcrSession == null)
+                       try {
+                               Session session = login(rep, workspace);
+                               if (!session.getUserID().equals(springUser)) {
+                                       JcrUtils.logoutQuietly(session);
+                                       throw new ArgeoException("Spring Security user '"
+                                                       + springUser + "' not in line with JCR user '"
+                                                       + session.getUserID() + "'");
+                               }
+                               currentRepositoryName = requestJcrRepository;
+                               // do not use workspace variable which may be null
+                               currentWorkspaceName = session.getWorkspace().getName();
+                               currentJcrUser = session.getUserID();
+
+                               jcrSession = session;
+                               return jcrSession;
+                       } catch (RepositoryException e) {
+                               throw new ArgeoException("Cannot open session to workspace "
+                                               + workspace, e);
+                       }
+
+               // returns cached session
+               return jcrSession;
+       }
+
+       protected Session login(Repository repository, String workspace)
+                       throws RepositoryException {
+               Session session = repository.login(workspace);
+               if (log.isDebugEnabled())
+                       log.debug(getHttpSessionId() + " User '" + session.getUserID()
+                                       + "' logged in workspace '"
+                                       + session.getWorkspace().getName() + "' of repository '"
+                                       + currentRepositoryName + "'");
+               return session;
+       }
+
+       public void releaseSession(Session session) {
+               if (log.isTraceEnabled())
+                       log.trace(getHttpSessionId() + " Releasing JCR session " + session);
+       }
+
+       protected void logout() {
+               JcrUtils.logoutQuietly(jcrSession);
+               jcrSession = null;
+       }
+
+       protected final String getHttpSessionId() {
+               return httpSession != null ? httpSession.getId() : "<null>";
+       }
+
+       public void init() {
+       }
+
+       public void destroy() {
+               logout();
+               if (getHttpSessionId() != null)
+                       if (log.isDebugEnabled())
+                               log.debug(getHttpSessionId()
+                                               + " Cleaned up provider for web session ");
+               httpSession = null;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoLdapShaPasswordEncoder.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoLdapShaPasswordEncoder.java
new file mode 100644 (file)
index 0000000..a1d25e9
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.ldap;
+
+import org.springframework.security.authentication.encoding.LdapShaPasswordEncoder;
+
+/**
+ * {@link LdapShaPasswordEncoder} allowing to configure the usage of salt (APache
+ * Directory Server 1.0 does not support bind with SSHA)
+ */
+public class ArgeoLdapShaPasswordEncoder extends LdapShaPasswordEncoder {
+       private Boolean useSalt = true;
+
+       @Override
+       public String encodePassword(String rawPass, Object salt) {
+               return super.encodePassword(rawPass, useSalt ? salt : null);
+       }
+
+       public void setUseSalt(Boolean useSalt) {
+               this.useSalt = useSalt;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoLdapUserDetailsManager.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoLdapUserDetailsManager.java
new file mode 100644 (file)
index 0000000..4381fa9
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.ldap;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.argeo.ArgeoException;
+import org.argeo.security.UserAdminService;
+import org.springframework.ldap.core.ContextSource;
+import org.springframework.security.authentication.encoding.PasswordEncoder;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.ldap.userdetails.LdapUserDetailsManager;
+
+/** Extends {@link LdapUserDetailsManager} by adding password encoding support. */
+@SuppressWarnings("deprecation")
+public class ArgeoLdapUserDetailsManager extends LdapUserDetailsManager
+               implements UserAdminService {
+       private String superUsername = "root";
+       private ArgeoUserAdminDaoLdap userAdminDao;
+       private PasswordEncoder passwordEncoder;
+       private final Random random;
+
+       public ArgeoLdapUserDetailsManager(ContextSource contextSource) {
+               super(contextSource);
+               this.random = createRandom();
+       }
+
+       private static Random createRandom() {
+               try {
+                       return SecureRandom.getInstance("SHA1PRNG");
+               } catch (NoSuchAlgorithmException e) {
+                       return new Random(System.currentTimeMillis());
+               }
+       }
+
+       @Override
+       public void changePassword(String oldPassword, String newPassword) {
+               Authentication authentication = SecurityContextHolder.getContext()
+                               .getAuthentication();
+               if (authentication == null)
+                       throw new ArgeoException(
+                                       "Cannot change password without authentication");
+               String username = authentication.getName();
+               UserDetails userDetails = loadUserByUsername(username);
+               String currentPassword = userDetails.getPassword();
+               if (currentPassword == null)
+                       throw new ArgeoException("Cannot access current password");
+               if (!passwordEncoder
+                               .isPasswordValid(currentPassword, oldPassword, null))
+                       throw new ArgeoException("Old password invalid");
+               // Spring Security LDAP 2.0 is buggy when used with OpenLDAP and called
+               // with oldPassword argument
+               super.changePassword(null, encodePassword(newPassword));
+       }
+
+       public void newRole(String role) {
+               userAdminDao.createRole(role, superUsername);
+       }
+
+       public void synchronize() {
+               for (String username : userAdminDao.listUsers())
+                       loadUserByUsername(username);
+               // TODO: find a way to remove from JCR
+       }
+
+       public void deleteRole(String role) {
+               userAdminDao.deleteRole(role);
+       }
+
+       public Set<String> listUsers() {
+               return userAdminDao.listUsers();
+       }
+
+       public Set<String> listUsersInRole(String role) {
+               Set<String> lst = new TreeSet<String>(
+                               userAdminDao.listUsersInRole(role));
+               Iterator<String> it = lst.iterator();
+               while (it.hasNext()) {
+                       if (it.next().equals(superUsername)) {
+                               it.remove();
+                               break;
+                       }
+               }
+               return lst;
+       }
+
+       public List<String> listUserRoles(String username) {
+               UserDetails userDetails = loadUserByUsername(username);
+               List<String> roles = new ArrayList<String>();
+               for (GrantedAuthority ga : userDetails.getAuthorities()) {
+                       roles.add(ga.getAuthority());
+               }
+               return Collections.unmodifiableList(roles);
+       }
+
+       public Set<String> listEditableRoles() {
+               return userAdminDao.listEditableRoles();
+       }
+
+       protected String encodePassword(String password) {
+               if (!password.startsWith("{")) {
+                       byte[] salt = new byte[16];
+                       random.nextBytes(salt);
+                       return passwordEncoder.encodePassword(password, salt);
+               } else {
+                       return password;
+               }
+       }
+
+       public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+               this.passwordEncoder = passwordEncoder;
+       }
+
+       public void setSuperUsername(String superUsername) {
+               this.superUsername = superUsername;
+       }
+
+       public void setUserAdminDao(ArgeoUserAdminDaoLdap userAdminDao) {
+               this.userAdminDao = userAdminDao;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoUserAdminDaoLdap.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoUserAdminDaoLdap.java
new file mode 100644 (file)
index 0000000..faead2e
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.ldap;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+
+import org.springframework.ldap.core.ContextExecutor;
+import org.springframework.ldap.core.ContextMapper;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DistinguishedName;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+import org.springframework.security.ldap.LdapUsernameToDnMapper;
+import org.springframework.security.ldap.LdapUtils;
+
+/**
+ * Wraps low-level LDAP operation on user and roles, used by
+ * {@link ArgeoLdapUserDetailsManager}
+ */
+public class ArgeoUserAdminDaoLdap {
+       private String userBase;
+       private String usernameAttribute;
+       private String groupBase;
+       private String[] groupClasses;
+
+       private String groupRoleAttribute;
+       private String groupMemberAttribute;
+       private String defaultRole;
+       private String rolePrefix;
+
+       private final LdapTemplate ldapTemplate;
+       private LdapUsernameToDnMapper usernameMapper;
+
+       /**
+        * Standard constructor, using the LDAP context source shared with Spring
+        * Security components.
+        */
+       public ArgeoUserAdminDaoLdap(BaseLdapPathContextSource contextSource) {
+               this.ldapTemplate = new LdapTemplate(contextSource);
+       }
+
+       @SuppressWarnings("unchecked")
+       public synchronized Set<String> listUsers() {
+               List<String> usernames = (List<String>) ldapTemplate.listBindings(
+                               new DistinguishedName(userBase), new ContextMapper() {
+                                       public Object mapFromContext(Object ctxArg) {
+                                               DirContextAdapter ctx = (DirContextAdapter) ctxArg;
+                                               return ctx.getStringAttribute(usernameAttribute);
+                                       }
+                               });
+
+               return Collections
+                               .unmodifiableSortedSet(new TreeSet<String>(usernames));
+       }
+
+       @SuppressWarnings("unchecked")
+       public Set<String> listEditableRoles() {
+               return Collections.unmodifiableSortedSet(new TreeSet<String>(
+                               ldapTemplate.listBindings(groupBase, new ContextMapper() {
+                                       public Object mapFromContext(Object ctxArg) {
+                                               String groupName = ((DirContextAdapter) ctxArg)
+                                                               .getStringAttribute(groupRoleAttribute);
+                                               String roleName = convertGroupToRole(groupName);
+                                               return roleName;
+                                       }
+                               })));
+       }
+
+       @SuppressWarnings("unchecked")
+       public Set<String> listUsersInRole(String role) {
+               return (Set<String>) ldapTemplate.lookup(
+                               buildGroupDn(convertRoleToGroup(role)), new ContextMapper() {
+                                       public Object mapFromContext(Object ctxArg) {
+                                               DirContextAdapter ctx = (DirContextAdapter) ctxArg;
+                                               String[] userDns = ctx
+                                                               .getStringAttributes(groupMemberAttribute);
+                                               TreeSet<String> set = new TreeSet<String>();
+                                               for (String userDn : userDns) {
+                                                       DistinguishedName dn = new DistinguishedName(userDn);
+                                                       String username = dn.getValue(usernameAttribute);
+                                                       set.add(username);
+                                               }
+                                               return Collections.unmodifiableSortedSet(set);
+                                       }
+                               });
+       }
+
+       public void createRole(String role, final String superuserName) {
+               String group = convertRoleToGroup(role);
+               DistinguishedName superuserDn = (DistinguishedName) ldapTemplate
+                               .executeReadWrite(new ContextExecutor() {
+                                       public Object executeWithContext(DirContext ctx)
+                                                       throws NamingException {
+                                               return LdapUtils.getFullDn(
+                                                               usernameMapper.buildDn(superuserName), ctx);
+                                       }
+                               });
+
+               Name groupDn = buildGroupDn(group);
+               DirContextAdapter context = new DirContextAdapter();
+               context.setAttributeValues("objectClass", groupClasses);
+               context.setAttributeValue("cn", group);
+               // Add superuser because cannot create empty group
+               context.setAttributeValue(groupMemberAttribute, superuserDn.toString());
+               ldapTemplate.bind(groupDn, context, null);
+       }
+
+       public void deleteRole(String role) {
+               String group = convertRoleToGroup(role);
+               Name dn = buildGroupDn(group);
+               ldapTemplate.unbind(dn);
+       }
+
+       /** Maps a role (ROLE_XXX) to the related LDAP group (xxx) */
+       protected String convertRoleToGroup(String role) {
+               String group = role;
+               if (group.startsWith(rolePrefix)) {
+                       group = group.substring(rolePrefix.length());
+                       group = group.toLowerCase();
+               }
+               return group;
+       }
+
+       /** Maps anLDAP group (xxx) to the related role (ROLE_XXX) */
+       protected String convertGroupToRole(String groupName) {
+               groupName = groupName.toUpperCase();
+
+               return rolePrefix + groupName;
+       }
+
+       protected Name buildGroupDn(String name) {
+               return new DistinguishedName(groupRoleAttribute + "=" + name + ","
+                               + groupBase);
+       }
+
+       public void setUserBase(String userBase) {
+               this.userBase = userBase;
+       }
+
+       public void setUsernameAttribute(String usernameAttribute) {
+               this.usernameAttribute = usernameAttribute;
+       }
+
+       public void setGroupBase(String groupBase) {
+               this.groupBase = groupBase;
+       }
+
+       public void setGroupRoleAttribute(String groupRoleAttributeName) {
+               this.groupRoleAttribute = groupRoleAttributeName;
+       }
+
+       public void setGroupMemberAttribute(String groupMemberAttributeName) {
+               this.groupMemberAttribute = groupMemberAttributeName;
+       }
+
+       public void setDefaultRole(String defaultRole) {
+               this.defaultRole = defaultRole;
+       }
+
+       public void setRolePrefix(String rolePrefix) {
+               this.rolePrefix = rolePrefix;
+       }
+
+       public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
+               this.usernameMapper = usernameMapper;
+       }
+
+       public String getDefaultRole() {
+               return defaultRole;
+       }
+
+       public void setGroupClasses(String[] groupClasses) {
+               this.groupClasses = groupClasses;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/JcrLdapSynchronizer.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/JcrLdapSynchronizer.java
new file mode 100644 (file)
index 0000000..de28c7f
--- /dev/null
@@ -0,0 +1,623 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.ldap;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+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.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.version.VersionManager;
+import javax.naming.Name;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.cms.internal.useradmin.SimpleJcrSecurityModel;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.security.SecurityUtils;
+import org.argeo.security.jcr.JcrSecurityModel;
+import org.argeo.security.jcr.JcrUserDetails;
+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.authentication.encoding.PasswordEncoder;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.ldap.LdapUsernameToDnMapper;
+import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
+
+/** Makes sure that LDAP and JCR are in line. */
+@SuppressWarnings("deprecation")
+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
+        * <a href=
+        * "http://forum.springsource.org/showthread.php?55955-Persistent-search-with-spring-ldap"
+        * >this</a>
+        */
+       // private LdapTemplate rawLdapTemplate;
+
+       private String userBase;
+       private String usernameAttribute;
+       private String passwordAttribute;
+       private String[] userClasses;
+       // private String defaultUserRole ="ROLE_USER";
+
+       // private NamingListener ldapUserListener;
+       // private SearchControls subTreeSearchControls;
+       private LdapUsernameToDnMapper usernameMapper;
+
+       private PasswordEncoder passwordEncoder;
+       private final Random random;
+
+       // JCR
+       /** Admin session on the main workspace */
+       private Session nodeSession;
+       private Repository repository;
+
+       // private JcrProfileListener jcrProfileListener;
+       private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
+
+       // Mapping
+       private Map<String, String> propertyToAttributes = new HashMap<String, String>();
+
+       public JcrLdapSynchronizer() {
+               random = createRandom();
+       }
+
+       public void init() {
+               try {
+                       nodeSession = repository.login();
+
+                       // TODO put this in a different thread, and poll the LDAP server
+                       // until it is up
+                       try {
+                               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;
+                               // }
+                               // });
+                       } catch (Exception e) {
+                               log.error("Could not synchronize and listen to LDAP,"
+                                               + " probably because the LDAP server is not available."
+                                               + " Restart the system as soon as possible.", e);
+                       }
+
+                       // 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
+                       // nodeSession
+                       // .getWorkspace()
+                       // .getObservationManager()
+                       // .addEventListener(jcrProfileListener,
+                       // Event.PROPERTY_CHANGED | Event.NODE_ADDED, "/",
+                       // true, null, nodeTypes, true);
+               } catch (Exception e) {
+                       JcrUtils.logoutQuietly(nodeSession);
+                       throw new ArgeoException("Cannot initialize LDAP/JCR synchronizer",
+                                       e);
+               }
+       }
+
+       public void destroy() {
+               // JcrUtils.removeListenerQuietly(nodeSession, jcrProfileListener);
+               JcrUtils.logoutQuietly(nodeSession);
+               // 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<String> userPaths = (List<String>) ldapTemplate.listBindings(
+                                       userBaseName, new ContextMapper() {
+                                               public Object mapFromContext(Object ctxObj) {
+                                                       try {
+                                                               return mapLdapToJcr((DirContextAdapter) ctxObj);
+                                                       } catch (Exception e) {
+                                                               // do not break process because of error
+                                                               log.error(
+                                                                               "Could not LDAP->JCR synchronize user "
+                                                                                               + ctxObj, e);
+                                                               return null;
+                                                       }
+                                               }
+                                       });
+
+                       // create accounts which are not in LDAP
+                       Query query = nodeSession
+                                       .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();
+                               try {
+                                       if (!userPaths.contains(path)) {
+                                               String username = userProfile
+                                                               .getProperty(ARGEO_USER_ID).getString();
+                                               // GrantedAuthority[] authorities = {new
+                                               // GrantedAuthorityImpl(defaultUserRole)};
+                                               List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
+                                               JcrUserDetails userDetails = new JcrUserDetails(
+                                                               userProfile, username, authorities);
+                                               String dn = createLdapUser(userDetails);
+                                               log.warn("Created ldap entry '" + dn + "' for user '"
+                                                               + username + "'");
+
+                                               // if(!userProfile.getProperty(ARGEO_ENABLED).getBoolean()){
+                                               // continue profiles;
+                                               // }
+                                               //
+                                               // log.warn("Path "
+                                               // + path
+                                               // + " not found in LDAP, disabling user "
+                                               // + userProfile.getProperty(ArgeoNames.ARGEO_USER_ID)
+                                               // .getString());
+
+                                               // Temporary hack to repair previous behaviour
+                                               if (!userProfile.getProperty(ARGEO_ENABLED)
+                                                               .getBoolean()) {
+                                                       VersionManager versionManager = nodeSession
+                                                                       .getWorkspace().getVersionManager();
+                                                       versionManager.checkout(userProfile.getPath());
+                                                       userProfile.setProperty(ArgeoNames.ARGEO_ENABLED,
+                                                                       true);
+                                                       nodeSession.save();
+                                                       versionManager.checkin(userProfile.getPath());
+                                               }
+                                       }
+                               } catch (Exception e) {
+                                       log.error("Cannot process " + path, e);
+                               }
+                       }
+               } catch (Exception e) {
+                       JcrUtils.discardQuietly(nodeSession);
+                       log.error("Cannot synchronize LDAP and JCR", e);
+                       // throw new ArgeoException("Cannot synchronize LDAP and JCR", e);
+               }
+       }
+
+       private String createLdapUser(UserDetails user) {
+               DirContextAdapter ctx = new DirContextAdapter();
+               mapUserToContext(user, ctx);
+               DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
+               ldapTemplate.bind(dn, ctx, null);
+               return dn.toString();
+       }
+
+       /** Called during authentication in order to retrieve user details */
+       public UserDetails mapUserFromContext(final DirContextOperations ctx,
+                       final String username,
+                       Collection<? extends GrantedAuthority> authorities) {
+               if (ctx == null)
+                       throw new ArgeoException("No LDAP information for user " + username);
+
+               String ldapUsername = ctx.getStringAttribute(usernameAttribute);
+               if (!ldapUsername.equals(username))
+                       throw new ArgeoException("Logged in with username " + username
+                                       + " but LDAP user is " + ldapUsername);
+
+               Node userProfile = jcrSecurityModel.sync(nodeSession, username,
+                               SecurityUtils.authoritiesToStringList(authorities));
+               // JcrUserDetails.checkAccountStatus(userProfile);
+
+               // password
+               SortedSet<?> passwordAttributes = ctx
+                               .getAttributeSortedStringSet(passwordAttribute);
+               String password;
+               if (passwordAttributes == null || passwordAttributes.size() == 0) {
+                       // throw new ArgeoException("No password found for user " +
+                       // username);
+                       password = "NULL";
+               } else {
+                       byte[] arr = (byte[]) passwordAttributes.first();
+                       password = new String(arr);
+                       // erase password
+                       Arrays.fill(arr, (byte) 0);
+               }
+
+               try {
+                       return new JcrUserDetails(userProfile, 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 synchronized String mapLdapToJcr(DirContextAdapter ctx) {
+               Session session = nodeSession;
+               try {
+                       // process
+                       String username = ctx.getStringAttribute(usernameAttribute);
+
+                       Node userProfile = jcrSecurityModel.sync(session, username, null);
+                       Map<String, String> modifications = new HashMap<String, String>();
+                       for (String jcrProperty : propertyToAttributes.keySet())
+                               ldapToJcr(userProfile, jcrProperty, ctx, modifications);
+
+                       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<String, String> 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);
+                       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 = nodeSession
+                                       .getNode(jcrUserDetails.getHomePath()).getNode(
+                                                       ARGEO_PROFILE);
+                       for (String jcrProperty : propertyToAttributes.keySet()) {
+                               if (userProfile.hasProperty(jcrProperty)) {
+                                       ModificationItem mi = jcrToLdap(jcrProperty, userProfile
+                                                       .getProperty(jcrProperty).getString());
+                                       if (mi != null)
+                                               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 setUserBase(String userBase) {
+               this.userBase = userBase;
+       }
+
+       public void setUsernameAttribute(String usernameAttribute) {
+               this.usernameAttribute = usernameAttribute;
+       }
+
+       public void setPropertyToAttributes(Map<String, String> 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;
+       }
+
+       public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
+               this.jcrSecurityModel = jcrSecurityModel;
+       }
+
+       /** 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<Name, List<ModificationItem>> modifications = new HashMap<Name,
+       // List<ModificationItem>>();
+       // while (events.hasNext()) {
+       // Event event = events.nextEvent();
+       // try {
+       // if (Event.PROPERTY_CHANGED == event.getType()) {
+       // Property property = (Property) nodeSession
+       // .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<ModificationItem>());
+       // 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 = nodeSession.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<ModificationItem>());
+       // 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<ModificationItem> 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/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/JcrUserDetailsContextMapper.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/JcrUserDetailsContextMapper.java
new file mode 100644 (file)
index 0000000..acfcebc
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.ldap;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
+
+/** @deprecated Read only mapping from LDAP to user details */
+@Deprecated
+public class JcrUserDetailsContextMapper implements UserDetailsContextMapper,
+               ArgeoNames {
+       /** Admin session on the security workspace */
+       private Session securitySession;
+       private Repository repository;
+       private String securityWorkspace = "security";
+
+       public void init() {
+               try {
+                       securitySession = repository.login(securityWorkspace);
+               } catch (RepositoryException e) {
+                       JcrUtils.logoutQuietly(securitySession);
+                       throw new ArgeoException(
+                                       "Cannot initialize LDAP/JCR user details context mapper", e);
+               }
+       }
+
+       public void destroy() {
+               JcrUtils.logoutQuietly(securitySession);
+       }
+
+       /** Called during authentication in order to retrieve user details */
+       public UserDetails mapUserFromContext(final DirContextOperations ctx,
+                       final String username,
+                       Collection<? extends GrantedAuthority> authorities) {
+               if (ctx == null)
+                       throw new ArgeoException("No LDAP information for user " + username);
+               Node userHome = UserJcrUtils.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 {
+                       // we don't have access to password, so let's not pretend
+                       String password = UUID.randomUUID().toString();
+                       return new JcrUserDetails(userHome.getNode(ARGEO_PROFILE),
+                                       password, authorities);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot retrieve user details for "
+                                       + username, e);
+               }
+       }
+
+       public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
+               throw new UnsupportedOperationException("LDAP access is read-only");
+       }
+
+}
index fa20c3d9e1534e11053fcd2efc14ffe99ec31e27..9124816cb6dd5b20407b0449c8f3c483b89919b5 100644 (file)
@@ -2,7 +2,4 @@ Bundle-ActivationPolicy: lazy
 Import-Package:org.bouncycastle.*;resolution:=optional,\
 org.springframework.util,\
 javax.jcr.security,\
-org.apache.jackrabbit.*;resolution:=optional,\
-org.springframework.ldap.*;resolution:=optional,\
-org.springframework.security.ldap.*;resolution:=optional,\
 *
index 8c6715446a96ec78cff35bd0176f3584ffadab69..2d453d3da7c6b02db1355f3cc6021fd3ae21ea24 100644 (file)
@@ -19,7 +19,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.UUID;
 
 import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.core.Authentication;
@@ -29,19 +28,9 @@ import org.springframework.security.core.context.SecurityContextHolder;
 
 /** Static utilities */
 public final class SecurityUtils {
-       private final static String systemKey = UUID.randomUUID().toString();
-
        private SecurityUtils() {
        }
 
-       /**
-        * @return a String which is guaranteed to be unique between and constant
-        *         within a Java static context (typically a VM launch)
-        */
-       public final static String getStaticKey() {
-               return systemKey;
-       }
-
        /** Whether the current thread has the admin role */
        public static boolean hasCurrentThreadAuthority(String authority) {
                SecurityContext securityContext = SecurityContextHolder.getContext();
index 0d075c3a60b5b0039a17fb95de648b12d2dbe952..bdd110da97ac2b44a6f8570cf61de9f5af601b44 100644 (file)
  */
 package org.argeo.security.core;
 
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.ArgeoException;
 import org.argeo.security.SystemAuthentication;
-import org.argeo.security.login.BundleContextCallbackHandler;
-import org.osgi.framework.BundleContext;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -45,9 +39,7 @@ public abstract class AbstractSystemExecution {
        private final static Log log = LogFactory
                        .getLog(AbstractSystemExecution.class);
        private AuthenticationManager authenticationManager;
-       private BundleContext bundleContext;
        private String systemAuthenticationKey;
-       private String loginContextName = "SYSTEM";
 
        /** Whether the current thread was authenticated by this component. */
        private ThreadLocal<Boolean> authenticatedBySelf = new ThreadLocal<Boolean>() {
@@ -85,24 +77,12 @@ public abstract class AbstractSystemExecution {
                                                InternalAuthentication.SYSTEM_KEY_DEFAULT);
                if (key == null)
                        throw new ArgeoException("No system key defined");
-               if (authenticationManager != null) {
-                       Authentication auth = authenticationManager
-                                       .authenticate(new InternalAuthentication(key));
-                       securityContext.setAuthentication(auth);
-               } else {
-                       try {
-                               // TODO test this
-                               if (bundleContext == null)
-                                       throw new ArgeoException("bundleContext must be set");
-                               BundleContextCallbackHandler callbackHandler = new BundleContextCallbackHandler(
-                                               bundleContext);
-                               LoginContext loginContext = new LoginContext(loginContextName,
-                                               callbackHandler);
-                               loginContext.login();
-                       } catch (LoginException e) {
-                               throw new BadCredentialsException("Cannot authenticate");
-                       }
-               }
+               if (authenticationManager == null)
+                       throw new ArgeoException("Authentication manager cannot be null.");
+               Authentication auth = authenticationManager
+                               .authenticate(new InternalAuthentication(key));
+               securityContext.setAuthentication(auth);
+
                authenticatedBySelf.set(true);
                if (log.isTraceEnabled())
                        log.trace("System authenticated");
@@ -128,20 +108,12 @@ public abstract class AbstractSystemExecution {
                return authenticatedBySelf.get();
        }
 
-       @Deprecated
        public void setAuthenticationManager(
                        AuthenticationManager authenticationManager) {
-               // log.warn("This approach is deprecated, inject bundleContext instead");
                this.authenticationManager = authenticationManager;
        }
 
-       @Deprecated
        public void setSystemAuthenticationKey(String systemAuthenticationKey) {
                this.systemAuthenticationKey = systemAuthenticationKey;
        }
-
-       public void setBundleContext(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-
 }
index e9ab89c2ae8eb4e70786975d0e6e20f4d3ce5f7d..a151c7f30a714939d14d2212d76ebf6aa0f7a060 100644 (file)
@@ -30,7 +30,7 @@ public interface JcrSecurityModel {
         * user has a home directory with full access and a profile with information
         * about him (read access)
         * 
-        * @return the user profile (whose parent is the user home), never null
+        * @return the user profile, never null
         */
        public Node sync(Session session, String username, List<String> roles);
 }
diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrAuthenticationProvider.java b/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrAuthenticationProvider.java
deleted file mode 100644 (file)
index 7125604..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr;
-
-import java.util.Collection;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.security.OsAuthenticationToken;
-import org.argeo.security.SecurityUtils;
-import org.argeo.security.core.OsAuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-
-/** Relies on OS to authenticate and additionally setup JCR */
-public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
-       private Repository repository;
-       private Session nodeSession;
-
-       private UserDetails userDetails;
-       private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
-
-       private final static String JVM_OSUSER = System.getProperty("user.name");
-
-       public void init() {
-               try {
-                       nodeSession = repository.login();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot initialize", e);
-               }
-       }
-
-       public void destroy() {
-               JcrUtils.logoutQuietly(nodeSession);
-       }
-
-       public Authentication authenticate(Authentication authentication)
-                       throws AuthenticationException {
-               if (authentication instanceof UsernamePasswordAuthenticationToken) {
-                       // deal with remote access to internal server
-                       // FIXME very primitive and unsecure at this sSession adminSession
-                       // =tage
-                       // consider using the keyring for username / password authentication
-                       // or certificate
-                       UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication;
-                       if (!upat.getPrincipal().toString().equals(JVM_OSUSER))
-                               throw new BadCredentialsException("Wrong credentials");
-                       UsernamePasswordAuthenticationToken authen = new UsernamePasswordAuthenticationToken(
-                                       authentication.getPrincipal(),
-                                       authentication.getCredentials(), getBaseAuthorities());
-                       authen.setDetails(userDetails);
-                       return authen;
-               } else if (authentication instanceof OsAuthenticationToken) {
-                       OsAuthenticationToken authen = (OsAuthenticationToken) super
-                                       .authenticate(authentication);
-                       try {
-                               // WARNING: at this stage we assume that the java properties
-                               // will have the same value
-                               Collection<? extends GrantedAuthority> authorities = getBaseAuthorities();
-                               String username = JVM_OSUSER;
-                               Node userProfile = jcrSecurityModel.sync(nodeSession, username,
-                                               SecurityUtils.authoritiesToStringList(authorities));
-                               JcrUserDetails.checkAccountStatus(userProfile);
-
-                               userDetails = new JcrUserDetails(userProfile, authen
-                                               .getCredentials().toString(), authorities);
-                               authen.setDetails(userDetails);
-                               return authen;
-                       } catch (RepositoryException e) {
-                               JcrUtils.discardQuietly(nodeSession);
-                               throw new ArgeoException(
-                                               "Unexpected exception when synchronizing OS and JCR security ",
-                                               e);
-                       }
-               } else {
-                       throw new ArgeoException("Unsupported authentication "
-                                       + authentication.getClass());
-               }
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
-               this.jcrSecurityModel = jcrSecurityModel;
-       }
-
-       @SuppressWarnings("rawtypes")
-       public boolean supports(Class authentication) {
-               return OsAuthenticationToken.class.isAssignableFrom(authentication)
-                               || UsernamePasswordAuthenticationToken.class
-                                               .isAssignableFrom(authentication);
-       }
-}
\ No newline at end of file
diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrUserAdminService.java b/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrUserAdminService.java
deleted file mode 100644 (file)
index 1eab370..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-import org.argeo.security.UserAdminService;
-import org.springframework.dao.DataAccessException;
-import org.springframework.security.core.userdetails.User;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-/**
- * Dummy user service to be used when running as a single OS user (typically
- * desktop). TODO integrate with JCR user / groups
- */
-public class OsJcrUserAdminService implements UserAdminService {
-       private Repository repository;
-
-       /** In memory roles provided by applications. */
-       private List<String> roles = new ArrayList<String>();
-
-       // private Session adminSession;
-
-       public void init() {
-               // try {
-               // adminSession = repository.login();
-               // } catch (RepositoryException e) {
-               // throw new ArgeoException("Cannot initialize", e);
-               // }
-       }
-
-       public void destroy() {
-               // JcrUtils.logoutQuietly(adminSession);
-       }
-
-       /** <b>Unsupported</b> */
-       public void createUser(UserDetails user) {
-               throw new UnsupportedOperationException();
-       }
-
-       /** Does nothing */
-       public void updateUser(UserDetails user) {
-
-       }
-
-       /** <b>Unsupported</b> */
-       public void deleteUser(String username) {
-               throw new UnsupportedOperationException();
-       }
-
-       /** <b>Unsupported</b> */
-       public void changePassword(String oldPassword, String newPassword) {
-               throw new UnsupportedOperationException();
-       }
-
-       public boolean userExists(String username) {
-               if (getSPropertyUsername().equals(username))
-                       return true;
-               else
-                       return false;
-       }
-
-       public UserDetails loadUserByUsername(String username)
-                       throws UsernameNotFoundException, DataAccessException {
-               if (getSPropertyUsername().equals(username)) {
-                       UserDetails userDetails;
-                       if (repository != null) {
-                               Session adminSession = null;
-                               try {
-                                       adminSession = repository.login();
-                                       Node userProfile = UserJcrUtils.getUserProfile(
-                                                       adminSession, username);
-                                       userDetails = new JcrUserDetails(userProfile, "",
-                                                       OsJcrAuthenticationProvider.getBaseAuthorities());
-                               } catch (RepositoryException e) {
-                                       throw new ArgeoException(
-                                                       "Cannot retrieve user profile for " + username, e);
-                               } finally {
-                                       JcrUtils.logoutQuietly(adminSession);
-                               }
-                       } else {
-                               userDetails = new User(username, "", true, true, true, true,
-                                               OsJcrAuthenticationProvider.getBaseAuthorities());
-                       }
-                       return userDetails;
-               } else {
-                       throw new UnsupportedOperationException();
-               }
-       }
-
-       protected final String getSPropertyUsername() {
-               return System.getProperty("user.name");
-       }
-
-       public Set<String> listUsers() {
-               Set<String> set = new HashSet<String>();
-               set.add(getSPropertyUsername());
-               return set;
-       }
-
-       public Set<String> listUsersInRole(String role) {
-               Set<String> set = new HashSet<String>();
-               set.add(getSPropertyUsername());
-               return set;
-       }
-
-       /** Does nothing */
-       public void synchronize() {
-       }
-
-       /** <b>Unsupported</b> */
-       public void newRole(String role) {
-               roles.add(role);
-       }
-
-       public Set<String> listEditableRoles() {
-               return new HashSet<String>(roles);
-       }
-
-       /** <b>Unsupported</b> */
-       public void deleteRole(String role) {
-               roles.remove(role);
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java b/org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java
deleted file mode 100644 (file)
index 1541075..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr;
-
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-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.UserJcrUtils;
-import org.argeo.security.NodeAuthenticationToken;
-import org.osgi.framework.BundleContext;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
-/** Connects to a JCR repository and delegates authentication to it. */
-public class RemoteJcrAuthenticationProvider implements AuthenticationProvider,
-               ArgeoNames {
-       private RepositoryFactory repositoryFactory;
-       private BundleContext bundleContext;
-
-       public final static String ROLE_REMOTE = "ROLE_REMOTE";
-
-       public Authentication authenticate(Authentication authentication)
-                       throws AuthenticationException {
-               NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication;
-               String url = siteAuth.getUrl();
-               if (url == null)// TODO? login on own node
-                       throw new ArgeoException("No url set in " + siteAuth);
-               Session session;
-
-               Node userProfile;
-               try {
-                       SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
-                                       siteAuth.getCredentials().toString().toCharArray());
-                       // get repository
-                       Repository repository = new RemoteJcrRepositoryWrapper(
-                                       repositoryFactory, url, sp);
-                       if (bundleContext != null) {
-                               Dictionary<String, String> serviceProperties = new Hashtable<String, String>();
-                               serviceProperties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS,
-                                               ArgeoJcrConstants.ALIAS_NODE);
-                               serviceProperties
-                                               .put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url);
-                               bundleContext.registerService(Repository.class.getName(),
-                                               repository, serviceProperties);
-                       }
-                       // Repository repository = ArgeoJcrUtils.getRepositoryByUri(
-                       // repositoryFactory, url);
-                       // if (repository == null)
-                       // throw new ArgeoException("Cannot connect to " + url);
-
-                       session = repository.login(sp, null);
-
-                       userProfile = UserJcrUtils.getUserProfile(session, sp.getUserID());
-                       JcrUserDetails.checkAccountStatus(userProfile);
-
-                       // Node userHome = UserJcrUtils.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 {
-                       // Node userHome = UserJcrUtils.getUserHome(session);
-                       // retrieve remote roles
-                       List<GrantedAuthority> authoritiesList = new ArrayList<GrantedAuthority>();
-                       if (userProfile != null
-                                       && 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 SimpleGrantedAuthority(roles[i]
-                                                       .getString()));
-                       }
-                       authoritiesList.add(new SimpleGrantedAuthority(ROLE_REMOTE));
-
-                       // create authenticated objects
-                       // GrantedAuthority[] authorities = authoritiesList
-                       // .toArray(new GrantedAuthority[authoritiesList.size()]);
-                       JcrUserDetails userDetails = new JcrUserDetails(userProfile,
-                                       siteAuth.getCredentials().toString(), authoritiesList);
-                       NodeAuthenticationToken authenticated = new NodeAuthenticationToken(
-                                       siteAuth, authoritiesList);
-                       authenticated.setDetails(userDetails);
-                       return authenticated;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException(
-                                       "Unexpected exception when authenticating to " + url, e);
-               }
-       }
-
-       @SuppressWarnings("rawtypes")
-       public boolean supports(Class authentication) {
-               return NodeAuthenticationToken.class.isAssignableFrom(authentication);
-       }
-
-       public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
-               this.repositoryFactory = repositoryFactory;
-       }
-
-       public void setBundleContext(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-
-}
index 7ef15947f09aa13f02a1d1eab089c92aca6673f9..e0e887a3efb5d5ebc79e2340b566d331be27ad0b 100644 (file)
@@ -27,6 +27,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
  * Thread bounded JCR session factory which checks authentication and is
  * autoconfigured in Spring.
  */
+@Deprecated
 public class SecureThreadBoundSession extends ThreadBoundSession {
        private final static Log log = LogFactory
                        .getLog(SecureThreadBoundSession.class);
diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/SimpleJcrSecurityModel.java b/org.argeo.security.core/src/org/argeo/security/jcr/SimpleJcrSecurityModel.java
deleted file mode 100644 (file)
index fc01587..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr;
-
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.security.Privilege;
-import javax.jcr.version.VersionManager;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoJcrConstants;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-
-/**
- * Manages data expected by the Argeo security model, such as user home and
- * profile.
- */
-public class SimpleJcrSecurityModel implements JcrSecurityModel {
-       private final static Log log = LogFactory
-                       .getLog(SimpleJcrSecurityModel.class);
-       // ArgeoNames not implemented as interface in order to ease derivation by
-       // Jackrabbit bundles
-
-       /** The home base path. */
-       private String homeBasePath = "/home";
-
-       public synchronized Node sync(Session session, String username,
-                       List<String> roles) {
-               // TODO check user name validity (e.g. should not start by ROLE_)
-
-               try {
-                       Node userHome = UserJcrUtils.getUserHome(session, username);
-                       if (userHome == null) {
-                               String homePath = generateUserPath(homeBasePath, username);
-                               userHome = JcrUtils.mkdirs(session, homePath);
-                               // userHome = JcrUtils.mkfolders(session, homePath);
-                               userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
-                               userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
-                               session.save();
-
-                               JcrUtils.clearAccessControList(session, homePath, username);
-                               JcrUtils.addPrivilege(session, homePath, username,
-                                               Privilege.JCR_ALL);
-                       } else {
-                               // for backward compatibility with pre 1.0 security model
-                               if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) {
-                                       userHome.getNode(ArgeoNames.ARGEO_PROFILE).remove();
-                                       userHome.getSession().save();
-                               }
-                       }
-
-                       // Remote roles
-                       if (roles != null) {
-                               // writeRemoteRoles(userHome, roles);
-                       }
-
-                       Node userProfile = UserJcrUtils.getUserProfile(session, username);
-                       if (userProfile == null) {
-                               String personPath = generateUserPath(
-                                               ArgeoJcrConstants.PEOPLE_BASE_PATH, username);
-                               Node personBase = JcrUtils.mkdirs(session, personPath);
-                               userProfile = personBase.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);
-                               session.save();
-
-                               JcrUtils.clearAccessControList(session, userProfile.getPath(),
-                                               username);
-                               JcrUtils.addPrivilege(session, userProfile.getPath(), username,
-                                               Privilege.JCR_READ);
-
-                               VersionManager versionManager = session.getWorkspace()
-                                               .getVersionManager();
-                               if (versionManager.isCheckedOut(userProfile.getPath()))
-                                       versionManager.checkin(userProfile.getPath());
-
-                       }
-
-                       // Remote roles
-                       if (roles != null) {
-                               writeRemoteRoles(userProfile, roles);
-                       }
-                       return userProfile;
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new ArgeoException("Cannot sync node security model for "
-                                       + username, e);
-               }
-       }
-
-       /** Generate path for a new user home */
-       protected String generateUserPath(String base, String username) {
-               int atIndex = username.indexOf('@');
-               if (atIndex > 0) {
-                       String domain = username.substring(0, atIndex);
-                       String name = username.substring(atIndex + 1);
-                       return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/'
-                                       + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/'
-                                       + name;
-               } else if (atIndex == 0 || atIndex == (username.length() - 1)) {
-                       throw new ArgeoException("Unsupported username " + username);
-               } else {
-                       return base + '/' + JcrUtils.firstCharsToPath(username, 2) + '/'
-                                       + username;
-               }
-       }
-
-       /** Write remote roles used by remote access in the home directory */
-       protected void writeRemoteRoles(Node userHome, List<String> roles)
-                       throws RepositoryException {
-               boolean writeRoles = false;
-               if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
-                       Value[] remoteRoles = userHome.getProperty(
-                                       ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
-                       if (remoteRoles.length != roles.size())
-                               writeRoles = true;
-                       else
-                               for (int i = 0; i < remoteRoles.length; i++)
-                                       if (!remoteRoles[i].getString().equals(roles.get(i)))
-                                               writeRoles = true;
-               } else
-                       writeRoles = true;
-
-               if (writeRoles) {
-                       userHome.getSession().getWorkspace().getVersionManager()
-                                       .checkout(userHome.getPath());
-                       String[] roleIds = roles.toArray(new String[roles.size()]);
-                       userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds);
-                       JcrUtils.updateLastModified(userHome);
-                       userHome.getSession().save();
-                       userHome.getSession().getWorkspace().getVersionManager()
-                                       .checkin(userHome.getPath());
-                       if (log.isDebugEnabled())
-                               log.debug("Wrote remote roles " + roles + " for "
-                                               + userHome.getProperty(ArgeoNames.ARGEO_USER_ID));
-               }
-
-       }
-
-       public void setHomeBasePath(String homeBasePath) {
-               this.homeBasePath = homeBasePath;
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/JackrabbitSecurityModel.java b/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/JackrabbitSecurityModel.java
deleted file mode 100644 (file)
index e52e6c6..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr.jackrabbit;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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.jcr.ArgeoNames;
-import org.argeo.security.jcr.SimpleJcrSecurityModel;
-
-/** Make sure that user authorizable exists before syncing user directories. */
-public class JackrabbitSecurityModel extends SimpleJcrSecurityModel {
-       private final static Log log = LogFactory
-                       .getLog(JackrabbitSecurityModel.class);
-
-       @Override
-       public synchronized Node sync(Session session, String username,
-                       List<String> roles) {
-               if (!(session instanceof JackrabbitSession))
-                       return super.sync(session, username, roles);
-
-               try {
-                       UserManager userManager = ((JackrabbitSession) session)
-                                       .getUserManager();
-                       User user = (User) userManager.getAuthorizable(username);
-                       if (user != null) {
-                               String principalName = user.getPrincipal().getName();
-                               if (!principalName.equals(username)) {
-                                       log.warn("Jackrabbit principal is '" + principalName
-                                                       + "' but username is '" + username
-                                                       + "'. Recreating...");
-                                       user.remove();
-                                       user = userManager.createUser(username, "");
-                               }
-                       } else {
-                               // create new principal
-                               user = userManager.createUser(username, "");
-                               log.info(username + " added as Jackrabbit user " + user);
-                       }
-
-                       // generic JCR sync
-                       Node userProfile = super.sync(session, username, roles);
-
-                       Boolean enabled = userProfile.getProperty(ArgeoNames.ARGEO_ENABLED)
-                                       .getBoolean();
-                       if (enabled && user.isDisabled())
-                               user.disable(null);
-                       else if (!enabled && !user.isDisabled())
-                               user.disable(userProfile.getPath() + " is disabled");
-
-                       // Sync Jackrabbit roles
-                       if (roles != null)
-                               syncRoles(userManager, user, roles);
-
-                       return userProfile;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException(
-                                       "Cannot perform Jackrabbit specific operations", e);
-               }
-       }
-
-       /** Make sure Jackrabbit roles are in line with authentication */
-       void syncRoles(UserManager userManager, User user, List<String> roles)
-                       throws RepositoryException {
-               List<String> userGroupIds = new ArrayList<String>();
-               for (String role : roles) {
-                       Group group = (Group) userManager.getAuthorizable(role);
-                       if (group == null) {
-                               group = userManager.createGroup(role);
-                               log.info(role + " added as " + group);
-                       }
-                       if (!group.isMember(user))
-                               group.addMember(user);
-                       userGroupIds.add(role);
-               }
-
-               // check if user has not been removed from some groups
-               for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
-                       Group group = it.next();
-                       if (!userGroupIds.contains(group.getID()))
-                               group.removeMember(user);
-               }
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/JackrabbitUserAdminService.java b/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/JackrabbitUserAdminService.java
deleted file mode 100644 (file)
index aceb516..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-package org.argeo.security.jcr.jackrabbit;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.user.Authorizable;
-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.apache.jackrabbit.core.security.authentication.CryptedSimpleCredentials;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-import org.argeo.security.NodeAuthenticationToken;
-import org.argeo.security.UserAdminService;
-import org.argeo.security.jcr.JcrSecurityModel;
-import org.argeo.security.jcr.JcrUserDetails;
-import org.argeo.security.login.GrantedAuthorityPrincipal;
-import org.springframework.dao.DataAccessException;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-/**
- * An implementation of {@link UserAdminService} which closely wraps Jackrabbits
- * implementation. Roles are implemented with Groups.
- */
-public class JackrabbitUserAdminService implements UserAdminService,
-               AuthenticationProvider {
-       final static String userRole = "ROLE_USER";
-       final static String adminRole = "ROLE_ADMIN";
-
-       private Repository repository;
-       private JcrSecurityModel securityModel;
-
-       private JackrabbitSession adminSession = null;
-
-       private String superUsername = "root";
-       private String superUserInitialPassword = "demo";
-
-       public void init() throws RepositoryException {
-               Authentication authentication = SecurityContextHolder.getContext()
-                               .getAuthentication();
-               authentication.getName();
-               adminSession = (JackrabbitSession) repository.login();
-               Authorizable adminGroup = getUserManager().getAuthorizable(adminRole);
-               if (adminGroup == null) {
-                       adminGroup = getUserManager().createGroup(adminRole);
-                       adminSession.save();
-               }
-               Authorizable superUser = getUserManager()
-                               .getAuthorizable(superUsername);
-               if (superUser == null) {
-                       superUser = getUserManager().createUser(superUsername,
-                                       superUserInitialPassword);
-                       ((Group) adminGroup).addMember(superUser);
-                       securityModel.sync(adminSession, superUsername, null);
-                       adminSession.save();
-               }
-       }
-
-       public void destroy() throws RepositoryException {
-               JcrUtils.logoutQuietly(adminSession);
-       }
-
-       private UserManager getUserManager() throws RepositoryException {
-               return adminSession.getUserManager();
-       }
-
-       @Override
-       public void createUser(UserDetails user) {
-               try {
-                       // FIXME workaround for issue in new user wizard where
-                       // security model is hardcoded and it already exists
-                       if (getUserManager().getAuthorizable(user.getUsername()) == null) {
-                               getUserManager().createUser(user.getUsername(),
-                                               user.getPassword());
-                               securityModel.sync(adminSession, user.getUsername(), null);
-                       }
-                       updateUser(user);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot create user " + user, e);
-               }
-       }
-
-       @Override
-       public void updateUser(UserDetails userDetails) {
-               try {
-                       User user = (User) getUserManager().getAuthorizable(
-                                       userDetails.getUsername());
-                       if (user == null)
-                               throw new ArgeoException("No user " + userDetails.getUsername());
-
-                       // new password
-                       String newPassword = userDetails.getPassword();
-                       if (!newPassword.trim().equals("")) {
-                               SimpleCredentials sp = new SimpleCredentials(
-                                               userDetails.getUsername(), newPassword.toCharArray());
-                               CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
-                                               .getCredentials();
-                               if (!credentials.matches(sp))
-                                       user.changePassword(new String(newPassword));
-                       }
-
-                       List<String> roles = new ArrayList<String>();
-                       for (GrantedAuthority ga : userDetails.getAuthorities()) {
-                               if (ga.getAuthority().equals(userRole))
-                                       continue;
-                               roles.add(ga.getAuthority());
-                       }
-
-                       for (Iterator<Group> it = user.memberOf(); it.hasNext();) {
-                               Group group = it.next();
-                               if (roles.contains(group.getPrincipal().getName()))
-                                       roles.remove(group.getPrincipal().getName());
-                               else
-                                       group.removeMember(user);
-                       }
-
-                       // remaining (new ones)
-                       for (String role : roles) {
-                               Group group = (Group) getUserManager().getAuthorizable(role);
-                               if (group == null)
-                                       throw new ArgeoException("Group " + role
-                                                       + " does not exist,"
-                                                       + " whereas it was granted to user " + userDetails);
-                               group.addMember(user);
-                       }
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot update user details", e);
-               }
-
-       }
-
-       @Override
-       public void deleteUser(String username) {
-               try {
-                       getUserManager().getAuthorizable(username).remove();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot remove user " + username, e);
-               }
-       }
-
-       @Override
-       public void changePassword(String oldPassword, String newPassword) {
-               Authentication authentication = SecurityContextHolder.getContext()
-                               .getAuthentication();
-               String username = authentication.getName();
-               try {
-                       SimpleCredentials sp = new SimpleCredentials(username,
-                                       oldPassword.toCharArray());
-                       User user = (User) getUserManager().getAuthorizable(username);
-                       CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
-                                       .getCredentials();
-                       if (credentials.matches(sp))
-                               user.changePassword(newPassword);
-                       else
-                               throw new BadCredentialsException("Bad credentials provided");
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot change password for user "
-                                       + username, e);
-               }
-       }
-
-       @Override
-       public boolean userExists(String username) {
-               try {
-                       Authorizable authorizable = getUserManager().getAuthorizable(
-                                       username);
-                       if (authorizable != null && authorizable instanceof User)
-                               return true;
-                       return false;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot check whether user " + username
-                                       + " exists ", e);
-               }
-       }
-
-       @Override
-       public Set<String> listUsers() {
-               LinkedHashSet<String> res = new LinkedHashSet<String>();
-               try {
-                       Iterator<Authorizable> users = getUserManager().findAuthorizables(
-                                       "rep:principalName", null, UserManager.SEARCH_TYPE_USER);
-                       while (users.hasNext()) {
-                               res.add(users.next().getPrincipal().getName());
-                       }
-                       return res;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot list users", e);
-               }
-       }
-
-       @Override
-       public Set<String> listUsersInRole(String role) {
-               LinkedHashSet<String> res = new LinkedHashSet<String>();
-               try {
-                       Group group = (Group) getUserManager().getAuthorizable(role);
-                       Iterator<Authorizable> users = group.getMembers();
-                       // NB: not recursive
-                       while (users.hasNext()) {
-                               res.add(users.next().getPrincipal().getName());
-                       }
-                       return res;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot list users in role " + role, e);
-               }
-       }
-
-       @Override
-       public void synchronize() {
-       }
-
-       @Override
-       public void newRole(String role) {
-               try {
-                       getUserManager().createGroup(role);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot create role " + role, e);
-               }
-       }
-
-       @Override
-       public Set<String> listEditableRoles() {
-               LinkedHashSet<String> res = new LinkedHashSet<String>();
-               try {
-                       Iterator<Authorizable> groups = getUserManager().findAuthorizables(
-                                       "rep:principalName", null, UserManager.SEARCH_TYPE_GROUP);
-                       while (groups.hasNext()) {
-                               res.add(groups.next().getPrincipal().getName());
-                       }
-                       return res;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot list groups", e);
-               }
-       }
-
-       @Override
-       public void deleteRole(String role) {
-               try {
-                       getUserManager().getAuthorizable(role).remove();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot remove role " + role, e);
-               }
-       }
-
-       @Override
-       public UserDetails loadUserByUsername(String username)
-                       throws UsernameNotFoundException, DataAccessException {
-               try {
-                       User user = (User) getUserManager().getAuthorizable(username);
-                       if (user == null)
-                               throw new UsernameNotFoundException("User " + username
-                                               + " cannot be found");
-                       return loadJcrUserDetails(adminSession, username);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot load user " + username, e);
-               }
-       }
-
-       protected JcrUserDetails loadJcrUserDetails(Session session, String username)
-                       throws RepositoryException {
-               if (username == null)
-                       username = session.getUserID();
-               User user = (User) getUserManager().getAuthorizable(username);
-               ArrayList<GrantedAuthorityPrincipal> authorities = new ArrayList<GrantedAuthorityPrincipal>();
-               // FIXME make it more generic
-               authorities.add(new GrantedAuthorityPrincipal("ROLE_USER"));
-               Iterator<Group> groups = user.declaredMemberOf();
-               while (groups.hasNext()) {
-                       Group group = groups.next();
-                       // String role = "ROLE_"
-                       // + group.getPrincipal().getName().toUpperCase();
-                       String role = group.getPrincipal().getName();
-                       authorities.add(new GrantedAuthorityPrincipal(role));
-               }
-
-               Node userProfile = UserJcrUtils.getUserProfile(session, username);
-               JcrUserDetails userDetails = new JcrUserDetails(userProfile, "",
-                               authorities);
-               return userDetails;
-       }
-
-       // AUTHENTICATION PROVIDER
-       public synchronized Authentication authenticate(
-                       Authentication authentication) throws AuthenticationException {
-               NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication;
-               String username = siteAuth.getName();
-               if (!(siteAuth.getCredentials() instanceof char[]))
-                       throw new ArgeoException("Only char array passwords are supported");
-               char[] password = (char[]) siteAuth.getCredentials();
-               try {
-                       SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
-                                       password);
-                       User user = (User) getUserManager().getAuthorizable(username);
-                       if (user == null)
-                               throw new BadCredentialsException("Bad credentials");
-                       CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
-                                       .getCredentials();
-                       // String providedPassword = siteAuth.getCredentials().toString();
-                       if (!credentials.matches(sp))
-                               throw new BadCredentialsException("Bad credentials");
-
-                       // session = repository.login(sp, null);
-
-                       Node userProfile = UserJcrUtils.getUserProfile(adminSession,
-                                       username);
-                       JcrUserDetails.checkAccountStatus(userProfile);
-               } catch (BadCredentialsException e) {
-                       throw e;
-               } catch (Exception e) {
-                       throw new BadCredentialsException(
-                                       "Cannot authenticate " + siteAuth, e);
-               } finally {
-                       Arrays.fill(password, '*');
-               }
-
-               try {
-                       JcrUserDetails userDetails = loadJcrUserDetails(adminSession,
-                                       username);
-                       NodeAuthenticationToken authenticated = new NodeAuthenticationToken(
-                                       siteAuth, userDetails.getAuthorities());
-                       authenticated.setDetails(userDetails);
-                       return authenticated;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException(
-                                       "Unexpected exception when authenticating " + siteAuth, e);
-               }
-       }
-
-       @SuppressWarnings("rawtypes")
-       public boolean supports(Class authentication) {
-               return UsernamePasswordAuthenticationToken.class
-                               .isAssignableFrom(authentication);
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setSecurityModel(JcrSecurityModel securityModel) {
-               this.securityModel = securityModel;
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/ScopedSessionProvider.java b/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/ScopedSessionProvider.java
deleted file mode 100644 (file)
index 635f71e..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr.jackrabbit;
-
-import java.io.Serializable;
-
-import javax.jcr.LoginException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.server.SessionProvider;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoJcrConstants;
-import org.argeo.jcr.JcrUtils;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-
-/**
- * Session provider assuming a single workspace and a short life cycle,
- * typically a Spring bean of scope (web) 'session'.
- */
-public class ScopedSessionProvider implements SessionProvider, Serializable {
-       private static final long serialVersionUID = 6589775984177317058L;
-       private static final Log log = LogFactory
-                       .getLog(ScopedSessionProvider.class);
-       private transient HttpSession httpSession = null;
-       private transient Session jcrSession = null;
-
-       private transient String currentRepositoryName = null;
-       private transient String currentWorkspaceName = null;
-       private transient String currentJcrUser = null;
-
-       // private transient String anonymousUserId = "anonymous";
-
-       public Session getSession(HttpServletRequest request, Repository rep,
-                       String workspace) throws LoginException, ServletException,
-                       RepositoryException {
-
-               Authentication authentication = SecurityContextHolder.getContext()
-                               .getAuthentication();
-               if (authentication == null)
-                       throw new ArgeoException(
-                                       "Request not authenticated by Spring Security");
-               String springUser = authentication.getName();
-
-               // HTTP
-               String requestJcrRepository = (String) request
-                               .getAttribute(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
-
-               // HTTP session
-               if (httpSession != null
-                               && !httpSession.getId().equals(request.getSession().getId()))
-                       throw new ArgeoException(
-                                       "Only session scope is supported in this mode");
-               if (httpSession == null)
-                       httpSession = request.getSession();
-
-               // Initializes current values
-               if (currentRepositoryName == null)
-                       currentRepositoryName = requestJcrRepository;
-               if (currentWorkspaceName == null)
-                       currentWorkspaceName = workspace;
-               if (currentJcrUser == null)
-                       currentJcrUser = springUser;
-
-               // logout if there was a change in session coordinates
-               if (jcrSession != null)
-                       if (!currentRepositoryName.equals(requestJcrRepository)) {
-                               if (log.isDebugEnabled())
-                                       log.debug(getHttpSessionId() + " Changed from repository '"
-                                                       + currentRepositoryName + "' to '"
-                                                       + requestJcrRepository
-                                                       + "', logging out cached JCR session.");
-                               logout();
-                       } else if (!currentWorkspaceName.equals(workspace)) {
-                               if (log.isDebugEnabled())
-                                       log.debug(getHttpSessionId() + " Changed from workspace '"
-                                                       + currentWorkspaceName + "' to '" + workspace
-                                                       + "', logging out cached JCR session.");
-                               logout();
-                       } else if (!currentJcrUser.equals(springUser)) {
-                               if (log.isDebugEnabled())
-                                       log.debug(getHttpSessionId() + " Changed from user '"
-                                                       + currentJcrUser + "' to '" + springUser
-                                                       + "', logging out cached JCR session.");
-                               logout();
-                       }
-
-               // login if needed
-               if (jcrSession == null)
-                       try {
-                               Session session = login(rep, workspace);
-                               if (!session.getUserID().equals(springUser)) {
-                                       JcrUtils.logoutQuietly(session);
-                                       throw new ArgeoException("Spring Security user '"
-                                                       + springUser + "' not in line with JCR user '"
-                                                       + session.getUserID() + "'");
-                               }
-                               currentRepositoryName = requestJcrRepository;
-                               // do not use workspace variable which may be null
-                               currentWorkspaceName = session.getWorkspace().getName();
-                               currentJcrUser = session.getUserID();
-
-                               jcrSession = session;
-                               return jcrSession;
-                       } catch (RepositoryException e) {
-                               throw new ArgeoException("Cannot open session to workspace "
-                                               + workspace, e);
-                       }
-
-               // returns cached session
-               return jcrSession;
-       }
-
-       protected Session login(Repository repository, String workspace)
-                       throws RepositoryException {
-               Session session = repository.login(workspace);
-               if (log.isDebugEnabled())
-                       log.debug(getHttpSessionId() + " User '" + session.getUserID()
-                                       + "' logged in workspace '"
-                                       + session.getWorkspace().getName() + "' of repository '"
-                                       + currentRepositoryName + "'");
-               return session;
-       }
-
-       public void releaseSession(Session session) {
-               if (log.isTraceEnabled())
-                       log.trace(getHttpSessionId() + " Releasing JCR session " + session);
-       }
-
-       protected void logout() {
-               JcrUtils.logoutQuietly(jcrSession);
-               jcrSession = null;
-       }
-
-       protected final String getHttpSessionId() {
-               return httpSession != null ? httpSession.getId() : "<null>";
-       }
-
-       public void init() {
-       }
-
-       public void destroy() {
-               logout();
-               if (getHttpSessionId() != null)
-                       if (log.isDebugEnabled())
-                               log.debug(getHttpSessionId()
-                                               + " Cleaned up provider for web session ");
-               httpSession = null;
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java b/org.argeo.security.core/src/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java
deleted file mode 100644 (file)
index a2c43a5..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ldap;
-
-import org.springframework.security.authentication.encoding.LdapShaPasswordEncoder;
-
-/**
- * {@link LdapShaPasswordEncoder} allowing to configure the usage of salt (APache
- * Directory Server 1.0 does not support bind with SSHA)
- */
-public class ArgeoLdapShaPasswordEncoder extends LdapShaPasswordEncoder {
-       private Boolean useSalt = true;
-
-       @Override
-       public String encodePassword(String rawPass, Object salt) {
-               return super.encodePassword(rawPass, useSalt ? salt : null);
-       }
-
-       public void setUseSalt(Boolean useSalt) {
-               this.useSalt = useSalt;
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java b/org.argeo.security.core/src/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java
deleted file mode 100644 (file)
index 89b5aa9..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ldap;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.argeo.ArgeoException;
-import org.argeo.security.UserAdminService;
-import org.springframework.ldap.core.ContextSource;
-import org.springframework.security.authentication.encoding.PasswordEncoder;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.ldap.userdetails.LdapUserDetailsManager;
-
-/** Extends {@link LdapUserDetailsManager} by adding password encoding support. */
-public class ArgeoLdapUserDetailsManager extends LdapUserDetailsManager
-               implements UserAdminService {
-       private String superUsername = "root";
-       private ArgeoUserAdminDaoLdap userAdminDao;
-       private PasswordEncoder passwordEncoder;
-       private final Random random;
-
-       public ArgeoLdapUserDetailsManager(ContextSource contextSource) {
-               super(contextSource);
-               this.random = createRandom();
-       }
-
-       private static Random createRandom() {
-               try {
-                       return SecureRandom.getInstance("SHA1PRNG");
-               } catch (NoSuchAlgorithmException e) {
-                       return new Random(System.currentTimeMillis());
-               }
-       }
-
-       @Override
-       public void changePassword(String oldPassword, String newPassword) {
-               Authentication authentication = SecurityContextHolder.getContext()
-                               .getAuthentication();
-               if (authentication == null)
-                       throw new ArgeoException(
-                                       "Cannot change password without authentication");
-               String username = authentication.getName();
-               UserDetails userDetails = loadUserByUsername(username);
-               String currentPassword = userDetails.getPassword();
-               if (currentPassword == null)
-                       throw new ArgeoException("Cannot access current password");
-               if (!passwordEncoder
-                               .isPasswordValid(currentPassword, oldPassword, null))
-                       throw new ArgeoException("Old password invalid");
-               // Spring Security LDAP 2.0 is buggy when used with OpenLDAP and called
-               // with oldPassword argument
-               super.changePassword(null, encodePassword(newPassword));
-       }
-
-       public void newRole(String role) {
-               userAdminDao.createRole(role, superUsername);
-       }
-
-       public void synchronize() {
-               for (String username : userAdminDao.listUsers())
-                       loadUserByUsername(username);
-               // TODO: find a way to remove from JCR
-       }
-
-       public void deleteRole(String role) {
-               userAdminDao.deleteRole(role);
-       }
-
-       public Set<String> listUsers() {
-               return userAdminDao.listUsers();
-       }
-
-       public Set<String> listUsersInRole(String role) {
-               Set<String> lst = new TreeSet<String>(
-                               userAdminDao.listUsersInRole(role));
-               Iterator<String> it = lst.iterator();
-               while (it.hasNext()) {
-                       if (it.next().equals(superUsername)) {
-                               it.remove();
-                               break;
-                       }
-               }
-               return lst;
-       }
-
-       public List<String> listUserRoles(String username) {
-               UserDetails userDetails = loadUserByUsername(username);
-               List<String> roles = new ArrayList<String>();
-               for (GrantedAuthority ga : userDetails.getAuthorities()) {
-                       roles.add(ga.getAuthority());
-               }
-               return Collections.unmodifiableList(roles);
-       }
-
-       public Set<String> listEditableRoles() {
-               return userAdminDao.listEditableRoles();
-       }
-
-       protected String encodePassword(String password) {
-               if (!password.startsWith("{")) {
-                       byte[] salt = new byte[16];
-                       random.nextBytes(salt);
-                       return passwordEncoder.encodePassword(password, salt);
-               } else {
-                       return password;
-               }
-       }
-
-       public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
-               this.passwordEncoder = passwordEncoder;
-       }
-
-       public void setSuperUsername(String superUsername) {
-               this.superUsername = superUsername;
-       }
-
-       public void setUserAdminDao(ArgeoUserAdminDaoLdap userAdminDao) {
-               this.userAdminDao = userAdminDao;
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java b/org.argeo.security.core/src/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java
deleted file mode 100644 (file)
index 37d2a06..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ldap;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.naming.Name;
-import javax.naming.NamingException;
-import javax.naming.directory.DirContext;
-
-import org.springframework.ldap.core.ContextExecutor;
-import org.springframework.ldap.core.ContextMapper;
-import org.springframework.ldap.core.DirContextAdapter;
-import org.springframework.ldap.core.DistinguishedName;
-import org.springframework.ldap.core.LdapTemplate;
-import org.springframework.ldap.core.support.BaseLdapPathContextSource;
-import org.springframework.security.ldap.LdapUsernameToDnMapper;
-import org.springframework.security.ldap.LdapUtils;
-
-/**
- * Wraps low-level LDAP operation on user and roles, used by
- * {@link ArgeoLdapUserDetailsManager}
- */
-public class ArgeoUserAdminDaoLdap {
-       private String userBase;
-       private String usernameAttribute;
-       private String groupBase;
-       private String[] groupClasses;
-
-       private String groupRoleAttribute;
-       private String groupMemberAttribute;
-       private String defaultRole;
-       private String rolePrefix;
-
-       private final LdapTemplate ldapTemplate;
-       private LdapUsernameToDnMapper usernameMapper;
-
-       /**
-        * Standard constructor, using the LDAP context source shared with Spring
-        * Security components.
-        */
-       public ArgeoUserAdminDaoLdap(BaseLdapPathContextSource contextSource) {
-               this.ldapTemplate = new LdapTemplate(contextSource);
-       }
-
-       @SuppressWarnings("unchecked")
-       public synchronized Set<String> listUsers() {
-               List<String> usernames = (List<String>) ldapTemplate.listBindings(
-                               new DistinguishedName(userBase), new ContextMapper() {
-                                       public Object mapFromContext(Object ctxArg) {
-                                               DirContextAdapter ctx = (DirContextAdapter) ctxArg;
-                                               return ctx.getStringAttribute(usernameAttribute);
-                                       }
-                               });
-
-               return Collections
-                               .unmodifiableSortedSet(new TreeSet<String>(usernames));
-       }
-
-       @SuppressWarnings("unchecked")
-       public Set<String> listEditableRoles() {
-               return Collections.unmodifiableSortedSet(new TreeSet<String>(
-                               ldapTemplate.listBindings(groupBase, new ContextMapper() {
-                                       public Object mapFromContext(Object ctxArg) {
-                                               String groupName = ((DirContextAdapter) ctxArg)
-                                                               .getStringAttribute(groupRoleAttribute);
-                                               String roleName = convertGroupToRole(groupName);
-                                               return roleName;
-                                       }
-                               })));
-       }
-
-       @SuppressWarnings("unchecked")
-       public Set<String> listUsersInRole(String role) {
-               return (Set<String>) ldapTemplate.lookup(
-                               buildGroupDn(convertRoleToGroup(role)), new ContextMapper() {
-                                       public Object mapFromContext(Object ctxArg) {
-                                               DirContextAdapter ctx = (DirContextAdapter) ctxArg;
-                                               String[] userDns = ctx
-                                                               .getStringAttributes(groupMemberAttribute);
-                                               TreeSet<String> set = new TreeSet<String>();
-                                               for (String userDn : userDns) {
-                                                       DistinguishedName dn = new DistinguishedName(userDn);
-                                                       String username = dn.getValue(usernameAttribute);
-                                                       set.add(username);
-                                               }
-                                               return Collections.unmodifiableSortedSet(set);
-                                       }
-                               });
-       }
-
-       public void createRole(String role, final String superuserName) {
-               String group = convertRoleToGroup(role);
-               DistinguishedName superuserDn = (DistinguishedName) ldapTemplate
-                               .executeReadWrite(new ContextExecutor() {
-                                       public Object executeWithContext(DirContext ctx)
-                                                       throws NamingException {
-                                               return LdapUtils.getFullDn(
-                                                               usernameMapper.buildDn(superuserName), ctx);
-                                       }
-                               });
-
-               Name groupDn = buildGroupDn(group);
-               DirContextAdapter context = new DirContextAdapter();
-               context.setAttributeValues("objectClass", groupClasses);
-               context.setAttributeValue("cn", group);
-               // Add superuser because cannot create empty group
-               context.setAttributeValue(groupMemberAttribute, superuserDn.toString());
-               ldapTemplate.bind(groupDn, context, null);
-       }
-
-       public void deleteRole(String role) {
-               String group = convertRoleToGroup(role);
-               Name dn = buildGroupDn(group);
-               ldapTemplate.unbind(dn);
-       }
-
-       /** Maps a role (ROLE_XXX) to the related LDAP group (xxx) */
-       protected String convertRoleToGroup(String role) {
-               String group = role;
-               if (group.startsWith(rolePrefix)) {
-                       group = group.substring(rolePrefix.length());
-                       group = group.toLowerCase();
-               }
-               return group;
-       }
-
-       /** Maps anLDAP group (xxx) to the related role (ROLE_XXX) */
-       protected String convertGroupToRole(String groupName) {
-               groupName = groupName.toUpperCase();
-
-               return rolePrefix + groupName;
-       }
-
-       protected Name buildGroupDn(String name) {
-               return new DistinguishedName(groupRoleAttribute + "=" + name + ","
-                               + groupBase);
-       }
-
-       public void setUserBase(String userBase) {
-               this.userBase = userBase;
-       }
-
-       public void setUsernameAttribute(String usernameAttribute) {
-               this.usernameAttribute = usernameAttribute;
-       }
-
-       public void setGroupBase(String groupBase) {
-               this.groupBase = groupBase;
-       }
-
-       public void setGroupRoleAttribute(String groupRoleAttributeName) {
-               this.groupRoleAttribute = groupRoleAttributeName;
-       }
-
-       public void setGroupMemberAttribute(String groupMemberAttributeName) {
-               this.groupMemberAttribute = groupMemberAttributeName;
-       }
-
-       public void setDefaultRole(String defaultRole) {
-               this.defaultRole = defaultRole;
-       }
-
-       public void setRolePrefix(String rolePrefix) {
-               this.rolePrefix = rolePrefix;
-       }
-
-       public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
-               this.usernameMapper = usernameMapper;
-       }
-
-       public String getDefaultRole() {
-               return defaultRole;
-       }
-
-       public void setGroupClasses(String[] groupClasses) {
-               this.groupClasses = groupClasses;
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java b/org.argeo.security.core/src/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java
deleted file mode 100644 (file)
index e0519c3..0000000
+++ /dev/null
@@ -1,622 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ldap.jcr;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-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.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-import javax.jcr.version.VersionManager;
-import javax.naming.Name;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.ModificationItem;
-
-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.SecurityUtils;
-import org.argeo.security.jcr.JcrSecurityModel;
-import org.argeo.security.jcr.JcrUserDetails;
-import org.argeo.security.jcr.SimpleJcrSecurityModel;
-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.authentication.encoding.PasswordEncoder;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.ldap.LdapUsernameToDnMapper;
-import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
-
-/** Makes sure 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
-        * <a href=
-        * "http://forum.springsource.org/showthread.php?55955-Persistent-search-with-spring-ldap"
-        * >this</a>
-        */
-       // private LdapTemplate rawLdapTemplate;
-
-       private String userBase;
-       private String usernameAttribute;
-       private String passwordAttribute;
-       private String[] userClasses;
-       // private String defaultUserRole ="ROLE_USER";
-
-       // private NamingListener ldapUserListener;
-       // private SearchControls subTreeSearchControls;
-       private LdapUsernameToDnMapper usernameMapper;
-
-       private PasswordEncoder passwordEncoder;
-       private final Random random;
-
-       // JCR
-       /** Admin session on the main workspace */
-       private Session nodeSession;
-       private Repository repository;
-
-       // private JcrProfileListener jcrProfileListener;
-       private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
-
-       // Mapping
-       private Map<String, String> propertyToAttributes = new HashMap<String, String>();
-
-       public JcrLdapSynchronizer() {
-               random = createRandom();
-       }
-
-       public void init() {
-               try {
-                       nodeSession = repository.login();
-
-                       // TODO put this in a different thread, and poll the LDAP server
-                       // until it is up
-                       try {
-                               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;
-                               // }
-                               // });
-                       } catch (Exception e) {
-                               log.error("Could not synchronize and listen to LDAP,"
-                                               + " probably because the LDAP server is not available."
-                                               + " Restart the system as soon as possible.", e);
-                       }
-
-                       // 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
-                       // nodeSession
-                       // .getWorkspace()
-                       // .getObservationManager()
-                       // .addEventListener(jcrProfileListener,
-                       // Event.PROPERTY_CHANGED | Event.NODE_ADDED, "/",
-                       // true, null, nodeTypes, true);
-               } catch (Exception e) {
-                       JcrUtils.logoutQuietly(nodeSession);
-                       throw new ArgeoException("Cannot initialize LDAP/JCR synchronizer",
-                                       e);
-               }
-       }
-
-       public void destroy() {
-               // JcrUtils.removeListenerQuietly(nodeSession, jcrProfileListener);
-               JcrUtils.logoutQuietly(nodeSession);
-               // 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<String> userPaths = (List<String>) ldapTemplate.listBindings(
-                                       userBaseName, new ContextMapper() {
-                                               public Object mapFromContext(Object ctxObj) {
-                                                       try {
-                                                               return mapLdapToJcr((DirContextAdapter) ctxObj);
-                                                       } catch (Exception e) {
-                                                               // do not break process because of error
-                                                               log.error(
-                                                                               "Could not LDAP->JCR synchronize user "
-                                                                                               + ctxObj, e);
-                                                               return null;
-                                                       }
-                                               }
-                                       });
-
-                       // create accounts which are not in LDAP
-                       Query query = nodeSession
-                                       .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();
-                               try {
-                                       if (!userPaths.contains(path)) {
-                                               String username = userProfile
-                                                               .getProperty(ARGEO_USER_ID).getString();
-                                               // GrantedAuthority[] authorities = {new
-                                               // GrantedAuthorityImpl(defaultUserRole)};
-                                               List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
-                                               JcrUserDetails userDetails = new JcrUserDetails(
-                                                               userProfile, username, authorities);
-                                               String dn = createLdapUser(userDetails);
-                                               log.warn("Created ldap entry '" + dn + "' for user '"
-                                                               + username + "'");
-
-                                               // if(!userProfile.getProperty(ARGEO_ENABLED).getBoolean()){
-                                               // continue profiles;
-                                               // }
-                                               //
-                                               // log.warn("Path "
-                                               // + path
-                                               // + " not found in LDAP, disabling user "
-                                               // + userProfile.getProperty(ArgeoNames.ARGEO_USER_ID)
-                                               // .getString());
-
-                                               // Temporary hack to repair previous behaviour
-                                               if (!userProfile.getProperty(ARGEO_ENABLED)
-                                                               .getBoolean()) {
-                                                       VersionManager versionManager = nodeSession
-                                                                       .getWorkspace().getVersionManager();
-                                                       versionManager.checkout(userProfile.getPath());
-                                                       userProfile.setProperty(ArgeoNames.ARGEO_ENABLED,
-                                                                       true);
-                                                       nodeSession.save();
-                                                       versionManager.checkin(userProfile.getPath());
-                                               }
-                                       }
-                               } catch (Exception e) {
-                                       log.error("Cannot process " + path, e);
-                               }
-                       }
-               } catch (Exception e) {
-                       JcrUtils.discardQuietly(nodeSession);
-                       log.error("Cannot synchronize LDAP and JCR", e);
-                       // throw new ArgeoException("Cannot synchronize LDAP and JCR", e);
-               }
-       }
-
-       private String createLdapUser(UserDetails user) {
-               DirContextAdapter ctx = new DirContextAdapter();
-               mapUserToContext(user, ctx);
-               DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
-               ldapTemplate.bind(dn, ctx, null);
-               return dn.toString();
-       }
-
-       /** Called during authentication in order to retrieve user details */
-       public UserDetails mapUserFromContext(final DirContextOperations ctx,
-                       final String username,
-                       Collection<? extends GrantedAuthority> authorities) {
-               if (ctx == null)
-                       throw new ArgeoException("No LDAP information for user " + username);
-
-               String ldapUsername = ctx.getStringAttribute(usernameAttribute);
-               if (!ldapUsername.equals(username))
-                       throw new ArgeoException("Logged in with username " + username
-                                       + " but LDAP user is " + ldapUsername);
-
-               Node userProfile = jcrSecurityModel.sync(nodeSession, username,
-                               SecurityUtils.authoritiesToStringList(authorities));
-               // JcrUserDetails.checkAccountStatus(userProfile);
-
-               // password
-               SortedSet<?> passwordAttributes = ctx
-                               .getAttributeSortedStringSet(passwordAttribute);
-               String password;
-               if (passwordAttributes == null || passwordAttributes.size() == 0) {
-                       // throw new ArgeoException("No password found for user " +
-                       // username);
-                       password = "NULL";
-               } else {
-                       byte[] arr = (byte[]) passwordAttributes.first();
-                       password = new String(arr);
-                       // erase password
-                       Arrays.fill(arr, (byte) 0);
-               }
-
-               try {
-                       return new JcrUserDetails(userProfile, 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 synchronized String mapLdapToJcr(DirContextAdapter ctx) {
-               Session session = nodeSession;
-               try {
-                       // process
-                       String username = ctx.getStringAttribute(usernameAttribute);
-
-                       Node userProfile = jcrSecurityModel.sync(session, username, null);
-                       Map<String, String> modifications = new HashMap<String, String>();
-                       for (String jcrProperty : propertyToAttributes.keySet())
-                               ldapToJcr(userProfile, jcrProperty, ctx, modifications);
-
-                       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<String, String> 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);
-                       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 = nodeSession
-                                       .getNode(jcrUserDetails.getHomePath()).getNode(
-                                                       ARGEO_PROFILE);
-                       for (String jcrProperty : propertyToAttributes.keySet()) {
-                               if (userProfile.hasProperty(jcrProperty)) {
-                                       ModificationItem mi = jcrToLdap(jcrProperty, userProfile
-                                                       .getProperty(jcrProperty).getString());
-                                       if (mi != null)
-                                               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 setUserBase(String userBase) {
-               this.userBase = userBase;
-       }
-
-       public void setUsernameAttribute(String usernameAttribute) {
-               this.usernameAttribute = usernameAttribute;
-       }
-
-       public void setPropertyToAttributes(Map<String, String> 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;
-       }
-
-       public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
-               this.jcrSecurityModel = jcrSecurityModel;
-       }
-
-       /** 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<Name, List<ModificationItem>> modifications = new HashMap<Name,
-       // List<ModificationItem>>();
-       // while (events.hasNext()) {
-       // Event event = events.nextEvent();
-       // try {
-       // if (Event.PROPERTY_CHANGED == event.getType()) {
-       // Property property = (Property) nodeSession
-       // .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<ModificationItem>());
-       // 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 = nodeSession.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<ModificationItem>());
-       // 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<ModificationItem> 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/org.argeo.security.core/src/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java b/org.argeo.security.core/src/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java
deleted file mode 100644 (file)
index f63250c..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ldap.jcr;
-
-import java.util.Collection;
-import java.util.UUID;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-import org.argeo.security.jcr.JcrUserDetails;
-import org.springframework.ldap.core.DirContextAdapter;
-import org.springframework.ldap.core.DirContextOperations;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
-
-/** @deprecated Read only mapping from LDAP to user details */
-@Deprecated
-public class JcrUserDetailsContextMapper implements UserDetailsContextMapper,
-               ArgeoNames {
-       /** Admin session on the security workspace */
-       private Session securitySession;
-       private Repository repository;
-       private String securityWorkspace = "security";
-
-       public void init() {
-               try {
-                       securitySession = repository.login(securityWorkspace);
-               } catch (RepositoryException e) {
-                       JcrUtils.logoutQuietly(securitySession);
-                       throw new ArgeoException(
-                                       "Cannot initialize LDAP/JCR user details context mapper", e);
-               }
-       }
-
-       public void destroy() {
-               JcrUtils.logoutQuietly(securitySession);
-       }
-
-       /** Called during authentication in order to retrieve user details */
-       public UserDetails mapUserFromContext(final DirContextOperations ctx,
-                       final String username,
-                       Collection<? extends GrantedAuthority> authorities) {
-               if (ctx == null)
-                       throw new ArgeoException("No LDAP information for user " + username);
-               Node userHome = UserJcrUtils.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 {
-                       // we don't have access to password, so let's not pretend
-                       String password = UUID.randomUUID().toString();
-                       return new JcrUserDetails(userHome.getNode(ARGEO_PROFILE),
-                                       password, authorities);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot retrieve user details for "
-                                       + username, e);
-               }
-       }
-
-       public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
-               throw new UnsupportedOperationException("LDAP access is read-only");
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/login/AbstractSpringLoginModule.java b/org.argeo.security.core/src/org/argeo/security/login/AbstractSpringLoginModule.java
deleted file mode 100644 (file)
index 8a09a08..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.login;
-
-import java.io.IOException;
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.service.useradmin.UserAdmin;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-
-/** Login module which caches one subject per thread. */
-abstract class AbstractSpringLoginModule implements LoginModule {
-       // private final static Log log = LogFactory
-       // .getLog(AbstractSpringLoginModule.class);
-       private CallbackHandler callbackHandler;
-       private Subject subject;
-
-       private Authentication authentication;
-
-       protected abstract Authentication processLogin(
-                       CallbackHandler callbackHandler) throws LoginException,
-                       UnsupportedCallbackException, IOException, InterruptedException;
-
-       @SuppressWarnings("rawtypes")
-       @Override
-       public void initialize(Subject subject, CallbackHandler callbackHandler,
-                       Map sharedState, Map options) {
-               this.callbackHandler = callbackHandler;
-               this.subject = subject;
-       }
-
-       @Override
-       public boolean login() throws LoginException {
-               try {
-                       // thread already logged in
-                       Authentication currentAuth = SecurityContextHolder.getContext()
-                                       .getAuthentication();
-                       if (currentAuth != null) {
-                               if (subject.getPrincipals(Authentication.class).size() == 0) {
-                                       throw new LoginException(
-                                                       "Security context set but not Authentication principal");
-                               } else {
-                                       Authentication principal = subject
-                                                       .getPrincipals(Authentication.class).iterator()
-                                                       .next();
-                                       if (principal != currentAuth)
-                                               throw new LoginException(
-                                                               "Already authenticated with a different auth");
-                               }
-                               return true;
-                       }
-
-                       if (callbackHandler == null)
-                               throw new LoginException("No callback handler available");
-
-                       authentication = processLogin(callbackHandler);
-                       if (authentication != null) {
-                               SecurityContextHolder.getContext().setAuthentication(
-                                               authentication);
-                               return true;
-                       } else {
-                               throw new LoginException("No authentication returned");
-                       }
-               } catch (LoginException e) {
-                       throw e;
-               } catch (ThreadDeath e) {
-                       LoginException le = new LoginException(
-                                       "Spring Security login thread died");
-                       le.initCause(e);
-                       throw le;
-               } catch (Exception e) {
-                       LoginException le = new LoginException(
-                                       "Spring Security login failed");
-                       le.initCause(e);
-                       throw le;
-               }
-       }
-
-       @Override
-       public boolean logout() throws LoginException {
-               SecurityContextHolder.getContext().setAuthentication(null);
-               return true;
-       }
-
-       @Override
-       public boolean commit() throws LoginException {
-               return true;
-       }
-
-       @Override
-       public boolean abort() throws LoginException {
-               SecurityContextHolder.getContext().setAuthentication(null);
-               return true;
-       }
-
-       protected AuthenticationManager getAuthenticationManager(
-                       BundleContextCallback bundleContextCallback) {
-               BundleContext bc = bundleContextCallback.getBundleContext();
-               return bc.getService(bc
-                               .getServiceReference(AuthenticationManager.class));
-
-       }
-
-       protected UserAdmin getUserAdmin(BundleContextCallback bundleContextCallback) {
-               BundleContext bc = bundleContextCallback.getBundleContext();
-               return bc.getService(bc.getServiceReference(UserAdmin.class));
-       }
-
-       protected Subject getSubject() {
-               return subject;
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/login/AnonymousLoginModule.java b/org.argeo.security.core/src/org/argeo/security/login/AnonymousLoginModule.java
deleted file mode 100644 (file)
index 0a1279c..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.login;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.security.SecurityUtils;
-import org.argeo.util.LocaleCallback;
-import org.argeo.util.LocaleUtils;
-import org.springframework.security.authentication.AnonymousAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
-/** Login module which caches one subject per thread. */
-public class AnonymousLoginModule extends AbstractSpringLoginModule {
-       private String anonymousRole = "ROLE_ANONYMOUS";
-       /** Comma separated list of locales */
-       private String availableLocales = null;
-
-       @Override
-       protected Authentication processLogin(CallbackHandler callbackHandler)
-                       throws LoginException, UnsupportedCallbackException, IOException,
-                       InterruptedException {
-               BundleContextCallback bundleContextCallback = new BundleContextCallback();
-               Locale selectedLocale = null;
-               // multi locale
-               if (availableLocales != null && !availableLocales.trim().equals("")) {
-                       LocaleCallback localeCallback = new LocaleCallback(availableLocales);
-                       callbackHandler.handle(new Callback[] { localeCallback,
-                                       bundleContextCallback });
-                       selectedLocale = localeCallback.getSelectedLocale();
-               } else {
-                       callbackHandler.handle(new Callback[] { bundleContextCallback });
-               }
-
-               List<SimpleGrantedAuthority> authorities = Collections
-                               .singletonList(new SimpleGrantedAuthority(anonymousRole));
-               AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken(
-                               SecurityUtils.getStaticKey(), null, authorities);
-
-               Authentication auth = getAuthenticationManager(bundleContextCallback)
-                               .authenticate(anonymousToken);
-
-               if (selectedLocale != null)
-                       LocaleUtils.threadLocale.set(selectedLocale);
-               return auth;
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/login/BundleContextCallback.java b/org.argeo.security.core/src/org/argeo/security/login/BundleContextCallback.java
deleted file mode 100644 (file)
index cf32af5..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.argeo.security.login;
-
-import javax.security.auth.callback.Callback;
-
-import org.osgi.framework.BundleContext;
-
-/** Gives access to the OSGi {@link BundleContext} */
-public class BundleContextCallback implements Callback {
-       private BundleContext bundleContext;
-
-       public BundleContext getBundleContext() {
-               return bundleContext;
-       }
-
-       public void setBundleContext(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/login/BundleContextCallbackHandler.java b/org.argeo.security.core/src/org/argeo/security/login/BundleContextCallbackHandler.java
deleted file mode 100644 (file)
index 3c7f9e8..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.argeo.security.login;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.osgi.framework.BundleContext;
-
-/**
- * {@link CallbackHandler} that simply wraps a {@link BundleContext} and inject
- * it in provided {@link BundleContextCallback}
- */
-public class BundleContextCallbackHandler implements CallbackHandler {
-       private BundleContext bundleContext;
-
-       public BundleContextCallbackHandler() {
-       }
-
-       public BundleContextCallbackHandler(BundleContext bundleContext) {
-               super();
-               this.bundleContext = bundleContext;
-       }
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException,
-                       UnsupportedCallbackException {
-               for (Callback callback : callbacks) {
-                       if (callback instanceof BundleContextCallback)
-                               ((BundleContextCallback) callback)
-                                               .setBundleContext(bundleContext);
-               }
-
-       }
-
-       public void setBundleContext(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/login/ConsoleCallbackHandler.java b/org.argeo.security.core/src/org/argeo/security/login/ConsoleCallbackHandler.java
deleted file mode 100644 (file)
index 4c47ac6..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.argeo.security.login;
-
-import java.io.Console;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Locale;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.argeo.ArgeoException;
-import org.argeo.util.LocaleCallback;
-
-/** Callback handler to be used with a command line UI. */
-public class ConsoleCallbackHandler implements CallbackHandler {
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException,
-                       UnsupportedCallbackException {
-               Console console = System.console();
-               if (console == null)
-                       throw new ArgeoException("No console available");
-
-               PrintWriter writer = console.writer();
-               for (int i = 0; i < callbacks.length; i++) {
-                       if (callbacks[i] instanceof TextOutputCallback) {
-                               TextOutputCallback callback = (TextOutputCallback) callbacks[i];
-                               writer.write(callback.getMessage());
-                       } else if (callbacks[i] instanceof NameCallback) {
-                               NameCallback callback = (NameCallback) callbacks[i];
-                               writer.write(callback.getPrompt());
-                               if (callback.getDefaultName() != null)
-                                       writer.write(" (" + callback.getDefaultName() + ")");
-                               writer.write(" : ");
-                               String answer = console.readLine();
-                               if (callback.getDefaultName() != null
-                                               && answer.trim().equals(""))
-                                       callback.setName(callback.getDefaultName());
-                               else
-                                       callback.setName(answer);
-                       } else if (callbacks[i] instanceof PasswordCallback) {
-                               PasswordCallback callback = (PasswordCallback) callbacks[i];
-                               writer.write(callback.getPrompt());
-                               char[] answer = console.readPassword();
-                               callback.setPassword(answer);
-                               Arrays.fill(answer, ' ');
-                       } else if (callbacks[i] instanceof LocaleCallback) {
-                               LocaleCallback callback = (LocaleCallback) callbacks[i];
-                               writer.write(callback.getPrompt());
-                               writer.write("\n");
-                               for (int j = 0; j < callback.getAvailableLocales().size(); j++) {
-                                       Locale locale = callback.getAvailableLocales().get(j);
-                                       writer.print(j + " : " + locale.getDisplayName() + "\n");
-                               }
-                               writer.write("(" + callback.getDefaultIndex() + ") : ");
-                               String answer = console.readLine();
-                               if (answer.trim().equals(""))
-                                       callback.setSelectedIndex(callback.getDefaultIndex());
-                               else
-                                       callback.setSelectedIndex(new Integer(answer.trim()));
-                       }
-               }
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/login/EndUserLoginModule.java b/org.argeo.security.core/src/org/argeo/security/login/EndUserLoginModule.java
deleted file mode 100644 (file)
index e01e714..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.login;
-
-import java.io.IOException;
-import java.util.Locale;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.security.NodeAuthenticationToken;
-import org.argeo.util.LocaleCallback;
-import org.argeo.util.LocaleUtils;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.core.Authentication;
-
-/** Authenticates an end user */
-public class EndUserLoginModule extends AbstractSpringLoginModule {
-       final static String NODE_REPO_URI = "argeo.node.repo.uri";
-
-       private Long waitBetweenFailedLoginAttempts = 5 * 1000l;
-
-       private Boolean remote = false;
-       /** Comma separated list of locales */
-       private String availableLocales = "";
-
-       @Override
-       protected Authentication processLogin(CallbackHandler callbackHandler)
-                       throws LoginException, UnsupportedCallbackException, IOException,
-                       InterruptedException {
-               // ask for username and password
-               NameCallback nameCallback = new NameCallback("User");
-               PasswordCallback passwordCallback = new PasswordCallback("Password",
-                               false);
-               final String defaultNodeUrl = System.getProperty(NODE_REPO_URI,
-                               "http://localhost:7070/org.argeo.jcr.webapp/remoting/node");
-               NameCallback urlCallback = new NameCallback("Site URL", defaultNodeUrl);
-               LocaleCallback localeCallback = new LocaleCallback(availableLocales);
-               BundleContextCallback bundleContextCallback = new BundleContextCallback();
-
-               // handle callbacks
-               if (remote)
-                       callbackHandler.handle(new Callback[] { nameCallback,
-                                       passwordCallback, urlCallback, localeCallback,
-                                       bundleContextCallback });
-               else
-                       callbackHandler.handle(new Callback[] { nameCallback,
-                                       passwordCallback, localeCallback, bundleContextCallback });
-
-               Locale selectedLocale = localeCallback.getSelectedLocale();
-
-               // create credentials
-               final String username = nameCallback.getName();
-               if (username == null || username.trim().equals(""))
-                       throw new LoginCanceledException();
-
-               char[] password = {};
-               if (passwordCallback.getPassword() != null)
-                       password = passwordCallback.getPassword();
-
-               NodeAuthenticationToken credentials;
-               if (remote) {
-                       String url = urlCallback.getName();
-                       credentials = new NodeAuthenticationToken(username, password, url);
-               } else {
-                       credentials = new NodeAuthenticationToken(username, password);
-               }
-
-               Authentication auth;
-               try {
-                       auth = getAuthenticationManager(bundleContextCallback)
-                                       .authenticate(credentials);
-               } catch (BadCredentialsException e) {
-                       // wait between failed login attempts
-                       Thread.sleep(waitBetweenFailedLoginAttempts);
-                       throw e;
-               }
-
-               if (selectedLocale != null)
-                       LocaleUtils.threadLocale.set(selectedLocale);
-
-               return auth;
-       }
-
-       @Override
-       public boolean commit() throws LoginException {
-               return super.commit();
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/login/GrantedAuthorityPrincipal.java b/org.argeo.security.core/src/org/argeo/security/login/GrantedAuthorityPrincipal.java
deleted file mode 100644 (file)
index c176c04..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.login;
-
-import java.security.Principal;
-
-import javax.security.auth.Subject;
-
-import org.springframework.security.core.GrantedAuthority;
-
-/**
- * A {@link Principal} which is also a {@link GrantedAuthority}, so that the
- * Spring Security can be used to quickly populate a {@link Subject} principals.
- */
-public final class GrantedAuthorityPrincipal implements Principal,
-               GrantedAuthority {
-       private static final long serialVersionUID = 6768044196343543328L;
-       private final String authority;
-
-       public GrantedAuthorityPrincipal(String authority) {
-               this.authority = authority;
-       }
-
-       @Override
-       public String getAuthority() {
-               return authority;
-       }
-
-       @Override
-       public String getName() {
-               return authority;
-       }
-
-       @Override
-       public int hashCode() {
-               return getName().hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof GrantedAuthorityPrincipal))
-                       return false;
-               return getName().equals(((GrantedAuthorityPrincipal) obj).getName());
-       }
-
-       @Override
-       public String toString() {
-               return "Granted Authority " + getName();
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/login/LoginCanceledException.java b/org.argeo.security.core/src/org/argeo/security/login/LoginCanceledException.java
deleted file mode 100644 (file)
index 5629e2e..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.security.login;
-
-import javax.security.auth.login.LoginException;
-
-public class LoginCanceledException extends LoginException {
-       private static final long serialVersionUID = 8289162094013471043L;
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/login/SystemLoginModule.java b/org.argeo.security.core/src/org/argeo/security/login/SystemLoginModule.java
deleted file mode 100644 (file)
index 54e80e8..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.login;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.security.SecurityUtils;
-import org.argeo.security.core.InternalAuthentication;
-import org.springframework.security.core.Authentication;
-
-/** Login module which caches one subject per thread. */
-public class SystemLoginModule extends AbstractSpringLoginModule {
-       @Override
-       protected Authentication processLogin(CallbackHandler callbackHandler)
-                       throws LoginException, UnsupportedCallbackException, IOException,
-                       InterruptedException {
-               BundleContextCallback bundleContextCallback = new BundleContextCallback();
-               callbackHandler.handle(new Callback[] { bundleContextCallback });
-               InternalAuthentication anonymousToken = new InternalAuthentication(
-                               SecurityUtils.getStaticKey());
-               return getAuthenticationManager(bundleContextCallback).authenticate(
-                               anonymousToken);
-       }
-}
index d2953a684d400476bf716ed6a6c3c3ae486fcd0e..f9f1a39d85d66c7198c956bbecfd2cad76b330cd 100644 (file)
@@ -4,6 +4,6 @@
        <classpathentry kind="con"
                path="org.eclipse.pde.core.requiredPlugins" />
        <classpathentry kind="con"
-               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" />
+               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7" />
        <classpathentry kind="output" path="bin" />
 </classpath>
index 65657dc66204a4642d890e04dd6939aa3dd75a64..9b01c552535afed97544bf234f0023430289d26c 100644 (file)
@@ -18,6 +18,8 @@ package org.argeo.security.ui.rap;
 import java.security.PrivilegedAction;
 
 import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.CredentialNotFoundException;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 import javax.servlet.http.HttpServletRequest;
@@ -27,9 +29,9 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.ArgeoException;
 import org.argeo.cms.KernelHeader;
+import org.argeo.cms.auth.ArgeoLoginContext;
 import org.argeo.eclipse.ui.workbench.ErrorFeedback;
-import org.argeo.security.login.LoginCanceledException;
-import org.argeo.security.ui.dialogs.DefaultLoginDialog;
+import org.argeo.security.ui.auth.DefaultLoginDialog;
 import org.argeo.util.LocaleUtils;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.rap.rwt.RWT;
@@ -94,12 +96,14 @@ public class SecureEntryPoint implements EntryPoint {
                Subject subject = new Subject();
 
                // log in
-               Thread.currentThread().setContextClassLoader(
-                               getClass().getClassLoader());
+               // Thread.currentThread().setContextClassLoader(
+               // getClass().getClassLoader());
                final LoginContext loginContext;
                try {
-                       loginContext = new LoginContext(KernelHeader.LOGIN_CONTEXT_USER,
-                                       subject, new DefaultLoginDialog(display.getActiveShell()));
+                       CallbackHandler callbackHandler = new DefaultLoginDialog(
+                                       display.getActiveShell());
+                       loginContext = new ArgeoLoginContext(
+                                       KernelHeader.LOGIN_CONTEXT_USER, subject, callbackHandler);
                } catch (LoginException e1) {
                        throw new ArgeoException("Cannot initialize login context", e1);
                }
@@ -119,7 +123,8 @@ public class SecureEntryPoint implements EntryPoint {
                                        httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
                                                        SecurityContextHolder.getContext());
                                // add thread locale to RWT session
-                               log.info("Locale " + LocaleUtils.threadLocale.get());
+                               if (log.isTraceEnabled())
+                                       log.trace("Locale " + LocaleUtils.threadLocale.get());
                                RWT.setLocale(LocaleUtils.threadLocale.get());
 
                                // Once the user is logged in, longer session timeout
@@ -201,7 +206,7 @@ public class SecureEntryPoint implements EntryPoint {
                if (t instanceof BadCredentialsException)
                        return (BadCredentialsException) t;
 
-               if (t instanceof LoginCanceledException)
+               if (t instanceof CredentialNotFoundException)
                        return new BadCredentialsException("Login canceled");
 
                if (t.getCause() != null)
index ad5b3620392a5961a3ff03fd408ef8b851554ff5..a4190d69a620c89385aaae5107e923badf4a7e35 100644 (file)
@@ -22,7 +22,7 @@ import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.callback.UnsupportedCallbackException;
 
 import org.argeo.ArgeoException;
-import org.argeo.security.ui.dialogs.DefaultLoginDialog;
+import org.argeo.security.ui.auth.DefaultLoginDialog;
 import org.eclipse.swt.widgets.Display;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
@@ -44,7 +44,7 @@ public class SecurityUiPlugin implements BundleActivator {
        // The plug-in ID
        public final static String PLUGIN_ID = "org.argeo.security.ui"; //$NON-NLS-1$
 
-       public final static String CONTEXT_KEYRING = "KEYRING";
+       final static String CONTEXT_KEYRING = "KEYRING";
 
        private CallbackHandler defaultCallbackHandler;
        private ServiceRegistration<CallbackHandler> defaultCallbackHandlerReg;
diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/auth/AbstractLoginDialog.java b/org.argeo.security.ui/src/org/argeo/security/ui/auth/AbstractLoginDialog.java
new file mode 100644 (file)
index 0000000..107697e
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.ui.auth;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.security.ui.SecurityUiPlugin;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.TrayDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.operation.ModalContext;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** Base for login dialogs */
+public abstract class AbstractLoginDialog extends TrayDialog implements
+               CallbackHandler {
+       private static final long serialVersionUID = -8046708963512717709L;
+
+       private final static Log log = LogFactory.getLog(AbstractLoginDialog.class);
+
+       private Thread modalContextThread = null;
+       boolean processCallbacks = false;
+       boolean isCancelled = false;
+       Callback[] callbackArray;
+
+       protected final Callback[] getCallbacks() {
+               return this.callbackArray;
+       }
+
+       public abstract void internalHandle();
+
+       public boolean isCancelled() {
+               return isCancelled;
+       }
+
+       protected AbstractLoginDialog(Shell parentShell) {
+               super(parentShell);
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see
+        * javax.security.auth.callback.CallbackHandler#handle(javax.security.auth
+        * .callback.Callback[])
+        */
+       public void handle(final Callback[] callbacks) throws IOException {
+               // clean previous usage
+               if (processCallbacks) {
+                       // this handler was already used
+                       processCallbacks = false;
+               }
+
+               if (modalContextThread != null) {
+                       try {
+                               modalContextThread.join(1000);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+                       modalContextThread = null;
+               }
+
+               // initialize
+               this.callbackArray = callbacks;
+               final Display display = Display.getDefault();
+               display.syncExec(new Runnable() {
+
+                       public void run() {
+                               isCancelled = false;
+                               setBlockOnOpen(false);
+                               open();
+
+                               final Button okButton = getButton(IDialogConstants.OK_ID);
+                               okButton.setText("Login");
+                               okButton.addSelectionListener(new SelectionListener() {
+                                       private static final long serialVersionUID = -200281625679096775L;
+
+                                       public void widgetSelected(final SelectionEvent event) {
+                                               processCallbacks = true;
+                                       }
+
+                                       public void widgetDefaultSelected(final SelectionEvent event) {
+                                               // nothing to do
+                                       }
+                               });
+                               final Button cancel = getButton(IDialogConstants.CANCEL_ID);
+                               cancel.addSelectionListener(new SelectionListener() {
+                                       private static final long serialVersionUID = -3826030278084915815L;
+
+                                       public void widgetSelected(final SelectionEvent event) {
+                                               isCancelled = true;
+                                               processCallbacks = true;
+                                       }
+
+                                       public void widgetDefaultSelected(final SelectionEvent event) {
+                                               // nothing to do
+                                       }
+                               });
+                       }
+               });
+               try {
+                       ModalContext.setAllowReadAndDispatch(true); // Works for now.
+                       ModalContext.run(new IRunnableWithProgress() {
+
+                               public void run(final IProgressMonitor monitor) {
+                                       modalContextThread = Thread.currentThread();
+                                       // Wait here until OK or cancel is pressed, then let it rip.
+                                       // The event
+                                       // listener
+                                       // is responsible for closing the dialog (in the
+                                       // loginSucceeded
+                                       // event).
+                                       while (!processCallbacks && (modalContextThread != null)
+                                                       && (modalContextThread == Thread.currentThread())
+                                                       && SecurityUiPlugin.getBundleContext() != null) {
+                                               // Note: SecurityUiPlugin.getDefault() != null is false
+                                               // when the OSGi runtime is shut down
+                                               try {
+                                                       Thread.sleep(100);
+                                                       // if (display.isDisposed()) {
+                                                       // log.warn("Display is disposed, killing login dialog thread");
+                                                       // throw new ThreadDeath();
+                                                       // }
+                                               } catch (final Exception e) {
+                                                       // do nothing
+                                               }
+                                       }
+                                       processCallbacks = false;
+                                       // Call the adapter to handle the callbacks
+                                       if (!isCancelled())
+                                               internalHandle();
+                                       else
+                                               // clear callbacks are when cancelling
+                                               for (Callback callback : callbacks)
+                                                       if (callback instanceof PasswordCallback) {
+                                                               char[] arr = ((PasswordCallback) callback)
+                                                                               .getPassword();
+                                                               if (arr != null) {
+                                                                       Arrays.fill(arr, '*');
+                                                                       ((PasswordCallback) callback)
+                                                                                       .setPassword(null);
+                                                               }
+                                                       } else if (callback instanceof NameCallback)
+                                                               ((NameCallback) callback).setName(null);
+                               }
+                       }, true, new NullProgressMonitor(), Display.getDefault());
+               } catch (ThreadDeath e) {
+                       isCancelled = true;
+                       log.debug("Thread " + Thread.currentThread().getId() + " died");
+                       throw e;
+               } catch (Exception e) {
+                       isCancelled = true;
+                       IOException ioe = new IOException(
+                                       "Unexpected issue in login dialog, see root cause for more details");
+                       ioe.initCause(e);
+                       throw ioe;
+               } finally {
+                       // so that the modal thread dies
+                       processCallbacks = true;
+                       // try {
+                       // // wait for the modal context thread to gracefully exit
+                       // modalContextThread.join();
+                       // } catch (InterruptedException ie) {
+                       // // silent
+                       // }
+                       modalContextThread = null;
+               }
+       }
+
+       protected void configureShell(Shell shell) {
+               super.configureShell(shell);
+               shell.setText("Authentication");
+       }
+}
diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/auth/CompositeCallbackHandler.java b/org.argeo.security.ui/src/org/argeo/security/ui/auth/CompositeCallbackHandler.java
new file mode 100644 (file)
index 0000000..a2fc831
--- /dev/null
@@ -0,0 +1,291 @@
+package org.argeo.security.ui.auth;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.util.LocaleCallback;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * A composite that can populate itself based on {@link Callback}s. It can be
+ * used directly as a {@link CallbackHandler} or be used by one by calling the
+ * {@link #createCallbackHandlers(Callback[])}.
+ * <p>
+ * Supported standard {@link Callback}s are:<br>
+ * <ul>
+ * <li>{@link PasswordCallback}</li>
+ * <li>{@link NameCallback}</li>
+ * <li>{@link TextOutputCallback}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Supported Argeo {@link Callback}s are:<br>
+ * <ul>
+ * <li>{@link LocaleCallback}</li>
+ * </ul>
+ * </p>
+ */
+public class CompositeCallbackHandler extends Composite implements
+               CallbackHandler {
+       private static final long serialVersionUID = -928223893722723777L;
+
+       private boolean wasUsedAlready = false;
+       private boolean isSubmitted = false;
+       private boolean isCanceled = false;
+
+       public CompositeCallbackHandler(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       public synchronized void handle(final Callback[] callbacks)
+                       throws IOException, UnsupportedCallbackException {
+               // reset
+               if (wasUsedAlready && !isSubmitted() && !isCanceled()) {
+                       cancel();
+                       for (Control control : getChildren())
+                               control.dispose();
+                       isSubmitted = false;
+                       isCanceled = false;
+               }
+
+               for (Callback callback : callbacks)
+                       checkCallbackSupported(callback);
+               // create controls synchronously in the UI thread
+               getDisplay().syncExec(new Runnable() {
+
+                       @Override
+                       public void run() {
+                               createCallbackHandlers(callbacks);
+                       }
+               });
+
+               if (!wasUsedAlready)
+                       wasUsedAlready = true;
+
+               while (!isSubmitted() && !isCanceled()) {
+                       try {
+                               wait(1000l);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+               }
+
+               cleanCallbacksAfterCancel(callbacks);
+       }
+
+       public void checkCallbackSupported(Callback callback)
+                       throws UnsupportedCallbackException {
+               if (callback instanceof TextOutputCallback
+                               || callback instanceof NameCallback
+                               || callback instanceof PasswordCallback
+                               || callback instanceof LocaleCallback) {
+                       return;
+               } else {
+                       throw new UnsupportedCallbackException(callback);
+               }
+       }
+
+       /**
+        * Set writable callbacks to null if the handle is canceled (check is done
+        * by the method)
+        */
+       public void cleanCallbacksAfterCancel(Callback[] callbacks) {
+               if (isCanceled()) {
+                       for (Callback callback : callbacks) {
+                               if (callback instanceof NameCallback) {
+                                       ((NameCallback) callback).setName(null);
+                               } else if (callback instanceof PasswordCallback) {
+                                       PasswordCallback pCallback = (PasswordCallback) callback;
+                                       char[] arr = pCallback.getPassword();
+                                       if (arr != null) {
+                                               Arrays.fill(arr, '*');
+                                               pCallback.setPassword(null);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       public void createCallbackHandlers(Callback[] callbacks) {
+               Composite composite = this;
+               for (int i = 0; i < callbacks.length; i++) {
+                       Callback callback = callbacks[i];
+                       if (callback instanceof TextOutputCallback) {
+                               createLabelTextoutputHandler(composite,
+                                               (TextOutputCallback) callback);
+                       } else if (callback instanceof NameCallback) {
+                               createNameHandler(composite, (NameCallback) callback);
+                       } else if (callback instanceof PasswordCallback) {
+                               createPasswordHandler(composite, (PasswordCallback) callback);
+                       } else if (callback instanceof LocaleCallback) {
+                               createLocaleHandler(composite, (LocaleCallback) callback);
+                       }
+               }
+       }
+
+       protected Text createNameHandler(Composite composite,
+                       final NameCallback callback) {
+               Label label = new Label(composite, SWT.NONE);
+               label.setText(callback.getPrompt());
+               final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD
+                               | SWT.BORDER);
+               if (callback.getDefaultName() != null) {
+                       // set default value, if provided
+                       text.setText(callback.getDefaultName());
+                       callback.setName(callback.getDefaultName());
+               }
+               text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               text.addModifyListener(new ModifyListener() {
+                       private static final long serialVersionUID = 7300032545287292973L;
+
+                       public void modifyText(ModifyEvent event) {
+                               callback.setName(text.getText());
+                       }
+               });
+               text.addSelectionListener(new SelectionListener() {
+                       private static final long serialVersionUID = 1820530045857665111L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               submit();
+                       }
+               });
+
+               text.addKeyListener(new KeyListener() {
+                       private static final long serialVersionUID = -8698107785092095713L;
+
+                       @Override
+                       public void keyReleased(KeyEvent e) {
+                       }
+
+                       @Override
+                       public void keyPressed(KeyEvent e) {
+                       }
+               });
+               return text;
+       }
+
+       protected Text createPasswordHandler(Composite composite,
+                       final PasswordCallback callback) {
+               Label label = new Label(composite, SWT.NONE);
+               label.setText(callback.getPrompt());
+               final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD
+                               | SWT.PASSWORD | SWT.BORDER);
+               passwordText
+                               .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               passwordText.addModifyListener(new ModifyListener() {
+                       private static final long serialVersionUID = -7099363995047686732L;
+
+                       public void modifyText(ModifyEvent event) {
+                               callback.setPassword(passwordText.getTextChars());
+                       }
+               });
+               passwordText.addSelectionListener(new SelectionListener() {
+                       private static final long serialVersionUID = 1820530045857665111L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               submit();
+                       }
+               });
+               return passwordText;
+       }
+
+       protected Combo createLocaleHandler(Composite composite,
+                       final LocaleCallback callback) {
+               String[] labels = callback.getSupportedLocalesLabels();
+               if (labels.length == 0)
+                       return null;
+               Label label = new Label(composite, SWT.NONE);
+               label.setText(callback.getPrompt());
+
+               final Combo combo = new Combo(composite, SWT.READ_ONLY);
+               combo.setItems(labels);
+               combo.select(callback.getDefaultIndex());
+               combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               combo.addSelectionListener(new SelectionListener() {
+                       private static final long serialVersionUID = 38678989091946277L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               callback.setSelectedIndex(combo.getSelectionIndex());
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                       }
+               });
+               return combo;
+       }
+
+       protected Label createLabelTextoutputHandler(Composite composite,
+                       final TextOutputCallback callback) {
+               Label label = new Label(composite, SWT.NONE);
+               label.setText(callback.getMessage());
+               GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
+               data.horizontalSpan = 2;
+               label.setLayoutData(data);
+               return label;
+               // TODO: find a way to pass this information
+               // int messageType = callback.getMessageType();
+               // int dialogMessageType = IMessageProvider.NONE;
+               // switch (messageType) {
+               // case TextOutputCallback.INFORMATION:
+               // dialogMessageType = IMessageProvider.INFORMATION;
+               // break;
+               // case TextOutputCallback.WARNING:
+               // dialogMessageType = IMessageProvider.WARNING;
+               // break;
+               // case TextOutputCallback.ERROR:
+               // dialogMessageType = IMessageProvider.ERROR;
+               // break;
+               // }
+               // setMessage(callback.getMessage(), dialogMessageType);
+       }
+
+       synchronized boolean isSubmitted() {
+               return isSubmitted;
+       }
+
+       synchronized boolean isCanceled() {
+               return isCanceled;
+       }
+
+       protected synchronized void submit() {
+               isSubmitted = true;
+               notifyAll();
+       }
+
+       protected synchronized void cancel() {
+               isCanceled = true;
+               notifyAll();
+       }
+}
diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/auth/DefaultLoginDialog.java b/org.argeo.security.ui/src/org/argeo/security/ui/auth/DefaultLoginDialog.java
new file mode 100644 (file)
index 0000000..85f90bd
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.ui.auth;
+
+import javax.security.auth.callback.CallbackHandler;
+
+import org.argeo.security.ui.SecurityUiPlugin;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** Default authentication dialog, to be used as {@link CallbackHandler}. */
+public class DefaultLoginDialog extends AbstractLoginDialog {
+       private static final long serialVersionUID = -8551827590693035734L;
+
+       public DefaultLoginDialog() {
+               this(SecurityUiPlugin.display.get().getActiveShell());
+       }
+
+       public DefaultLoginDialog(Shell parentShell) {
+               super(parentShell);
+       }
+
+       protected Point getInitialSize() {
+               return new Point(350, 180);
+       }
+
+       @Override
+       protected Control createContents(Composite parent) {
+               Control control = super.createContents(parent);
+               parent.pack();
+
+               // Move the dialog to the center of the top level shell.
+               Rectangle shellBounds;
+               if (Display.getCurrent().getActiveShell() != null) // RCP
+                       shellBounds = Display.getCurrent().getActiveShell().getBounds();
+               else
+                       shellBounds = Display.getCurrent().getBounds();// RAP
+               Point dialogSize = parent.getSize();
+               int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
+               int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
+               parent.setLocation(x, y);
+               return control;
+       }
+
+       protected Control createDialogArea(Composite parent) {
+               Composite dialogarea = (Composite) super.createDialogArea(parent);
+               CompositeCallbackHandler composite = new CompositeCallbackHandler(
+                               dialogarea, SWT.NONE);
+               composite.setLayout(new GridLayout(2, false));
+               composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               composite.createCallbackHandlers(getCallbacks());
+               return composite;
+       }
+
+       public void internalHandle() {
+       }
+}
diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/dialogs/AbstractLoginDialog.java b/org.argeo.security.ui/src/org/argeo/security/ui/dialogs/AbstractLoginDialog.java
deleted file mode 100644 (file)
index 8a8d1f6..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ui.dialogs;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.security.ui.SecurityUiPlugin;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.jface.dialogs.IDialogConstants;
-import org.eclipse.jface.dialogs.TrayDialog;
-import org.eclipse.jface.operation.IRunnableWithProgress;
-import org.eclipse.jface.operation.ModalContext;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** Base for login dialogs */
-public abstract class AbstractLoginDialog extends TrayDialog implements
-               CallbackHandler {
-       private static final long serialVersionUID = -8046708963512717709L;
-
-       private final static Log log = LogFactory.getLog(AbstractLoginDialog.class);
-
-       private Thread modalContextThread = null;
-       boolean processCallbacks = false;
-       boolean isCancelled = false;
-       Callback[] callbackArray;
-
-       protected final Callback[] getCallbacks() {
-               return this.callbackArray;
-       }
-
-       public abstract void internalHandle();
-
-       public boolean isCancelled() {
-               return isCancelled;
-       }
-
-       protected AbstractLoginDialog(Shell parentShell) {
-               super(parentShell);
-       }
-
-       /*
-        * (non-Javadoc)
-        * 
-        * @see
-        * javax.security.auth.callback.CallbackHandler#handle(javax.security.auth
-        * .callback.Callback[])
-        */
-       public void handle(final Callback[] callbacks) throws IOException {
-               // clean previous usage
-               if (processCallbacks) {
-                       // this handler was already used
-                       processCallbacks = false;
-               }
-
-               if (modalContextThread != null) {
-                       try {
-                               modalContextThread.join(1000);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-                       modalContextThread = null;
-               }
-
-               // initialize
-               this.callbackArray = callbacks;
-               final Display display = Display.getDefault();
-               display.syncExec(new Runnable() {
-
-                       public void run() {
-                               isCancelled = false;
-                               setBlockOnOpen(false);
-                               open();
-
-                               final Button okButton = getButton(IDialogConstants.OK_ID);
-                               okButton.setText("Login");
-                               okButton.addSelectionListener(new SelectionListener() {
-                                       private static final long serialVersionUID = -200281625679096775L;
-
-                                       public void widgetSelected(final SelectionEvent event) {
-                                               processCallbacks = true;
-                                       }
-
-                                       public void widgetDefaultSelected(final SelectionEvent event) {
-                                               // nothing to do
-                                       }
-                               });
-                               final Button cancel = getButton(IDialogConstants.CANCEL_ID);
-                               cancel.addSelectionListener(new SelectionListener() {
-                                       private static final long serialVersionUID = -3826030278084915815L;
-
-                                       public void widgetSelected(final SelectionEvent event) {
-                                               isCancelled = true;
-                                               processCallbacks = true;
-                                       }
-
-                                       public void widgetDefaultSelected(final SelectionEvent event) {
-                                               // nothing to do
-                                       }
-                               });
-                       }
-               });
-               try {
-                       ModalContext.setAllowReadAndDispatch(true); // Works for now.
-                       ModalContext.run(new IRunnableWithProgress() {
-
-                               public void run(final IProgressMonitor monitor) {
-                                       modalContextThread = Thread.currentThread();
-                                       // Wait here until OK or cancel is pressed, then let it rip.
-                                       // The event
-                                       // listener
-                                       // is responsible for closing the dialog (in the
-                                       // loginSucceeded
-                                       // event).
-                                       while (!processCallbacks && (modalContextThread != null)
-                                                       && (modalContextThread == Thread.currentThread())
-                                                       && SecurityUiPlugin.getBundleContext() != null) {
-                                               // Note: SecurityUiPlugin.getDefault() != null is false
-                                               // when the OSGi runtime is shut down
-                                               try {
-                                                       Thread.sleep(100);
-                                                       // if (display.isDisposed()) {
-                                                       // log.warn("Display is disposed, killing login dialog thread");
-                                                       // throw new ThreadDeath();
-                                                       // }
-                                               } catch (final Exception e) {
-                                                       // do nothing
-                                               }
-                                       }
-                                       processCallbacks = false;
-                                       // Call the adapter to handle the callbacks
-                                       if (!isCancelled())
-                                               internalHandle();
-                                       else
-                                               // clear callbacks are when cancelling
-                                               for (Callback callback : callbacks)
-                                                       if (callback instanceof PasswordCallback)
-                                                               ((PasswordCallback) callback).setPassword(null);
-                                                       else if (callback instanceof NameCallback)
-                                                               ((NameCallback) callback).setName(null);
-                               }
-                       }, true, new NullProgressMonitor(), Display.getDefault());
-               } catch (ThreadDeath e) {
-                       isCancelled = true;
-                       log.debug("Thread " + Thread.currentThread().getId() + " died");
-                       throw e;
-               } catch (Exception e) {
-                       isCancelled = true;
-                       IOException ioe = new IOException(
-                                       "Unexpected issue in login dialog, see root cause for more details");
-                       ioe.initCause(e);
-                       throw ioe;
-               } finally {
-                       // so that the modal thread dies
-                       processCallbacks = true;
-                       // try {
-                       // // wait for the modal context thread to gracefully exit
-                       // modalContextThread.join();
-                       // } catch (InterruptedException ie) {
-                       // // silent
-                       // }
-                       modalContextThread = null;
-               }
-       }
-
-       protected void configureShell(Shell shell) {
-               super.configureShell(shell);
-               shell.setText("Authentication");
-       }
-}
diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/dialogs/DefaultLoginDialog.java b/org.argeo.security.ui/src/org/argeo/security/ui/dialogs/DefaultLoginDialog.java
deleted file mode 100644 (file)
index 3dea95b..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ui.dialogs;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
-
-import org.argeo.security.login.BundleContextCallback;
-import org.argeo.security.ui.SecurityUiPlugin;
-import org.argeo.util.LocaleCallback;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Combo;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** Default authentication dialog, to be used as {@link CallbackHandler}. */
-public class DefaultLoginDialog extends AbstractLoginDialog {
-       private static final long serialVersionUID = -8551827590693035734L;
-
-       public DefaultLoginDialog() {
-               this(SecurityUiPlugin.display.get().getActiveShell());
-       }
-
-       public DefaultLoginDialog(Shell parentShell) {
-               super(parentShell);
-       }
-
-       protected Point getInitialSize() {
-               return new Point(350, 180);
-       }
-
-       @Override
-       protected Control createContents(Composite parent) {
-               Control control = super.createContents(parent);
-               parent.pack();
-
-               // Move the dialog to the center of the top level shell.
-               Rectangle shellBounds;
-               if (Display.getCurrent().getActiveShell() != null) // RCP
-                       shellBounds = Display.getCurrent().getActiveShell().getBounds();
-               else
-                       shellBounds = Display.getCurrent().getBounds();// RAP
-               Point dialogSize = parent.getSize();
-               int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
-               int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
-               parent.setLocation(x, y);
-               return control;
-       }
-
-       protected Control createDialogArea(Composite parent) {
-               Composite dialogarea = (Composite) super.createDialogArea(parent);
-               Composite composite = new Composite(dialogarea, SWT.NONE);
-               composite.setLayout(new GridLayout(2, false));
-               composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               createCallbackHandlers(composite);
-               // parent.pack();
-               return composite;
-       }
-
-       private void createCallbackHandlers(Composite composite) {
-               Callback[] callbacks = getCallbacks();
-               for (int i = 0; i < callbacks.length; i++) {
-                       Callback callback = callbacks[i];
-                       if (callback instanceof TextOutputCallback) {
-                               createLabelTextoutputHandler(composite,
-                                               (TextOutputCallback) callback);
-                       } else if (callback instanceof NameCallback) {
-                               createNameHandler(composite, (NameCallback) callback);
-                       } else if (callback instanceof PasswordCallback) {
-                               createPasswordHandler(composite, (PasswordCallback) callback);
-                       } else if (callback instanceof LocaleCallback) {
-                               createLocaleHandler(composite, (LocaleCallback) callback);
-                       } else if (callback instanceof BundleContextCallback) {
-                               ((BundleContextCallback) callback)
-                                               .setBundleContext(SecurityUiPlugin.getBundleContext());
-                       }
-               }
-       }
-
-       private void createPasswordHandler(Composite composite,
-                       final PasswordCallback callback) {
-               Label label = new Label(composite, SWT.NONE);
-               label.setText(callback.getPrompt());
-               final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD
-                               | SWT.PASSWORD | SWT.BORDER);
-               passwordText
-                               .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               passwordText.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = -7099363995047686732L;
-
-                       public void modifyText(ModifyEvent event) {
-                               // FIXME use getTextChars() in Eclipse 3.7
-                               callback.setPassword(passwordText.getText().toCharArray());
-                       }
-               });
-       }
-
-       private void createLocaleHandler(Composite composite,
-                       final LocaleCallback callback) {
-               String[] labels = callback.getSupportedLocalesLabels();
-               if (labels.length == 0)
-                       return;
-               Label label = new Label(composite, SWT.NONE);
-               label.setText(callback.getPrompt());
-
-               final Combo combo = new Combo(composite, SWT.READ_ONLY);
-               combo.setItems(labels);
-               combo.select(callback.getDefaultIndex());
-               combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               combo.addSelectionListener(new SelectionListener() {
-                       private static final long serialVersionUID = 38678989091946277L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               callback.setSelectedIndex(combo.getSelectionIndex());
-                       }
-
-                       @Override
-                       public void widgetDefaultSelected(SelectionEvent e) {
-                       }
-               });
-       }
-
-       private void createNameHandler(Composite composite,
-                       final NameCallback callback) {
-               Label label = new Label(composite, SWT.NONE);
-               label.setText(callback.getPrompt());
-               final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD
-                               | SWT.BORDER);
-               if (callback.getDefaultName() != null) {
-                       // set default value, if provided
-                       text.setText(callback.getDefaultName());
-                       callback.setName(callback.getDefaultName());
-               }
-               text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               text.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = 7300032545287292973L;
-
-                       public void modifyText(ModifyEvent event) {
-                               callback.setName(text.getText());
-                       }
-               });
-       }
-
-       private void createLabelTextoutputHandler(Composite composite,
-                       final TextOutputCallback callback) {
-               Label label = new Label(composite, SWT.NONE);
-               label.setText(callback.getMessage());
-               GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
-               data.horizontalSpan = 2;
-               label.setLayoutData(data);
-               // TODO: find a way to pass this information
-               // int messageType = callback.getMessageType();
-               // int dialogMessageType = IMessageProvider.NONE;
-               // switch (messageType) {
-               // case TextOutputCallback.INFORMATION:
-               // dialogMessageType = IMessageProvider.INFORMATION;
-               // break;
-               // case TextOutputCallback.WARNING:
-               // dialogMessageType = IMessageProvider.WARNING;
-               // break;
-               // case TextOutputCallback.ERROR:
-               // dialogMessageType = IMessageProvider.ERROR;
-               // break;
-               // }
-               // setMessage(callback.getMessage(), dialogMessageType);
-       }
-
-       public void internalHandle() {
-       }
-}
index 5486ff45854ebbfaa28a9166577559ca32164c23..8f4599cd6da1a06e0a4020e3a24bcb3191c39d75 100644 (file)
@@ -19,6 +19,7 @@ import javax.jcr.Node;
 
 import org.argeo.jcr.spring.BeanNodeMapper;
 
+@Deprecated
 public class MapperTest extends AbstractInternalJackrabbitTestCase {
        public void testSimpleObject() throws Exception {
                SimpleObject mySo = new SimpleObject();
index 53a9ff1e2b4e802a6fddeeb3bc2e9f714daf7cae..c1addf1622a873a2f888f828e58a771e151083d3 100644 (file)
@@ -58,6 +58,7 @@ import org.springframework.core.io.ResourceLoader;
  * Wrapper around a Jackrabbit repository which allows to simplify configuration
  * and intercept some actions. It exposes itself as a {@link Repository}.
  */
+@SuppressWarnings("deprecation")
 public class JackrabbitWrapper extends JcrRepositoryWrapper implements
                ResourceLoaderAware {
        private final static Log log = LogFactory.getLog(JackrabbitWrapper.class);
@@ -170,8 +171,8 @@ public class JackrabbitWrapper extends JcrRepositoryWrapper implements
                                        String oldDigest = JcrUtils.checksumFile(dataModel,
                                                        DIGEST_ALGORITHM);
                                        if (oldDigest.equals(newDigest)) {
-                                               if (log.isDebugEnabled())
-                                                       log.debug("Data model " + resUrl
+                                               if (log.isTraceEnabled())
+                                                       log.trace("Data model " + resUrl
                                                                        + " hasn't changed, keeping version "
                                                                        + currentVersion);
                                                return;
@@ -313,8 +314,8 @@ public class JackrabbitWrapper extends JcrRepositoryWrapper implements
                        resUrl = resUrl.substring(1);
                String pkg = resUrl.substring(0, resUrl.lastIndexOf('/')).replace('/',
                                '.');
-               ServiceReference paSr = bundleContext
-                               .getServiceReference(PackageAdmin.class.getName());
+               ServiceReference<PackageAdmin> paSr = bundleContext
+                               .getServiceReference(PackageAdmin.class);
                PackageAdmin packageAdmin = (PackageAdmin) bundleContext
                                .getService(paSr);
 
index 2176e757cacac3141eb2e7c90a39302e085e0a8b..3b546684a544aba79d81757d169a318294a27ab8 100644 (file)
@@ -1415,7 +1415,6 @@ public class JcrUtils implements ArgeoJcrConstants {
         *            files
         * @return how many files were copied
         */
-       @SuppressWarnings("resource")
        public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive,
                        ArgeoMonitor monitor) {
                long count = 0l;