From: Mathieu Baudier Date: Sat, 14 Feb 2015 13:31:59 +0000 (+0000) Subject: Move security model to CMS X-Git-Tag: argeo-commons-2.1.30~357 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=6ddb7b6b224a00344a182761e42b2241a721224f;p=lgpl%2Fargeo-commons.git Move security model to CMS git-svn-id: https://svn.argeo.org/commons/trunk@7868 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/demo/log4j.properties b/demo/log4j.properties index 13f949ff5..53e2c2f19 100644 --- a/demo/log4j.properties +++ b/demo/log4j.properties @@ -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. diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index bdcfd61d0..711fe437a 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -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 index 000000000..3a536672a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/ArgeoLoginContext.java @@ -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 currentContextClassLoader = new ThreadLocal() { + @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 index 000000000..731cdd170 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/LoginCanceledException.java @@ -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 index 000000000..36d5e0fef --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/AbstractLoginModule.java @@ -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; + + protected abstract Authentication processLogin( + CallbackHandler callbackHandler) throws LoginException, + UnsupportedCallbackException, IOException, InterruptedException; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map 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 index 000000000..6078b8f2b --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/AnonymousLoginModule.java @@ -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 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 index 000000000..7ea9c0737 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java @@ -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 index 000000000..de2a007de --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/EndUserLoginModule.java @@ -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 index 000000000..a0622da3b --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/GrantedAuthorityPrincipal.java @@ -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 index 000000000..61ed7ba35 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/OsJcrAuthenticationProvider.java @@ -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 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 index 000000000..b9ebaf761 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteJcrAuthenticationProvider.java @@ -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 serviceProperties = new Hashtable(); + 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 authoritiesList = new ArrayList(); + 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 index 000000000..5e8587d25 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/SystemLoginModule.java @@ -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); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java index 4d166eaf6..5ec9f5087 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java @@ -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; } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java index d0aec2023..13f48f369 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java @@ -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(); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg index 55194eab8..cc1a07499 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg @@ -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; }; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/JcrUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/JcrUserAdmin.java index 94051d92e..ecaf5e0af 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/useradmin/JcrUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/JcrUserAdmin.java @@ -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 index 000000000..4ad2ad16d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/OsJcrUserAdminService.java @@ -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 roles = new ArrayList(); + + // 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); + } + + /** Unsupported */ + public void createUser(UserDetails user) { + throw new UnsupportedOperationException(); + } + + /** Does nothing */ + public void updateUser(UserDetails user) { + + } + + /** Unsupported */ + public void deleteUser(String username) { + throw new UnsupportedOperationException(); + } + + /** Unsupported */ + 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 listUsers() { + Set set = new HashSet(); + set.add(getSPropertyUsername()); + return set; + } + + public Set listUsersInRole(String role) { + Set set = new HashSet(); + set.add(getSPropertyUsername()); + return set; + } + + /** Does nothing */ + public void synchronize() { + } + + /** Unsupported */ + public void newRole(String role) { + roles.add(role); + } + + public Set listEditableRoles() { + return new HashSet(roles); + } + + /** Unsupported */ + 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 index 000000000..029719c3a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/SimpleJcrSecurityModel.java @@ -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 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 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 index 000000000..de7f72466 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitSecurityModel.java @@ -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 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 roles) + throws RepositoryException { + List userGroupIds = new ArrayList(); + 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 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 index 000000000..f846e1c74 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/JackrabbitUserAdminService.java @@ -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 roles = new ArrayList(); + for (GrantedAuthority ga : userDetails.getAuthorities()) { + if (ga.getAuthority().equals(userRole)) + continue; + roles.add(ga.getAuthority()); + } + + for (Iterator 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 listUsers() { + LinkedHashSet res = new LinkedHashSet(); + try { + Iterator 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 listUsersInRole(String role) { + LinkedHashSet res = new LinkedHashSet(); + try { + Group group = (Group) getUserManager().getAuthorizable(role); + Iterator 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 listEditableRoles() { + LinkedHashSet res = new LinkedHashSet(); + try { + Iterator 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 authorities = new ArrayList(); + // FIXME make it more generic + authorities.add(new GrantedAuthorityPrincipal("ROLE_USER")); + Iterator 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 index 000000000..dcb139939 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/jackrabbit/ScopedSessionProvider.java @@ -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() : ""; + } + + 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 index 000000000..a1d25e92c --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoLdapShaPasswordEncoder.java @@ -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 index 000000000..4381fa991 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoLdapUserDetailsManager.java @@ -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 listUsers() { + return userAdminDao.listUsers(); + } + + public Set listUsersInRole(String role) { + Set lst = new TreeSet( + userAdminDao.listUsersInRole(role)); + Iterator it = lst.iterator(); + while (it.hasNext()) { + if (it.next().equals(superUsername)) { + it.remove(); + break; + } + } + return lst; + } + + public List listUserRoles(String username) { + UserDetails userDetails = loadUserByUsername(username); + List roles = new ArrayList(); + for (GrantedAuthority ga : userDetails.getAuthorities()) { + roles.add(ga.getAuthority()); + } + return Collections.unmodifiableList(roles); + } + + public Set 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 index 000000000..faead2e76 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/ArgeoUserAdminDaoLdap.java @@ -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 listUsers() { + List usernames = (List) 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(usernames)); + } + + @SuppressWarnings("unchecked") + public Set listEditableRoles() { + return Collections.unmodifiableSortedSet(new TreeSet( + 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 listUsersInRole(String role) { + return (Set) ldapTemplate.lookup( + buildGroupDn(convertRoleToGroup(role)), new ContextMapper() { + public Object mapFromContext(Object ctxArg) { + DirContextAdapter ctx = (DirContextAdapter) ctxArg; + String[] userDns = ctx + .getStringAttributes(groupMemberAttribute); + TreeSet set = new TreeSet(); + 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 index 000000000..de28c7f49 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/JcrLdapSynchronizer.java @@ -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 + * this + */ + // 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 propertyToAttributes = new HashMap(); + + 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 userPaths = (List) 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 authorities = new ArrayList(); + 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 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 modifications = new HashMap(); + 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 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 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> modifications = new HashMap>(); + // 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()); + // 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()); + // String value = userProfile.getProperty( + // propertyName).getString(); + // ModificationItem mi = jcrToLdap(propertyName, + // value); + // if (mi != null) + // modifications.get(name).add(mi); + // } + // } + // } catch (RepositoryException e) { + // throw new ArgeoException("Cannot process event " + // + event, e); + // } + // } + // + // for (Name name : modifications.keySet()) { + // List userModifs = modifications.get(name); + // int modifCount = userModifs.size(); + // ldapTemplate.modifyAttributes(name, userModifs + // .toArray(new ModificationItem[modifCount])); + // if (log.isDebugEnabled()) + // log.debug("Mapped " + modifCount + " JCR modification" + // + (modifCount == 1 ? "" : "s") + " to " + name); + // } + // } catch (Exception e) { + // // if (log.isDebugEnabled()) + // // e.printStackTrace(); + // throw new ArgeoException("Cannot process JCR events (" + // + e.getMessage() + ")", e); + // } + // } + // + // } +} diff --git a/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 index 000000000..acfcebc11 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/useradmin/ldap/JcrUserDetailsContextMapper.java @@ -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 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/bnd.bnd b/org.argeo.security.core/bnd.bnd index fa20c3d9e..9124816cb 100644 --- a/org.argeo.security.core/bnd.bnd +++ b/org.argeo.security.core/bnd.bnd @@ -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,\ * diff --git a/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java b/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java index 8c6715446..2d453d3da 100644 --- a/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java +++ b/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java @@ -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(); diff --git a/org.argeo.security.core/src/org/argeo/security/core/AbstractSystemExecution.java b/org.argeo.security.core/src/org/argeo/security/core/AbstractSystemExecution.java index 0d075c3a6..bdd110da9 100644 --- a/org.argeo.security.core/src/org/argeo/security/core/AbstractSystemExecution.java +++ b/org.argeo.security.core/src/org/argeo/security/core/AbstractSystemExecution.java @@ -15,17 +15,11 @@ */ 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 authenticatedBySelf = new ThreadLocal() { @@ -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; - } - } diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/JcrSecurityModel.java b/org.argeo.security.core/src/org/argeo/security/jcr/JcrSecurityModel.java index e9ab89c2a..a151c7f30 100644 --- a/org.argeo.security.core/src/org/argeo/security/jcr/JcrSecurityModel.java +++ b/org.argeo.security.core/src/org/argeo/security/jcr/JcrSecurityModel.java @@ -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 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 index 7125604c7..000000000 --- a/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrAuthenticationProvider.java +++ /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 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 index 1eab37048..000000000 --- a/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrUserAdminService.java +++ /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 roles = new ArrayList(); - - // 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); - } - - /** Unsupported */ - public void createUser(UserDetails user) { - throw new UnsupportedOperationException(); - } - - /** Does nothing */ - public void updateUser(UserDetails user) { - - } - - /** Unsupported */ - public void deleteUser(String username) { - throw new UnsupportedOperationException(); - } - - /** Unsupported */ - 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 listUsers() { - Set set = new HashSet(); - set.add(getSPropertyUsername()); - return set; - } - - public Set listUsersInRole(String role) { - Set set = new HashSet(); - set.add(getSPropertyUsername()); - return set; - } - - /** Does nothing */ - public void synchronize() { - } - - /** Unsupported */ - public void newRole(String role) { - roles.add(role); - } - - public Set listEditableRoles() { - return new HashSet(roles); - } - - /** Unsupported */ - 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 index 154107576..000000000 --- a/org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java +++ /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 serviceProperties = new Hashtable(); - 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 authoritiesList = new ArrayList(); - 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.security.core/src/org/argeo/security/jcr/SecureThreadBoundSession.java b/org.argeo.security.core/src/org/argeo/security/jcr/SecureThreadBoundSession.java index 7ef15947f..e0e887a3e 100644 --- a/org.argeo.security.core/src/org/argeo/security/jcr/SecureThreadBoundSession.java +++ b/org.argeo.security.core/src/org/argeo/security/jcr/SecureThreadBoundSession.java @@ -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 index fc0158738..000000000 --- a/org.argeo.security.core/src/org/argeo/security/jcr/SimpleJcrSecurityModel.java +++ /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 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 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 index e52e6c6cb..000000000 --- a/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/JackrabbitSecurityModel.java +++ /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 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 roles) - throws RepositoryException { - List userGroupIds = new ArrayList(); - 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 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 index aceb51612..000000000 --- a/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/JackrabbitUserAdminService.java +++ /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 roles = new ArrayList(); - for (GrantedAuthority ga : userDetails.getAuthorities()) { - if (ga.getAuthority().equals(userRole)) - continue; - roles.add(ga.getAuthority()); - } - - for (Iterator 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 listUsers() { - LinkedHashSet res = new LinkedHashSet(); - try { - Iterator 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 listUsersInRole(String role) { - LinkedHashSet res = new LinkedHashSet(); - try { - Group group = (Group) getUserManager().getAuthorizable(role); - Iterator 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 listEditableRoles() { - LinkedHashSet res = new LinkedHashSet(); - try { - Iterator 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 authorities = new ArrayList(); - // FIXME make it more generic - authorities.add(new GrantedAuthorityPrincipal("ROLE_USER")); - Iterator 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 index 635f71ee2..000000000 --- a/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/ScopedSessionProvider.java +++ /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() : ""; - } - - 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 index a2c43a589..000000000 --- a/org.argeo.security.core/src/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java +++ /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 index 89b5aa92d..000000000 --- a/org.argeo.security.core/src/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java +++ /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 listUsers() { - return userAdminDao.listUsers(); - } - - public Set listUsersInRole(String role) { - Set lst = new TreeSet( - userAdminDao.listUsersInRole(role)); - Iterator it = lst.iterator(); - while (it.hasNext()) { - if (it.next().equals(superUsername)) { - it.remove(); - break; - } - } - return lst; - } - - public List listUserRoles(String username) { - UserDetails userDetails = loadUserByUsername(username); - List roles = new ArrayList(); - for (GrantedAuthority ga : userDetails.getAuthorities()) { - roles.add(ga.getAuthority()); - } - return Collections.unmodifiableList(roles); - } - - public Set 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 index 37d2a06a0..000000000 --- a/org.argeo.security.core/src/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java +++ /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 listUsers() { - List usernames = (List) 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(usernames)); - } - - @SuppressWarnings("unchecked") - public Set listEditableRoles() { - return Collections.unmodifiableSortedSet(new TreeSet( - 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 listUsersInRole(String role) { - return (Set) ldapTemplate.lookup( - buildGroupDn(convertRoleToGroup(role)), new ContextMapper() { - public Object mapFromContext(Object ctxArg) { - DirContextAdapter ctx = (DirContextAdapter) ctxArg; - String[] userDns = ctx - .getStringAttributes(groupMemberAttribute); - TreeSet set = new TreeSet(); - 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 index e0519c37c..000000000 --- a/org.argeo.security.core/src/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java +++ /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 - * this - */ - // 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 propertyToAttributes = new HashMap(); - - 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 userPaths = (List) 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 authorities = new ArrayList(); - 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 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 modifications = new HashMap(); - 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 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 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> modifications = new HashMap>(); - // 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()); - // 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()); - // String value = userProfile.getProperty( - // propertyName).getString(); - // ModificationItem mi = jcrToLdap(propertyName, - // value); - // if (mi != null) - // modifications.get(name).add(mi); - // } - // } - // } catch (RepositoryException e) { - // throw new ArgeoException("Cannot process event " - // + event, e); - // } - // } - // - // for (Name name : modifications.keySet()) { - // List userModifs = modifications.get(name); - // int modifCount = userModifs.size(); - // ldapTemplate.modifyAttributes(name, userModifs - // .toArray(new ModificationItem[modifCount])); - // if (log.isDebugEnabled()) - // log.debug("Mapped " + modifCount + " JCR modification" - // + (modifCount == 1 ? "" : "s") + " to " + name); - // } - // } catch (Exception e) { - // // if (log.isDebugEnabled()) - // // e.printStackTrace(); - // throw new ArgeoException("Cannot process JCR events (" - // + e.getMessage() + ")", e); - // } - // } - // - // } -} diff --git a/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 index f63250c31..000000000 --- a/org.argeo.security.core/src/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java +++ /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 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 index 8a09a08e0..000000000 --- a/org.argeo.security.core/src/org/argeo/security/login/AbstractSpringLoginModule.java +++ /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 index 0a1279cae..000000000 --- a/org.argeo.security.core/src/org/argeo/security/login/AnonymousLoginModule.java +++ /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 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 index cf32af55c..000000000 --- a/org.argeo.security.core/src/org/argeo/security/login/BundleContextCallback.java +++ /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 index 3c7f9e844..000000000 --- a/org.argeo.security.core/src/org/argeo/security/login/BundleContextCallbackHandler.java +++ /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 index 4c47ac637..000000000 --- a/org.argeo.security.core/src/org/argeo/security/login/ConsoleCallbackHandler.java +++ /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 index e01e714fd..000000000 --- a/org.argeo.security.core/src/org/argeo/security/login/EndUserLoginModule.java +++ /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 index c176c04bc..000000000 --- a/org.argeo.security.core/src/org/argeo/security/login/GrantedAuthorityPrincipal.java +++ /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 index 5629e2e25..000000000 --- a/org.argeo.security.core/src/org/argeo/security/login/LoginCanceledException.java +++ /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 index 54e80e847..000000000 --- a/org.argeo.security.core/src/org/argeo/security/login/SystemLoginModule.java +++ /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); - } -} diff --git a/org.argeo.security.ui.rap/.classpath b/org.argeo.security.ui.rap/.classpath index d2953a684..f9f1a39d8 100644 --- a/org.argeo.security.ui.rap/.classpath +++ b/org.argeo.security.ui.rap/.classpath @@ -4,6 +4,6 @@ + path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7" /> diff --git a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java index 65657dc66..9b01c5525 100644 --- a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java +++ b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java @@ -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) diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/SecurityUiPlugin.java b/org.argeo.security.ui/src/org/argeo/security/ui/SecurityUiPlugin.java index ad5b36203..a4190d69a 100644 --- a/org.argeo.security.ui/src/org/argeo/security/ui/SecurityUiPlugin.java +++ b/org.argeo.security.ui/src/org/argeo/security/ui/SecurityUiPlugin.java @@ -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 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 index 000000000..107697e75 --- /dev/null +++ b/org.argeo.security.ui/src/org/argeo/security/ui/auth/AbstractLoginDialog.java @@ -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 index 000000000..a2fc83162 --- /dev/null +++ b/org.argeo.security.ui/src/org/argeo/security/ui/auth/CompositeCallbackHandler.java @@ -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[])}. + *

+ * Supported standard {@link Callback}s are:
+ *

    + *
  • {@link PasswordCallback}
  • + *
  • {@link NameCallback}
  • + *
  • {@link TextOutputCallback}
  • + *
+ *

+ *

+ * Supported Argeo {@link Callback}s are:
+ *

    + *
  • {@link LocaleCallback}
  • + *
+ *

+ */ +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 index 000000000..85f90bd3f --- /dev/null +++ b/org.argeo.security.ui/src/org/argeo/security/ui/auth/DefaultLoginDialog.java @@ -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 index 8a8d1f66d..000000000 --- a/org.argeo.security.ui/src/org/argeo/security/ui/dialogs/AbstractLoginDialog.java +++ /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 index 3dea95bf9..000000000 --- a/org.argeo.security.ui/src/org/argeo/security/ui/dialogs/DefaultLoginDialog.java +++ /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() { - } -} diff --git a/org.argeo.server.jcr/ext/test/org/argeo/jcr/MapperTest.java b/org.argeo.server.jcr/ext/test/org/argeo/jcr/MapperTest.java index 5486ff458..8f4599cd6 100644 --- a/org.argeo.server.jcr/ext/test/org/argeo/jcr/MapperTest.java +++ b/org.argeo.server.jcr/ext/test/org/argeo/jcr/MapperTest.java @@ -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(); diff --git a/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitWrapper.java b/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitWrapper.java index 53a9ff1e2..c1addf162 100644 --- a/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitWrapper.java +++ b/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitWrapper.java @@ -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 paSr = bundleContext + .getServiceReference(PackageAdmin.class); PackageAdmin packageAdmin = (PackageAdmin) bundleContext .getService(paSr); diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/JcrUtils.java b/org.argeo.server.jcr/src/org/argeo/jcr/JcrUtils.java index 2176e757c..3b546684a 100644 --- a/org.argeo.server.jcr/src/org/argeo/jcr/JcrUtils.java +++ b/org.argeo.server.jcr/src/org/argeo/jcr/JcrUtils.java @@ -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;