## 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.
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,\
*
--- /dev/null
+package org.argeo.cms.auth;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+/** Integrates JAAS with the Argeo platform */
+public class ArgeoLoginContext extends LoginContext {
+ private static ThreadLocal<ClassLoader> currentContextClassLoader = new ThreadLocal<ClassLoader>() {
+ @Override
+ protected ClassLoader initialValue() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ @Override
+ public void set(ClassLoader value) {
+ throw new IllegalAccessError("Current class loader is read-only");
+ }
+ };
+
+ public ArgeoLoginContext(String name, Subject subject,
+ CallbackHandler callbackHandler) throws LoginException {
+ super(setContextClassLoaderForName(name), subject, callbackHandler);
+ // reset current context classloader
+ Thread.currentThread().setContextClassLoader(
+ currentContextClassLoader.get());
+ currentContextClassLoader.remove();
+ }
+
+ /**
+ * Set the context classloader
+ *
+ * @return the passed name, in order to chain calls in the constructor
+ */
+ private static String setContextClassLoaderForName(String name) {
+ // store current context class loader;
+ currentContextClassLoader.get();
+ Thread.currentThread().setContextClassLoader(
+ ArgeoLoginContext.class.getClassLoader());
+ return name;
+ }
+
+ @Override
+ public void login() throws LoginException {
+ super.login();
+ }
+
+ @Override
+ public void logout() throws LoginException {
+ super.logout();
+ }
+}
--- /dev/null
+package org.argeo.cms.auth;
+
+import javax.security.auth.login.LoginException;
+
+public class LoginCanceledException extends LoginException {
+ private static final long serialVersionUID = 8289162094013471043L;
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.argeo.ArgeoException;
+import org.argeo.cms.internal.kernel.Activator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/** Login module which caches one subject per thread. */
+public abstract class AbstractLoginModule implements LoginModule {
+ // private final static Log log = LogFactory
+ // .getLog(AbstractSpringLoginModule.class);
+ private CallbackHandler callbackHandler;
+ private Subject subject;
+
+ private Authentication authentication;
+
+ // state
+ private BundleContext bundleContext;
+ private ServiceReference<AuthenticationManager> authenticationManager;
+
+ protected abstract Authentication processLogin(
+ CallbackHandler callbackHandler) throws LoginException,
+ UnsupportedCallbackException, IOException, InterruptedException;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map<String, ?> sharedState, Map<String, ?> options) {
+ this.callbackHandler = callbackHandler;
+ this.subject = subject;
+ this.bundleContext = Activator.getBundleContext();
+ this.authenticationManager = bundleContext
+ .getServiceReference(AuthenticationManager.class);
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ try {
+ // thread already logged in
+ Authentication currentAuth = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (currentAuth != null) {
+ if (subject.getPrincipals(Authentication.class).size() == 0) {
+ throw new LoginException(
+ "Security context set but not Authentication principal");
+ } else {
+ Authentication principal = subject
+ .getPrincipals(Authentication.class).iterator()
+ .next();
+ if (principal != currentAuth)
+ throw new LoginException(
+ "Already authenticated with a different auth");
+ }
+ return true;
+ }
+
+ if (callbackHandler == null)
+ throw new LoginException("No callback handler available");
+
+ authentication = processLogin(callbackHandler);
+ if (authentication != null) {
+ SecurityContextHolder.getContext().setAuthentication(
+ authentication);
+ return true;
+ } else {
+ throw new LoginException("No authentication returned");
+ }
+ } catch (LoginException e) {
+ throw e;
+ } catch (ThreadDeath e) {
+ LoginException le = new LoginException(
+ "Spring Security login thread died");
+ le.initCause(e);
+ throw le;
+ } catch (Exception e) {
+ LoginException le = new LoginException(
+ "Spring Security login failed");
+ le.initCause(e);
+ throw le;
+ }
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ SecurityContextHolder.getContext().setAuthentication(null);
+ return true;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ SecurityContextHolder.getContext().setAuthentication(null);
+ return true;
+ }
+
+ /**
+ * Return the related {@link BundleContext} (never null), or throws an
+ * Exception if the login module was not properly initialised.
+ */
+ protected BundleContext getBundleContext() {
+ if (bundleContext == null)
+ throw new ArgeoException("No bundle context provided");
+ return bundleContext;
+ }
+
+ AuthenticationManager getAuthenticationManager() {
+ BundleContext bc = getBundleContext();
+ assert authenticationManager != null;
+ return bc.getService(authenticationManager);
+ }
+
+ // protected UserAdmin getUserAdmin(BundleContextCallback
+ // bundleContextCallback) {
+ // BundleContext bc = bundleContextCallback.getBundleContext();
+ // return bc.getService(bc.getServiceReference(UserAdmin.class));
+ // }
+
+ protected Subject getSubject() {
+ return subject;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.util.LocaleCallback;
+import org.argeo.util.LocaleUtils;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+/** Login module which caches one subject per thread. */
+public class AnonymousLoginModule extends AbstractLoginModule {
+ private String anonymousRole = "ROLE_ANONYMOUS";
+ /** Comma separated list of locales */
+ private String availableLocales = null;
+
+ @Override
+ protected Authentication processLogin(CallbackHandler callbackHandler)
+ throws LoginException, UnsupportedCallbackException, IOException,
+ InterruptedException {
+ Locale selectedLocale = null;
+ // multi locale
+ if (availableLocales != null && !availableLocales.trim().equals("")) {
+ LocaleCallback localeCallback = new LocaleCallback(availableLocales);
+ callbackHandler.handle(new Callback[] { localeCallback });
+ selectedLocale = localeCallback.getSelectedLocale();
+ } else {
+ callbackHandler.handle(new Callback[] {});
+ }
+
+ List<SimpleGrantedAuthority> authorities = Collections
+ .singletonList(new SimpleGrantedAuthority(anonymousRole));
+ AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken(
+ Activator.getSystemKey(), null, authorities);
+
+ Authentication auth = getAuthenticationManager().authenticate(
+ anonymousToken);
+
+ if (selectedLocale != null)
+ LocaleUtils.threadLocale.set(selectedLocale);
+ return auth;
+ }
+}
--- /dev/null
+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()));
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.util.Collection;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.ArgeoException;
+import org.argeo.cms.internal.useradmin.SimpleJcrSecurityModel;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.security.OsAuthenticationToken;
+import org.argeo.security.SecurityUtils;
+import org.argeo.security.core.OsAuthenticationProvider;
+import org.argeo.security.jcr.JcrSecurityModel;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/** Relies on OS to authenticate and additionally setup JCR */
+public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
+ private Repository repository;
+ private Session nodeSession;
+
+ private UserDetails userDetails;
+ private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
+
+ private final static String JVM_OSUSER = System.getProperty("user.name");
+
+ public void init() {
+ try {
+ nodeSession = repository.login();
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot initialize", e);
+ }
+ }
+
+ public void destroy() {
+ JcrUtils.logoutQuietly(nodeSession);
+ }
+
+ public Authentication authenticate(Authentication authentication)
+ throws AuthenticationException {
+ if (authentication instanceof UsernamePasswordAuthenticationToken) {
+ // deal with remote access to internal server
+ // FIXME very primitive and unsecure at this sSession adminSession
+ // =tage
+ // consider using the keyring for username / password authentication
+ // or certificate
+ UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication;
+ if (!upat.getPrincipal().toString().equals(JVM_OSUSER))
+ throw new BadCredentialsException("Wrong credentials");
+ UsernamePasswordAuthenticationToken authen = new UsernamePasswordAuthenticationToken(
+ authentication.getPrincipal(),
+ authentication.getCredentials(), getBaseAuthorities());
+ authen.setDetails(userDetails);
+ return authen;
+ } else if (authentication instanceof OsAuthenticationToken) {
+ OsAuthenticationToken authen = (OsAuthenticationToken) super
+ .authenticate(authentication);
+ try {
+ // WARNING: at this stage we assume that the java properties
+ // will have the same value
+ Collection<? extends GrantedAuthority> authorities = getBaseAuthorities();
+ String username = JVM_OSUSER;
+ Node userProfile = jcrSecurityModel.sync(nodeSession, username,
+ SecurityUtils.authoritiesToStringList(authorities));
+ JcrUserDetails.checkAccountStatus(userProfile);
+
+ userDetails = new JcrUserDetails(userProfile, authen
+ .getCredentials().toString(), authorities);
+ authen.setDetails(userDetails);
+ return authen;
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(nodeSession);
+ throw new ArgeoException(
+ "Unexpected exception when synchronizing OS and JCR security ",
+ e);
+ }
+ } else {
+ throw new ArgeoException("Unsupported authentication "
+ + authentication.getClass());
+ }
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
+ this.jcrSecurityModel = jcrSecurityModel;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean supports(Class authentication) {
+ return OsAuthenticationToken.class.isAssignableFrom(authentication)
+ || UsernamePasswordAuthenticationToken.class
+ .isAssignableFrom(authentication);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.auth;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoJcrConstants;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.security.NodeAuthenticationToken;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.argeo.security.jcr.RemoteJcrRepositoryWrapper;
+import org.osgi.framework.BundleContext;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+/** Connects to a JCR repository and delegates authentication to it. */
+public class RemoteJcrAuthenticationProvider implements AuthenticationProvider,
+ ArgeoNames {
+ private RepositoryFactory repositoryFactory;
+ private BundleContext bundleContext;
+
+ public final static String ROLE_REMOTE = "ROLE_REMOTE";
+
+ public Authentication authenticate(Authentication authentication)
+ throws AuthenticationException {
+ NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication;
+ String url = siteAuth.getUrl();
+ if (url == null)// TODO? login on own node
+ throw new ArgeoException("No url set in " + siteAuth);
+ Session session;
+
+ Node userProfile;
+ try {
+ SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
+ siteAuth.getCredentials().toString().toCharArray());
+ // get repository
+ Repository repository = new RemoteJcrRepositoryWrapper(
+ repositoryFactory, url, sp);
+ if (bundleContext != null) {
+ Dictionary<String, String> serviceProperties = new Hashtable<String, String>();
+ serviceProperties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS,
+ ArgeoJcrConstants.ALIAS_NODE);
+ serviceProperties
+ .put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url);
+ bundleContext.registerService(Repository.class.getName(),
+ repository, serviceProperties);
+ }
+ // Repository repository = ArgeoJcrUtils.getRepositoryByUri(
+ // repositoryFactory, url);
+ // if (repository == null)
+ // throw new ArgeoException("Cannot connect to " + url);
+
+ session = repository.login(sp, null);
+
+ userProfile = UserJcrUtils.getUserProfile(session, sp.getUserID());
+ JcrUserDetails.checkAccountStatus(userProfile);
+
+ // Node userHome = UserJcrUtils.getUserHome(session);
+ // if (userHome == null ||
+ // !userHome.hasNode(ArgeoNames.ARGEO_PROFILE))
+ // throw new ArgeoException("No profile for user "
+ // + siteAuth.getName() + " in security workspace "
+ // + siteAuth.getSecurityWorkspace() + " of "
+ // + siteAuth.getUrl());
+ // userProfile = userHome.getNode(ArgeoNames.ARGEO_PROFILE);
+ } catch (RepositoryException e) {
+ throw new BadCredentialsException(
+ "Cannot authenticate " + siteAuth, e);
+ }
+
+ try {
+ // Node userHome = UserJcrUtils.getUserHome(session);
+ // retrieve remote roles
+ List<GrantedAuthority> authoritiesList = new ArrayList<GrantedAuthority>();
+ if (userProfile != null
+ && userProfile.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
+ Value[] roles = userProfile.getProperty(
+ ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
+ for (int i = 0; i < roles.length; i++)
+ authoritiesList.add(new SimpleGrantedAuthority(roles[i]
+ .getString()));
+ }
+ authoritiesList.add(new SimpleGrantedAuthority(ROLE_REMOTE));
+
+ // create authenticated objects
+ // GrantedAuthority[] authorities = authoritiesList
+ // .toArray(new GrantedAuthority[authoritiesList.size()]);
+ JcrUserDetails userDetails = new JcrUserDetails(userProfile,
+ siteAuth.getCredentials().toString(), authoritiesList);
+ NodeAuthenticationToken authenticated = new NodeAuthenticationToken(
+ siteAuth, authoritiesList);
+ authenticated.setDetails(userDetails);
+ return authenticated;
+ } catch (RepositoryException e) {
+ throw new ArgeoException(
+ "Unexpected exception when authenticating to " + url, e);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean supports(Class authentication) {
+ return NodeAuthenticationToken.class.isAssignableFrom(authentication);
+ }
+
+ public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
+ this.repositoryFactory = repositoryFactory;
+ }
+
+ public void setBundleContext(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
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();
}
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;
}
}
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;
this.bundleContext = bundleContext;
internalAuth = new InternalAuthenticationProvider(
- SecurityUtils.getStaticKey());
+ Activator.getSystemKey());
anonymousAuth = new AnonymousAuthenticationProvider(
- SecurityUtils.getStaticKey());
+ Activator.getSystemKey());
// user admin
userAdminService = new JackrabbitUserAdminService();
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;
};
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;
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.ArgeoException;
+import org.argeo.cms.internal.auth.OsJcrAuthenticationProvider;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.security.UserAdminService;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+/**
+ * Dummy user service to be used when running as a single OS user (typically
+ * desktop). TODO integrate with JCR user / groups
+ */
+public class OsJcrUserAdminService implements UserAdminService {
+ private Repository repository;
+
+ /** In memory roles provided by applications. */
+ private List<String> roles = new ArrayList<String>();
+
+ // private Session adminSession;
+
+ public void init() {
+ // try {
+ // adminSession = repository.login();
+ // } catch (RepositoryException e) {
+ // throw new ArgeoException("Cannot initialize", e);
+ // }
+ }
+
+ public void destroy() {
+ // JcrUtils.logoutQuietly(adminSession);
+ }
+
+ /** <b>Unsupported</b> */
+ public void createUser(UserDetails user) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** Does nothing */
+ public void updateUser(UserDetails user) {
+
+ }
+
+ /** <b>Unsupported</b> */
+ public void deleteUser(String username) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** <b>Unsupported</b> */
+ public void changePassword(String oldPassword, String newPassword) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean userExists(String username) {
+ if (getSPropertyUsername().equals(username))
+ return true;
+ else
+ return false;
+ }
+
+ public UserDetails loadUserByUsername(String username)
+ throws UsernameNotFoundException, DataAccessException {
+ if (getSPropertyUsername().equals(username)) {
+ UserDetails userDetails;
+ if (repository != null) {
+ Session adminSession = null;
+ try {
+ adminSession = repository.login();
+ Node userProfile = UserJcrUtils.getUserProfile(
+ adminSession, username);
+ userDetails = new JcrUserDetails(userProfile, "",
+ OsJcrAuthenticationProvider.getBaseAuthorities());
+ } catch (RepositoryException e) {
+ throw new ArgeoException(
+ "Cannot retrieve user profile for " + username, e);
+ } finally {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+ } else {
+ userDetails = new User(username, "", true, true, true, true,
+ OsJcrAuthenticationProvider.getBaseAuthorities());
+ }
+ return userDetails;
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected final String getSPropertyUsername() {
+ return System.getProperty("user.name");
+ }
+
+ public Set<String> listUsers() {
+ Set<String> set = new HashSet<String>();
+ set.add(getSPropertyUsername());
+ return set;
+ }
+
+ public Set<String> listUsersInRole(String role) {
+ Set<String> set = new HashSet<String>();
+ set.add(getSPropertyUsername());
+ return set;
+ }
+
+ /** Does nothing */
+ public void synchronize() {
+ }
+
+ /** <b>Unsupported</b> */
+ public void newRole(String role) {
+ roles.add(role);
+ }
+
+ public Set<String> listEditableRoles() {
+ return new HashSet<String>(roles);
+ }
+
+ /** <b>Unsupported</b> */
+ public void deleteRole(String role) {
+ roles.remove(role);
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin;
+
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.VersionManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoJcrConstants;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.security.jcr.JcrSecurityModel;
+
+/**
+ * Manages data expected by the Argeo security model, such as user home and
+ * profile.
+ */
+public class SimpleJcrSecurityModel implements JcrSecurityModel {
+ private final static Log log = LogFactory
+ .getLog(SimpleJcrSecurityModel.class);
+ // ArgeoNames not implemented as interface in order to ease derivation by
+ // Jackrabbit bundles
+
+ /** The home base path. */
+ private String homeBasePath = "/home";
+
+ public synchronized Node sync(Session session, String username,
+ List<String> roles) {
+ // TODO check user name validity (e.g. should not start by ROLE_)
+
+ try {
+ Node userHome = UserJcrUtils.getUserHome(session, username);
+ if (userHome == null) {
+ String homePath = generateUserPath(homeBasePath, username);
+ userHome = JcrUtils.mkdirs(session, homePath);
+ // userHome = JcrUtils.mkfolders(session, homePath);
+ userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
+ userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
+ session.save();
+
+ JcrUtils.clearAccessControList(session, homePath, username);
+ JcrUtils.addPrivilege(session, homePath, username,
+ Privilege.JCR_ALL);
+ } else {
+ // for backward compatibility with pre 1.0 security model
+ if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) {
+ userHome.getNode(ArgeoNames.ARGEO_PROFILE).remove();
+ userHome.getSession().save();
+ }
+ }
+
+ // Remote roles
+ if (roles != null) {
+ // writeRemoteRoles(userHome, roles);
+ }
+
+ Node userProfile = UserJcrUtils.getUserProfile(session, username);
+ if (userProfile == null) {
+ String personPath = generateUserPath(
+ ArgeoJcrConstants.PEOPLE_BASE_PATH, username);
+ Node personBase = JcrUtils.mkdirs(session, personPath);
+ userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE);
+ userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
+ userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
+ userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true);
+ userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED,
+ true);
+ userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED,
+ true);
+ userProfile.setProperty(
+ ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true);
+ session.save();
+
+ JcrUtils.clearAccessControList(session, userProfile.getPath(),
+ username);
+ JcrUtils.addPrivilege(session, userProfile.getPath(), username,
+ Privilege.JCR_READ);
+
+ VersionManager versionManager = session.getWorkspace()
+ .getVersionManager();
+ if (versionManager.isCheckedOut(userProfile.getPath()))
+ versionManager.checkin(userProfile.getPath());
+
+ }
+
+ // Remote roles
+ if (roles != null) {
+ writeRemoteRoles(userProfile, roles);
+ }
+ return userProfile;
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(session);
+ throw new ArgeoException("Cannot sync node security model for "
+ + username, e);
+ }
+ }
+
+ /** Generate path for a new user home */
+ protected String generateUserPath(String base, String username) {
+ int atIndex = username.indexOf('@');
+ if (atIndex > 0) {
+ String domain = username.substring(0, atIndex);
+ String name = username.substring(atIndex + 1);
+ return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/'
+ + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/'
+ + name;
+ } else if (atIndex == 0 || atIndex == (username.length() - 1)) {
+ throw new ArgeoException("Unsupported username " + username);
+ } else {
+ return base + '/' + JcrUtils.firstCharsToPath(username, 2) + '/'
+ + username;
+ }
+ }
+
+ /** Write remote roles used by remote access in the home directory */
+ protected void writeRemoteRoles(Node userHome, List<String> roles)
+ throws RepositoryException {
+ boolean writeRoles = false;
+ if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
+ Value[] remoteRoles = userHome.getProperty(
+ ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
+ if (remoteRoles.length != roles.size())
+ writeRoles = true;
+ else
+ for (int i = 0; i < remoteRoles.length; i++)
+ if (!remoteRoles[i].getString().equals(roles.get(i)))
+ writeRoles = true;
+ } else
+ writeRoles = true;
+
+ if (writeRoles) {
+ userHome.getSession().getWorkspace().getVersionManager()
+ .checkout(userHome.getPath());
+ String[] roleIds = roles.toArray(new String[roles.size()]);
+ userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds);
+ JcrUtils.updateLastModified(userHome);
+ userHome.getSession().save();
+ userHome.getSession().getWorkspace().getVersionManager()
+ .checkin(userHome.getPath());
+ if (log.isDebugEnabled())
+ log.debug("Wrote remote roles " + roles + " for "
+ + userHome.getProperty(ArgeoNames.ARGEO_USER_ID));
+ }
+
+ }
+
+ public void setHomeBasePath(String homeBasePath) {
+ this.homeBasePath = homeBasePath;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.jackrabbit;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.argeo.ArgeoException;
+import org.argeo.cms.internal.useradmin.SimpleJcrSecurityModel;
+import org.argeo.jcr.ArgeoNames;
+
+/** Make sure that user authorizable exists before syncing user directories. */
+public class JackrabbitSecurityModel extends SimpleJcrSecurityModel {
+ private final static Log log = LogFactory
+ .getLog(JackrabbitSecurityModel.class);
+
+ @Override
+ public synchronized Node sync(Session session, String username,
+ List<String> roles) {
+ if (!(session instanceof JackrabbitSession))
+ return super.sync(session, username, roles);
+
+ try {
+ UserManager userManager = ((JackrabbitSession) session)
+ .getUserManager();
+ User user = (User) userManager.getAuthorizable(username);
+ if (user != null) {
+ String principalName = user.getPrincipal().getName();
+ if (!principalName.equals(username)) {
+ log.warn("Jackrabbit principal is '" + principalName
+ + "' but username is '" + username
+ + "'. Recreating...");
+ user.remove();
+ user = userManager.createUser(username, "");
+ }
+ } else {
+ // create new principal
+ user = userManager.createUser(username, "");
+ log.info(username + " added as Jackrabbit user " + user);
+ }
+
+ // generic JCR sync
+ Node userProfile = super.sync(session, username, roles);
+
+ Boolean enabled = userProfile.getProperty(ArgeoNames.ARGEO_ENABLED)
+ .getBoolean();
+ if (enabled && user.isDisabled())
+ user.disable(null);
+ else if (!enabled && !user.isDisabled())
+ user.disable(userProfile.getPath() + " is disabled");
+
+ // Sync Jackrabbit roles
+ if (roles != null)
+ syncRoles(userManager, user, roles);
+
+ return userProfile;
+ } catch (RepositoryException e) {
+ throw new ArgeoException(
+ "Cannot perform Jackrabbit specific operations", e);
+ }
+ }
+
+ /** Make sure Jackrabbit roles are in line with authentication */
+ void syncRoles(UserManager userManager, User user, List<String> roles)
+ throws RepositoryException {
+ List<String> userGroupIds = new ArrayList<String>();
+ for (String role : roles) {
+ Group group = (Group) userManager.getAuthorizable(role);
+ if (group == null) {
+ group = userManager.createGroup(role);
+ log.info(role + " added as " + group);
+ }
+ if (!group.isMember(user))
+ group.addMember(user);
+ userGroupIds.add(role);
+ }
+
+ // check if user has not been removed from some groups
+ for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
+ Group group = it.next();
+ if (!userGroupIds.contains(group.getID()))
+ group.removeMember(user);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.internal.useradmin.jackrabbit;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.security.authentication.CryptedSimpleCredentials;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.security.NodeAuthenticationToken;
+import org.argeo.security.UserAdminService;
+import org.argeo.cms.internal.auth.GrantedAuthorityPrincipal;
+import org.argeo.security.jcr.JcrSecurityModel;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+/**
+ * An implementation of {@link UserAdminService} which closely wraps Jackrabbits
+ * implementation. Roles are implemented with Groups.
+ */
+public class JackrabbitUserAdminService implements UserAdminService,
+ AuthenticationProvider {
+ final static String userRole = "ROLE_USER";
+ final static String adminRole = "ROLE_ADMIN";
+
+ private Repository repository;
+ private JcrSecurityModel securityModel;
+
+ private JackrabbitSession adminSession = null;
+
+ private String superUsername = "root";
+ private String superUserInitialPassword = "demo";
+
+ public void init() throws RepositoryException {
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ authentication.getName();
+ adminSession = (JackrabbitSession) repository.login();
+ Authorizable adminGroup = getUserManager().getAuthorizable(adminRole);
+ if (adminGroup == null) {
+ adminGroup = getUserManager().createGroup(adminRole);
+ adminSession.save();
+ }
+ Authorizable superUser = getUserManager()
+ .getAuthorizable(superUsername);
+ if (superUser == null) {
+ superUser = getUserManager().createUser(superUsername,
+ superUserInitialPassword);
+ ((Group) adminGroup).addMember(superUser);
+ securityModel.sync(adminSession, superUsername, null);
+ adminSession.save();
+ }
+ }
+
+ public void destroy() throws RepositoryException {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+
+ private UserManager getUserManager() throws RepositoryException {
+ return adminSession.getUserManager();
+ }
+
+ @Override
+ public void createUser(UserDetails user) {
+ try {
+ // FIXME workaround for issue in new user wizard where
+ // security model is hardcoded and it already exists
+ if (getUserManager().getAuthorizable(user.getUsername()) == null) {
+ getUserManager().createUser(user.getUsername(),
+ user.getPassword());
+ securityModel.sync(adminSession, user.getUsername(), null);
+ }
+ updateUser(user);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot create user " + user, e);
+ }
+ }
+
+ @Override
+ public void updateUser(UserDetails userDetails) {
+ try {
+ User user = (User) getUserManager().getAuthorizable(
+ userDetails.getUsername());
+ if (user == null)
+ throw new ArgeoException("No user " + userDetails.getUsername());
+
+ // new password
+ String newPassword = userDetails.getPassword();
+ if (!newPassword.trim().equals("")) {
+ SimpleCredentials sp = new SimpleCredentials(
+ userDetails.getUsername(), newPassword.toCharArray());
+ CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
+ .getCredentials();
+ if (!credentials.matches(sp))
+ user.changePassword(new String(newPassword));
+ }
+
+ List<String> roles = new ArrayList<String>();
+ for (GrantedAuthority ga : userDetails.getAuthorities()) {
+ if (ga.getAuthority().equals(userRole))
+ continue;
+ roles.add(ga.getAuthority());
+ }
+
+ for (Iterator<Group> it = user.memberOf(); it.hasNext();) {
+ Group group = it.next();
+ if (roles.contains(group.getPrincipal().getName()))
+ roles.remove(group.getPrincipal().getName());
+ else
+ group.removeMember(user);
+ }
+
+ // remaining (new ones)
+ for (String role : roles) {
+ Group group = (Group) getUserManager().getAuthorizable(role);
+ if (group == null)
+ throw new ArgeoException("Group " + role
+ + " does not exist,"
+ + " whereas it was granted to user " + userDetails);
+ group.addMember(user);
+ }
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot update user details", e);
+ }
+
+ }
+
+ @Override
+ public void deleteUser(String username) {
+ try {
+ getUserManager().getAuthorizable(username).remove();
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot remove user " + username, e);
+ }
+ }
+
+ @Override
+ public void changePassword(String oldPassword, String newPassword) {
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ String username = authentication.getName();
+ try {
+ SimpleCredentials sp = new SimpleCredentials(username,
+ oldPassword.toCharArray());
+ User user = (User) getUserManager().getAuthorizable(username);
+ CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
+ .getCredentials();
+ if (credentials.matches(sp))
+ user.changePassword(newPassword);
+ else
+ throw new BadCredentialsException("Bad credentials provided");
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot change password for user "
+ + username, e);
+ }
+ }
+
+ @Override
+ public boolean userExists(String username) {
+ try {
+ Authorizable authorizable = getUserManager().getAuthorizable(
+ username);
+ if (authorizable != null && authorizable instanceof User)
+ return true;
+ return false;
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot check whether user " + username
+ + " exists ", e);
+ }
+ }
+
+ @Override
+ public Set<String> listUsers() {
+ LinkedHashSet<String> res = new LinkedHashSet<String>();
+ try {
+ Iterator<Authorizable> users = getUserManager().findAuthorizables(
+ "rep:principalName", null, UserManager.SEARCH_TYPE_USER);
+ while (users.hasNext()) {
+ res.add(users.next().getPrincipal().getName());
+ }
+ return res;
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot list users", e);
+ }
+ }
+
+ @Override
+ public Set<String> listUsersInRole(String role) {
+ LinkedHashSet<String> res = new LinkedHashSet<String>();
+ try {
+ Group group = (Group) getUserManager().getAuthorizable(role);
+ Iterator<Authorizable> users = group.getMembers();
+ // NB: not recursive
+ while (users.hasNext()) {
+ res.add(users.next().getPrincipal().getName());
+ }
+ return res;
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot list users in role " + role, e);
+ }
+ }
+
+ @Override
+ public void synchronize() {
+ }
+
+ @Override
+ public void newRole(String role) {
+ try {
+ getUserManager().createGroup(role);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot create role " + role, e);
+ }
+ }
+
+ @Override
+ public Set<String> listEditableRoles() {
+ LinkedHashSet<String> res = new LinkedHashSet<String>();
+ try {
+ Iterator<Authorizable> groups = getUserManager().findAuthorizables(
+ "rep:principalName", null, UserManager.SEARCH_TYPE_GROUP);
+ while (groups.hasNext()) {
+ res.add(groups.next().getPrincipal().getName());
+ }
+ return res;
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot list groups", e);
+ }
+ }
+
+ @Override
+ public void deleteRole(String role) {
+ try {
+ getUserManager().getAuthorizable(role).remove();
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot remove role " + role, e);
+ }
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username)
+ throws UsernameNotFoundException, DataAccessException {
+ try {
+ User user = (User) getUserManager().getAuthorizable(username);
+ if (user == null)
+ throw new UsernameNotFoundException("User " + username
+ + " cannot be found");
+ return loadJcrUserDetails(adminSession, username);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot load user " + username, e);
+ }
+ }
+
+ protected JcrUserDetails loadJcrUserDetails(Session session, String username)
+ throws RepositoryException {
+ if (username == null)
+ username = session.getUserID();
+ User user = (User) getUserManager().getAuthorizable(username);
+ ArrayList<GrantedAuthorityPrincipal> authorities = new ArrayList<GrantedAuthorityPrincipal>();
+ // FIXME make it more generic
+ authorities.add(new GrantedAuthorityPrincipal("ROLE_USER"));
+ Iterator<Group> groups = user.declaredMemberOf();
+ while (groups.hasNext()) {
+ Group group = groups.next();
+ // String role = "ROLE_"
+ // + group.getPrincipal().getName().toUpperCase();
+ String role = group.getPrincipal().getName();
+ authorities.add(new GrantedAuthorityPrincipal(role));
+ }
+
+ Node userProfile = UserJcrUtils.getUserProfile(session, username);
+ JcrUserDetails userDetails = new JcrUserDetails(userProfile, "",
+ authorities);
+ return userDetails;
+ }
+
+ // AUTHENTICATION PROVIDER
+ public synchronized Authentication authenticate(
+ Authentication authentication) throws AuthenticationException {
+ NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication;
+ String username = siteAuth.getName();
+ if (!(siteAuth.getCredentials() instanceof char[]))
+ throw new ArgeoException("Only char array passwords are supported");
+ char[] password = (char[]) siteAuth.getCredentials();
+ try {
+ SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
+ password);
+ User user = (User) getUserManager().getAuthorizable(username);
+ if (user == null)
+ throw new BadCredentialsException("Bad credentials");
+ CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
+ .getCredentials();
+ // String providedPassword = siteAuth.getCredentials().toString();
+ if (!credentials.matches(sp))
+ throw new BadCredentialsException("Bad credentials");
+
+ // session = repository.login(sp, null);
+
+ Node userProfile = UserJcrUtils.getUserProfile(adminSession,
+ username);
+ JcrUserDetails.checkAccountStatus(userProfile);
+ } catch (BadCredentialsException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new BadCredentialsException(
+ "Cannot authenticate " + siteAuth, e);
+ } finally {
+ Arrays.fill(password, '*');
+ }
+
+ try {
+ JcrUserDetails userDetails = loadJcrUserDetails(adminSession,
+ username);
+ NodeAuthenticationToken authenticated = new NodeAuthenticationToken(
+ siteAuth, userDetails.getAuthorities());
+ authenticated.setDetails(userDetails);
+ return authenticated;
+ } catch (RepositoryException e) {
+ throw new ArgeoException(
+ "Unexpected exception when authenticating " + siteAuth, e);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean supports(Class authentication) {
+ return UsernamePasswordAuthenticationToken.class
+ .isAssignableFrom(authentication);
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setSecurityModel(JcrSecurityModel securityModel) {
+ this.securityModel = securityModel;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.jackrabbit;
+
+import java.io.Serializable;
+
+import javax.jcr.LoginException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.server.SessionProvider;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoJcrConstants;
+import org.argeo.jcr.JcrUtils;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/**
+ * Session provider assuming a single workspace and a short life cycle,
+ * typically a Spring bean of scope (web) 'session'.
+ */
+public class ScopedSessionProvider implements SessionProvider, Serializable {
+ private static final long serialVersionUID = 6589775984177317058L;
+ private static final Log log = LogFactory
+ .getLog(ScopedSessionProvider.class);
+ private transient HttpSession httpSession = null;
+ private transient Session jcrSession = null;
+
+ private transient String currentRepositoryName = null;
+ private transient String currentWorkspaceName = null;
+ private transient String currentJcrUser = null;
+
+ // private transient String anonymousUserId = "anonymous";
+
+ public Session getSession(HttpServletRequest request, Repository rep,
+ String workspace) throws LoginException, ServletException,
+ RepositoryException {
+
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (authentication == null)
+ throw new ArgeoException(
+ "Request not authenticated by Spring Security");
+ String springUser = authentication.getName();
+
+ // HTTP
+ String requestJcrRepository = (String) request
+ .getAttribute(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
+
+ // HTTP session
+ if (httpSession != null
+ && !httpSession.getId().equals(request.getSession().getId()))
+ throw new ArgeoException(
+ "Only session scope is supported in this mode");
+ if (httpSession == null)
+ httpSession = request.getSession();
+
+ // Initializes current values
+ if (currentRepositoryName == null)
+ currentRepositoryName = requestJcrRepository;
+ if (currentWorkspaceName == null)
+ currentWorkspaceName = workspace;
+ if (currentJcrUser == null)
+ currentJcrUser = springUser;
+
+ // logout if there was a change in session coordinates
+ if (jcrSession != null)
+ if (!currentRepositoryName.equals(requestJcrRepository)) {
+ if (log.isDebugEnabled())
+ log.debug(getHttpSessionId() + " Changed from repository '"
+ + currentRepositoryName + "' to '"
+ + requestJcrRepository
+ + "', logging out cached JCR session.");
+ logout();
+ } else if (!currentWorkspaceName.equals(workspace)) {
+ if (log.isDebugEnabled())
+ log.debug(getHttpSessionId() + " Changed from workspace '"
+ + currentWorkspaceName + "' to '" + workspace
+ + "', logging out cached JCR session.");
+ logout();
+ } else if (!currentJcrUser.equals(springUser)) {
+ if (log.isDebugEnabled())
+ log.debug(getHttpSessionId() + " Changed from user '"
+ + currentJcrUser + "' to '" + springUser
+ + "', logging out cached JCR session.");
+ logout();
+ }
+
+ // login if needed
+ if (jcrSession == null)
+ try {
+ Session session = login(rep, workspace);
+ if (!session.getUserID().equals(springUser)) {
+ JcrUtils.logoutQuietly(session);
+ throw new ArgeoException("Spring Security user '"
+ + springUser + "' not in line with JCR user '"
+ + session.getUserID() + "'");
+ }
+ currentRepositoryName = requestJcrRepository;
+ // do not use workspace variable which may be null
+ currentWorkspaceName = session.getWorkspace().getName();
+ currentJcrUser = session.getUserID();
+
+ jcrSession = session;
+ return jcrSession;
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot open session to workspace "
+ + workspace, e);
+ }
+
+ // returns cached session
+ return jcrSession;
+ }
+
+ protected Session login(Repository repository, String workspace)
+ throws RepositoryException {
+ Session session = repository.login(workspace);
+ if (log.isDebugEnabled())
+ log.debug(getHttpSessionId() + " User '" + session.getUserID()
+ + "' logged in workspace '"
+ + session.getWorkspace().getName() + "' of repository '"
+ + currentRepositoryName + "'");
+ return session;
+ }
+
+ public void releaseSession(Session session) {
+ if (log.isTraceEnabled())
+ log.trace(getHttpSessionId() + " Releasing JCR session " + session);
+ }
+
+ protected void logout() {
+ JcrUtils.logoutQuietly(jcrSession);
+ jcrSession = null;
+ }
+
+ protected final String getHttpSessionId() {
+ return httpSession != null ? httpSession.getId() : "<null>";
+ }
+
+ public void init() {
+ }
+
+ public void destroy() {
+ logout();
+ if (getHttpSessionId() != null)
+ if (log.isDebugEnabled())
+ log.debug(getHttpSessionId()
+ + " Cleaned up provider for web session ");
+ httpSession = null;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.ldap;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.argeo.ArgeoException;
+import org.argeo.security.UserAdminService;
+import org.springframework.ldap.core.ContextSource;
+import org.springframework.security.authentication.encoding.PasswordEncoder;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.ldap.userdetails.LdapUserDetailsManager;
+
+/** Extends {@link LdapUserDetailsManager} by adding password encoding support. */
+@SuppressWarnings("deprecation")
+public class ArgeoLdapUserDetailsManager extends LdapUserDetailsManager
+ implements UserAdminService {
+ private String superUsername = "root";
+ private ArgeoUserAdminDaoLdap userAdminDao;
+ private PasswordEncoder passwordEncoder;
+ private final Random random;
+
+ public ArgeoLdapUserDetailsManager(ContextSource contextSource) {
+ super(contextSource);
+ this.random = createRandom();
+ }
+
+ private static Random createRandom() {
+ try {
+ return SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ return new Random(System.currentTimeMillis());
+ }
+ }
+
+ @Override
+ public void changePassword(String oldPassword, String newPassword) {
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (authentication == null)
+ throw new ArgeoException(
+ "Cannot change password without authentication");
+ String username = authentication.getName();
+ UserDetails userDetails = loadUserByUsername(username);
+ String currentPassword = userDetails.getPassword();
+ if (currentPassword == null)
+ throw new ArgeoException("Cannot access current password");
+ if (!passwordEncoder
+ .isPasswordValid(currentPassword, oldPassword, null))
+ throw new ArgeoException("Old password invalid");
+ // Spring Security LDAP 2.0 is buggy when used with OpenLDAP and called
+ // with oldPassword argument
+ super.changePassword(null, encodePassword(newPassword));
+ }
+
+ public void newRole(String role) {
+ userAdminDao.createRole(role, superUsername);
+ }
+
+ public void synchronize() {
+ for (String username : userAdminDao.listUsers())
+ loadUserByUsername(username);
+ // TODO: find a way to remove from JCR
+ }
+
+ public void deleteRole(String role) {
+ userAdminDao.deleteRole(role);
+ }
+
+ public Set<String> listUsers() {
+ return userAdminDao.listUsers();
+ }
+
+ public Set<String> listUsersInRole(String role) {
+ Set<String> lst = new TreeSet<String>(
+ userAdminDao.listUsersInRole(role));
+ Iterator<String> it = lst.iterator();
+ while (it.hasNext()) {
+ if (it.next().equals(superUsername)) {
+ it.remove();
+ break;
+ }
+ }
+ return lst;
+ }
+
+ public List<String> listUserRoles(String username) {
+ UserDetails userDetails = loadUserByUsername(username);
+ List<String> roles = new ArrayList<String>();
+ for (GrantedAuthority ga : userDetails.getAuthorities()) {
+ roles.add(ga.getAuthority());
+ }
+ return Collections.unmodifiableList(roles);
+ }
+
+ public Set<String> listEditableRoles() {
+ return userAdminDao.listEditableRoles();
+ }
+
+ protected String encodePassword(String password) {
+ if (!password.startsWith("{")) {
+ byte[] salt = new byte[16];
+ random.nextBytes(salt);
+ return passwordEncoder.encodePassword(password, salt);
+ } else {
+ return password;
+ }
+ }
+
+ public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+ this.passwordEncoder = passwordEncoder;
+ }
+
+ public void setSuperUsername(String superUsername) {
+ this.superUsername = superUsername;
+ }
+
+ public void setUserAdminDao(ArgeoUserAdminDaoLdap userAdminDao) {
+ this.userAdminDao = userAdminDao;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.ldap;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+
+import org.springframework.ldap.core.ContextExecutor;
+import org.springframework.ldap.core.ContextMapper;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DistinguishedName;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+import org.springframework.security.ldap.LdapUsernameToDnMapper;
+import org.springframework.security.ldap.LdapUtils;
+
+/**
+ * Wraps low-level LDAP operation on user and roles, used by
+ * {@link ArgeoLdapUserDetailsManager}
+ */
+public class ArgeoUserAdminDaoLdap {
+ private String userBase;
+ private String usernameAttribute;
+ private String groupBase;
+ private String[] groupClasses;
+
+ private String groupRoleAttribute;
+ private String groupMemberAttribute;
+ private String defaultRole;
+ private String rolePrefix;
+
+ private final LdapTemplate ldapTemplate;
+ private LdapUsernameToDnMapper usernameMapper;
+
+ /**
+ * Standard constructor, using the LDAP context source shared with Spring
+ * Security components.
+ */
+ public ArgeoUserAdminDaoLdap(BaseLdapPathContextSource contextSource) {
+ this.ldapTemplate = new LdapTemplate(contextSource);
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized Set<String> listUsers() {
+ List<String> usernames = (List<String>) ldapTemplate.listBindings(
+ new DistinguishedName(userBase), new ContextMapper() {
+ public Object mapFromContext(Object ctxArg) {
+ DirContextAdapter ctx = (DirContextAdapter) ctxArg;
+ return ctx.getStringAttribute(usernameAttribute);
+ }
+ });
+
+ return Collections
+ .unmodifiableSortedSet(new TreeSet<String>(usernames));
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<String> listEditableRoles() {
+ return Collections.unmodifiableSortedSet(new TreeSet<String>(
+ ldapTemplate.listBindings(groupBase, new ContextMapper() {
+ public Object mapFromContext(Object ctxArg) {
+ String groupName = ((DirContextAdapter) ctxArg)
+ .getStringAttribute(groupRoleAttribute);
+ String roleName = convertGroupToRole(groupName);
+ return roleName;
+ }
+ })));
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<String> listUsersInRole(String role) {
+ return (Set<String>) ldapTemplate.lookup(
+ buildGroupDn(convertRoleToGroup(role)), new ContextMapper() {
+ public Object mapFromContext(Object ctxArg) {
+ DirContextAdapter ctx = (DirContextAdapter) ctxArg;
+ String[] userDns = ctx
+ .getStringAttributes(groupMemberAttribute);
+ TreeSet<String> set = new TreeSet<String>();
+ for (String userDn : userDns) {
+ DistinguishedName dn = new DistinguishedName(userDn);
+ String username = dn.getValue(usernameAttribute);
+ set.add(username);
+ }
+ return Collections.unmodifiableSortedSet(set);
+ }
+ });
+ }
+
+ public void createRole(String role, final String superuserName) {
+ String group = convertRoleToGroup(role);
+ DistinguishedName superuserDn = (DistinguishedName) ldapTemplate
+ .executeReadWrite(new ContextExecutor() {
+ public Object executeWithContext(DirContext ctx)
+ throws NamingException {
+ return LdapUtils.getFullDn(
+ usernameMapper.buildDn(superuserName), ctx);
+ }
+ });
+
+ Name groupDn = buildGroupDn(group);
+ DirContextAdapter context = new DirContextAdapter();
+ context.setAttributeValues("objectClass", groupClasses);
+ context.setAttributeValue("cn", group);
+ // Add superuser because cannot create empty group
+ context.setAttributeValue(groupMemberAttribute, superuserDn.toString());
+ ldapTemplate.bind(groupDn, context, null);
+ }
+
+ public void deleteRole(String role) {
+ String group = convertRoleToGroup(role);
+ Name dn = buildGroupDn(group);
+ ldapTemplate.unbind(dn);
+ }
+
+ /** Maps a role (ROLE_XXX) to the related LDAP group (xxx) */
+ protected String convertRoleToGroup(String role) {
+ String group = role;
+ if (group.startsWith(rolePrefix)) {
+ group = group.substring(rolePrefix.length());
+ group = group.toLowerCase();
+ }
+ return group;
+ }
+
+ /** Maps anLDAP group (xxx) to the related role (ROLE_XXX) */
+ protected String convertGroupToRole(String groupName) {
+ groupName = groupName.toUpperCase();
+
+ return rolePrefix + groupName;
+ }
+
+ protected Name buildGroupDn(String name) {
+ return new DistinguishedName(groupRoleAttribute + "=" + name + ","
+ + groupBase);
+ }
+
+ public void setUserBase(String userBase) {
+ this.userBase = userBase;
+ }
+
+ public void setUsernameAttribute(String usernameAttribute) {
+ this.usernameAttribute = usernameAttribute;
+ }
+
+ public void setGroupBase(String groupBase) {
+ this.groupBase = groupBase;
+ }
+
+ public void setGroupRoleAttribute(String groupRoleAttributeName) {
+ this.groupRoleAttribute = groupRoleAttributeName;
+ }
+
+ public void setGroupMemberAttribute(String groupMemberAttributeName) {
+ this.groupMemberAttribute = groupMemberAttributeName;
+ }
+
+ public void setDefaultRole(String defaultRole) {
+ this.defaultRole = defaultRole;
+ }
+
+ public void setRolePrefix(String rolePrefix) {
+ this.rolePrefix = rolePrefix;
+ }
+
+ public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
+ this.usernameMapper = usernameMapper;
+ }
+
+ public String getDefaultRole() {
+ return defaultRole;
+ }
+
+ public void setGroupClasses(String[] groupClasses) {
+ this.groupClasses = groupClasses;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.ldap;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.SortedSet;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.version.VersionManager;
+import javax.naming.Name;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.cms.internal.useradmin.SimpleJcrSecurityModel;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.security.SecurityUtils;
+import org.argeo.security.jcr.JcrSecurityModel;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.springframework.ldap.core.ContextMapper;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.ldap.core.DistinguishedName;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.security.authentication.encoding.PasswordEncoder;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.ldap.LdapUsernameToDnMapper;
+import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
+
+/** Makes sure that LDAP and JCR are in line. */
+@SuppressWarnings("deprecation")
+public class JcrLdapSynchronizer implements UserDetailsContextMapper,
+ ArgeoNames {
+ private final static Log log = LogFactory.getLog(JcrLdapSynchronizer.class);
+
+ // LDAP
+ private LdapTemplate ldapTemplate;
+ /**
+ * LDAP template whose context source has an object factory set to null. see
+ * <a href=
+ * "http://forum.springsource.org/showthread.php?55955-Persistent-search-with-spring-ldap"
+ * >this</a>
+ */
+ // private LdapTemplate rawLdapTemplate;
+
+ private String userBase;
+ private String usernameAttribute;
+ private String passwordAttribute;
+ private String[] userClasses;
+ // private String defaultUserRole ="ROLE_USER";
+
+ // private NamingListener ldapUserListener;
+ // private SearchControls subTreeSearchControls;
+ private LdapUsernameToDnMapper usernameMapper;
+
+ private PasswordEncoder passwordEncoder;
+ private final Random random;
+
+ // JCR
+ /** Admin session on the main workspace */
+ private Session nodeSession;
+ private Repository repository;
+
+ // private JcrProfileListener jcrProfileListener;
+ private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
+
+ // Mapping
+ private Map<String, String> propertyToAttributes = new HashMap<String, String>();
+
+ public JcrLdapSynchronizer() {
+ random = createRandom();
+ }
+
+ public void init() {
+ try {
+ nodeSession = repository.login();
+
+ // TODO put this in a different thread, and poll the LDAP server
+ // until it is up
+ try {
+ synchronize();
+
+ // LDAP
+ // subTreeSearchControls = new SearchControls();
+ // subTreeSearchControls
+ // .setSearchScope(SearchControls.SUBTREE_SCOPE);
+ // LDAP listener
+ // ldapUserListener = new LdapUserListener();
+ // rawLdapTemplate.executeReadOnly(new ContextExecutor() {
+ // public Object executeWithContext(DirContext ctx)
+ // throws NamingException {
+ // EventDirContext ectx = (EventDirContext) ctx.lookup("");
+ // ectx.addNamingListener(userBase, "("
+ // + usernameAttribute + "=*)",
+ // subTreeSearchControls, ldapUserListener);
+ // return null;
+ // }
+ // });
+ } catch (Exception e) {
+ log.error("Could not synchronize and listen to LDAP,"
+ + " probably because the LDAP server is not available."
+ + " Restart the system as soon as possible.", e);
+ }
+
+ // JCR
+ // String[] nodeTypes = { ArgeoTypes.ARGEO_USER_PROFILE };
+ // jcrProfileListener = new JcrProfileListener();
+ // noLocal is used so that we are not notified when we modify JCR
+ // from LDAP
+ // nodeSession
+ // .getWorkspace()
+ // .getObservationManager()
+ // .addEventListener(jcrProfileListener,
+ // Event.PROPERTY_CHANGED | Event.NODE_ADDED, "/",
+ // true, null, nodeTypes, true);
+ } catch (Exception e) {
+ JcrUtils.logoutQuietly(nodeSession);
+ throw new ArgeoException("Cannot initialize LDAP/JCR synchronizer",
+ e);
+ }
+ }
+
+ public void destroy() {
+ // JcrUtils.removeListenerQuietly(nodeSession, jcrProfileListener);
+ JcrUtils.logoutQuietly(nodeSession);
+ // try {
+ // rawLdapTemplate.executeReadOnly(new ContextExecutor() {
+ // public Object executeWithContext(DirContext ctx)
+ // throws NamingException {
+ // EventDirContext ectx = (EventDirContext) ctx.lookup("");
+ // ectx.removeNamingListener(ldapUserListener);
+ // return null;
+ // }
+ // });
+ // } catch (Exception e) {
+ // // silent (LDAP server may have been shutdown already)
+ // if (log.isTraceEnabled())
+ // log.trace("Cannot remove LDAP listener", e);
+ // }
+ }
+
+ /*
+ * LDAP TO JCR
+ */
+ /** Full synchronization between LDAP and JCR. LDAP has priority. */
+ protected void synchronize() {
+ try {
+ Name userBaseName = new DistinguishedName(userBase);
+ // TODO subtree search?
+ @SuppressWarnings("unchecked")
+ List<String> userPaths = (List<String>) ldapTemplate.listBindings(
+ userBaseName, new ContextMapper() {
+ public Object mapFromContext(Object ctxObj) {
+ try {
+ return mapLdapToJcr((DirContextAdapter) ctxObj);
+ } catch (Exception e) {
+ // do not break process because of error
+ log.error(
+ "Could not LDAP->JCR synchronize user "
+ + ctxObj, e);
+ return null;
+ }
+ }
+ });
+
+ // create accounts which are not in LDAP
+ Query query = nodeSession
+ .getWorkspace()
+ .getQueryManager()
+ .createQuery(
+ "select * from [" + ArgeoTypes.ARGEO_USER_PROFILE
+ + "]", Query.JCR_SQL2);
+ NodeIterator it = query.execute().getNodes();
+ while (it.hasNext()) {
+ Node userProfile = it.nextNode();
+ String path = userProfile.getPath();
+ try {
+ if (!userPaths.contains(path)) {
+ String username = userProfile
+ .getProperty(ARGEO_USER_ID).getString();
+ // GrantedAuthority[] authorities = {new
+ // GrantedAuthorityImpl(defaultUserRole)};
+ List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
+ JcrUserDetails userDetails = new JcrUserDetails(
+ userProfile, username, authorities);
+ String dn = createLdapUser(userDetails);
+ log.warn("Created ldap entry '" + dn + "' for user '"
+ + username + "'");
+
+ // if(!userProfile.getProperty(ARGEO_ENABLED).getBoolean()){
+ // continue profiles;
+ // }
+ //
+ // log.warn("Path "
+ // + path
+ // + " not found in LDAP, disabling user "
+ // + userProfile.getProperty(ArgeoNames.ARGEO_USER_ID)
+ // .getString());
+
+ // Temporary hack to repair previous behaviour
+ if (!userProfile.getProperty(ARGEO_ENABLED)
+ .getBoolean()) {
+ VersionManager versionManager = nodeSession
+ .getWorkspace().getVersionManager();
+ versionManager.checkout(userProfile.getPath());
+ userProfile.setProperty(ArgeoNames.ARGEO_ENABLED,
+ true);
+ nodeSession.save();
+ versionManager.checkin(userProfile.getPath());
+ }
+ }
+ } catch (Exception e) {
+ log.error("Cannot process " + path, e);
+ }
+ }
+ } catch (Exception e) {
+ JcrUtils.discardQuietly(nodeSession);
+ log.error("Cannot synchronize LDAP and JCR", e);
+ // throw new ArgeoException("Cannot synchronize LDAP and JCR", e);
+ }
+ }
+
+ private String createLdapUser(UserDetails user) {
+ DirContextAdapter ctx = new DirContextAdapter();
+ mapUserToContext(user, ctx);
+ DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
+ ldapTemplate.bind(dn, ctx, null);
+ return dn.toString();
+ }
+
+ /** Called during authentication in order to retrieve user details */
+ public UserDetails mapUserFromContext(final DirContextOperations ctx,
+ final String username,
+ Collection<? extends GrantedAuthority> authorities) {
+ if (ctx == null)
+ throw new ArgeoException("No LDAP information for user " + username);
+
+ String ldapUsername = ctx.getStringAttribute(usernameAttribute);
+ if (!ldapUsername.equals(username))
+ throw new ArgeoException("Logged in with username " + username
+ + " but LDAP user is " + ldapUsername);
+
+ Node userProfile = jcrSecurityModel.sync(nodeSession, username,
+ SecurityUtils.authoritiesToStringList(authorities));
+ // JcrUserDetails.checkAccountStatus(userProfile);
+
+ // password
+ SortedSet<?> passwordAttributes = ctx
+ .getAttributeSortedStringSet(passwordAttribute);
+ String password;
+ if (passwordAttributes == null || passwordAttributes.size() == 0) {
+ // throw new ArgeoException("No password found for user " +
+ // username);
+ password = "NULL";
+ } else {
+ byte[] arr = (byte[]) passwordAttributes.first();
+ password = new String(arr);
+ // erase password
+ Arrays.fill(arr, (byte) 0);
+ }
+
+ try {
+ return new JcrUserDetails(userProfile, password, authorities);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot retrieve user details for "
+ + username, e);
+ }
+ }
+
+ /**
+ * Writes an LDAP context to the JCR user profile.
+ *
+ * @return path to user profile
+ */
+ protected synchronized String mapLdapToJcr(DirContextAdapter ctx) {
+ Session session = nodeSession;
+ try {
+ // process
+ String username = ctx.getStringAttribute(usernameAttribute);
+
+ Node userProfile = jcrSecurityModel.sync(session, username, null);
+ Map<String, String> modifications = new HashMap<String, String>();
+ for (String jcrProperty : propertyToAttributes.keySet())
+ ldapToJcr(userProfile, jcrProperty, ctx, modifications);
+
+ int modifCount = modifications.size();
+ if (modifCount > 0) {
+ session.getWorkspace().getVersionManager()
+ .checkout(userProfile.getPath());
+ for (String prop : modifications.keySet())
+ userProfile.setProperty(prop, modifications.get(prop));
+ JcrUtils.updateLastModified(userProfile);
+ session.save();
+ session.getWorkspace().getVersionManager()
+ .checkin(userProfile.getPath());
+ if (log.isDebugEnabled())
+ log.debug("Mapped " + modifCount + " LDAP modification"
+ + (modifCount == 1 ? "" : "s") + " from "
+ + ctx.getDn() + " to " + userProfile);
+ }
+ return userProfile.getPath();
+ } catch (Exception e) {
+ JcrUtils.discardQuietly(session);
+ throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
+ }
+ }
+
+ /** Maps an LDAP property to a JCR property */
+ protected void ldapToJcr(Node userProfile, String jcrProperty,
+ DirContextOperations ctx, Map<String, String> modifications) {
+ // TODO do we really need DirContextOperations?
+ try {
+ String ldapAttribute;
+ if (propertyToAttributes.containsKey(jcrProperty))
+ ldapAttribute = propertyToAttributes.get(jcrProperty);
+ else
+ throw new ArgeoException(
+ "No LDAP attribute mapped for JCR proprty "
+ + jcrProperty);
+
+ String value = ctx.getStringAttribute(ldapAttribute);
+ String jcrValue = userProfile.hasProperty(jcrProperty) ? userProfile
+ .getProperty(jcrProperty).getString() : null;
+ if (value != null && jcrValue != null) {
+ if (!value.equals(jcrValue))
+ modifications.put(jcrProperty, value);
+ } else if (value != null && jcrValue == null) {
+ modifications.put(jcrProperty, value);
+ } else if (value == null && jcrValue != null) {
+ modifications.put(jcrProperty, value);
+ }
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot map JCR property " + jcrProperty
+ + " from LDAP", e);
+ }
+ }
+
+ /*
+ * JCR to LDAP
+ */
+
+ public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
+ if (!(user instanceof JcrUserDetails))
+ throw new ArgeoException("Unsupported user details: "
+ + user.getClass());
+
+ ctx.setAttributeValues("objectClass", userClasses);
+ ctx.setAttributeValue(usernameAttribute, user.getUsername());
+ ctx.setAttributeValue(passwordAttribute,
+ encodePassword(user.getPassword()));
+
+ final JcrUserDetails jcrUserDetails = (JcrUserDetails) user;
+ try {
+ Node userProfile = nodeSession
+ .getNode(jcrUserDetails.getHomePath()).getNode(
+ ARGEO_PROFILE);
+ for (String jcrProperty : propertyToAttributes.keySet()) {
+ if (userProfile.hasProperty(jcrProperty)) {
+ ModificationItem mi = jcrToLdap(jcrProperty, userProfile
+ .getProperty(jcrProperty).getString());
+ if (mi != null)
+ ctx.setAttribute(mi.getAttribute());
+ }
+ }
+ if (log.isTraceEnabled())
+ log.trace("Mapped " + userProfile + " to " + ctx.getDn());
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
+ }
+
+ }
+
+ /** Maps a JCR property to an LDAP property */
+ protected ModificationItem jcrToLdap(String jcrProperty, String value) {
+ // TODO do we really need DirContextOperations?
+ try {
+ String ldapAttribute;
+ if (propertyToAttributes.containsKey(jcrProperty))
+ ldapAttribute = propertyToAttributes.get(jcrProperty);
+ else
+ return null;
+
+ // fix issue with empty 'sn' in LDAP
+ if (ldapAttribute.equals("sn") && (value.trim().equals("")))
+ return null;
+ // fix issue with empty 'description' in LDAP
+ if (ldapAttribute.equals("description") && value.trim().equals(""))
+ return null;
+ BasicAttribute attr = new BasicAttribute(
+ propertyToAttributes.get(jcrProperty), value);
+ ModificationItem mi = new ModificationItem(
+ DirContext.REPLACE_ATTRIBUTE, attr);
+ return mi;
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot map JCR property " + jcrProperty
+ + " from LDAP", e);
+ }
+ }
+
+ /*
+ * UTILITIES
+ */
+ protected String encodePassword(String password) {
+ if (!password.startsWith("{")) {
+ byte[] salt = new byte[16];
+ random.nextBytes(salt);
+ return passwordEncoder.encodePassword(password, salt);
+ } else {
+ return password;
+ }
+ }
+
+ private static Random createRandom() {
+ try {
+ return SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ return new Random(System.currentTimeMillis());
+ }
+ }
+
+ /*
+ * DEPENDENCY INJECTION
+ */
+
+ public void setLdapTemplate(LdapTemplate ldapTemplate) {
+ this.ldapTemplate = ldapTemplate;
+ }
+
+ public void setRawLdapTemplate(LdapTemplate rawLdapTemplate) {
+ // this.rawLdapTemplate = rawLdapTemplate;
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setUserBase(String userBase) {
+ this.userBase = userBase;
+ }
+
+ public void setUsernameAttribute(String usernameAttribute) {
+ this.usernameAttribute = usernameAttribute;
+ }
+
+ public void setPropertyToAttributes(Map<String, String> propertyToAttributes) {
+ this.propertyToAttributes = propertyToAttributes;
+ }
+
+ public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
+ this.usernameMapper = usernameMapper;
+ }
+
+ public void setPasswordAttribute(String passwordAttribute) {
+ this.passwordAttribute = passwordAttribute;
+ }
+
+ public void setUserClasses(String[] userClasses) {
+ this.userClasses = userClasses;
+ }
+
+ public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+ this.passwordEncoder = passwordEncoder;
+ }
+
+ public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
+ this.jcrSecurityModel = jcrSecurityModel;
+ }
+
+ /** Listen to LDAP */
+ // class LdapUserListener implements ObjectChangeListener,
+ // NamespaceChangeListener, UnsolicitedNotificationListener {
+ //
+ // public void namingExceptionThrown(NamingExceptionEvent evt) {
+ // evt.getException().printStackTrace();
+ // }
+ //
+ // public void objectChanged(NamingEvent evt) {
+ // Binding user = evt.getNewBinding();
+ // // TODO find a way not to be called when JCR is the source of the
+ // // modification
+ // DirContextAdapter ctx = (DirContextAdapter) ldapTemplate
+ // .lookup(user.getName());
+ // mapLdapToJcr(ctx);
+ // }
+ //
+ // public void objectAdded(NamingEvent evt) {
+ // Binding user = evt.getNewBinding();
+ // DirContextAdapter ctx = (DirContextAdapter) ldapTemplate
+ // .lookup(user.getName());
+ // mapLdapToJcr(ctx);
+ // }
+ //
+ // public void objectRemoved(NamingEvent evt) {
+ // if (log.isDebugEnabled())
+ // log.debug(evt);
+ // }
+ //
+ // public void objectRenamed(NamingEvent evt) {
+ // if (log.isDebugEnabled())
+ // log.debug(evt);
+ // }
+ //
+ // public void notificationReceived(UnsolicitedNotificationEvent evt) {
+ // UnsolicitedNotification notification = evt.getNotification();
+ // NamingException ne = notification.getException();
+ // String msg = "LDAP notification " + "ID=" + notification.getID()
+ // + ", referrals=" + notification.getReferrals();
+ // if (ne != null) {
+ // if (log.isTraceEnabled())
+ // log.trace(msg + ", exception= " + ne, ne);
+ // else
+ // log.warn(msg + ", exception= " + ne);
+ // } else if (log.isDebugEnabled()) {
+ // log.debug("Unsollicited LDAP notification " + msg);
+ // }
+ // }
+ //
+ // }
+
+ /** Listen to JCR */
+ // class JcrProfileListener implements EventListener {
+ //
+ // public void onEvent(EventIterator events) {
+ // try {
+ // final Map<Name, List<ModificationItem>> modifications = new HashMap<Name,
+ // List<ModificationItem>>();
+ // while (events.hasNext()) {
+ // Event event = events.nextEvent();
+ // try {
+ // if (Event.PROPERTY_CHANGED == event.getType()) {
+ // Property property = (Property) nodeSession
+ // .getItem(event.getPath());
+ // String propertyName = property.getName();
+ // Node userProfile = property.getParent();
+ // String username = userProfile.getProperty(
+ // ARGEO_USER_ID).getString();
+ // if (propertyToAttributes.containsKey(propertyName)) {
+ // Name name = usernameMapper.buildDn(username);
+ // if (!modifications.containsKey(name))
+ // modifications.put(name,
+ // new ArrayList<ModificationItem>());
+ // String value = property.getString();
+ // ModificationItem mi = jcrToLdap(propertyName,
+ // value);
+ // if (mi != null)
+ // modifications.get(name).add(mi);
+ // }
+ // } else if (Event.NODE_ADDED == event.getType()) {
+ // Node userProfile = nodeSession.getNode(event
+ // .getPath());
+ // String username = userProfile.getProperty(
+ // ARGEO_USER_ID).getString();
+ // Name name = usernameMapper.buildDn(username);
+ // for (String propertyName : propertyToAttributes
+ // .keySet()) {
+ // if (!modifications.containsKey(name))
+ // modifications.put(name,
+ // new ArrayList<ModificationItem>());
+ // String value = userProfile.getProperty(
+ // propertyName).getString();
+ // ModificationItem mi = jcrToLdap(propertyName,
+ // value);
+ // if (mi != null)
+ // modifications.get(name).add(mi);
+ // }
+ // }
+ // } catch (RepositoryException e) {
+ // throw new ArgeoException("Cannot process event "
+ // + event, e);
+ // }
+ // }
+ //
+ // for (Name name : modifications.keySet()) {
+ // List<ModificationItem> userModifs = modifications.get(name);
+ // int modifCount = userModifs.size();
+ // ldapTemplate.modifyAttributes(name, userModifs
+ // .toArray(new ModificationItem[modifCount]));
+ // if (log.isDebugEnabled())
+ // log.debug("Mapped " + modifCount + " JCR modification"
+ // + (modifCount == 1 ? "" : "s") + " to " + name);
+ // }
+ // } catch (Exception e) {
+ // // if (log.isDebugEnabled())
+ // // e.printStackTrace();
+ // throw new ArgeoException("Cannot process JCR events ("
+ // + e.getMessage() + ")", e);
+ // }
+ // }
+ //
+ // }
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.internal.useradmin.ldap;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
+
+/** @deprecated Read only mapping from LDAP to user details */
+@Deprecated
+public class JcrUserDetailsContextMapper implements UserDetailsContextMapper,
+ ArgeoNames {
+ /** Admin session on the security workspace */
+ private Session securitySession;
+ private Repository repository;
+ private String securityWorkspace = "security";
+
+ public void init() {
+ try {
+ securitySession = repository.login(securityWorkspace);
+ } catch (RepositoryException e) {
+ JcrUtils.logoutQuietly(securitySession);
+ throw new ArgeoException(
+ "Cannot initialize LDAP/JCR user details context mapper", e);
+ }
+ }
+
+ public void destroy() {
+ JcrUtils.logoutQuietly(securitySession);
+ }
+
+ /** Called during authentication in order to retrieve user details */
+ public UserDetails mapUserFromContext(final DirContextOperations ctx,
+ final String username,
+ Collection<? extends GrantedAuthority> authorities) {
+ if (ctx == null)
+ throw new ArgeoException("No LDAP information for user " + username);
+ Node userHome = UserJcrUtils.getUserHome(securitySession, username);
+ if (userHome == null)
+ throw new ArgeoException("No JCR information for user " + username);
+
+ // password
+ // SortedSet<?> passwordAttributes = ctx
+ // .getAttributeSortedStringSet(passwordAttribute);
+ // String password;
+ // if (passwordAttributes == null || passwordAttributes.size() == 0) {
+ // throw new ArgeoException("No password found for user " + username);
+ // } else {
+ // byte[] arr = (byte[]) passwordAttributes.first();
+ // password = new String(arr);
+ // // erase password
+ // Arrays.fill(arr, (byte) 0);
+ // }
+
+ try {
+ // we don't have access to password, so let's not pretend
+ String password = UUID.randomUUID().toString();
+ return new JcrUserDetails(userHome.getNode(ARGEO_PROFILE),
+ password, authorities);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot retrieve user details for "
+ + username, e);
+ }
+ }
+
+ public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
+ throw new UnsupportedOperationException("LDAP access is read-only");
+ }
+
+}
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,\
*
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;
/** 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();
*/
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;
private final static Log log = LogFactory
.getLog(AbstractSystemExecution.class);
private AuthenticationManager authenticationManager;
- private BundleContext bundleContext;
private String systemAuthenticationKey;
- private String loginContextName = "SYSTEM";
/** Whether the current thread was authenticated by this component. */
private ThreadLocal<Boolean> authenticatedBySelf = new ThreadLocal<Boolean>() {
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");
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;
- }
-
}
* user has a home directory with full access and a profile with information
* about him (read access)
*
- * @return the user profile (whose parent is the user home), never null
+ * @return the user profile, never null
*/
public Node sync(Session session, String username, List<String> roles);
}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr;
-
-import java.util.Collection;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.security.OsAuthenticationToken;
-import org.argeo.security.SecurityUtils;
-import org.argeo.security.core.OsAuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-
-/** Relies on OS to authenticate and additionally setup JCR */
-public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
- private Repository repository;
- private Session nodeSession;
-
- private UserDetails userDetails;
- private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
-
- private final static String JVM_OSUSER = System.getProperty("user.name");
-
- public void init() {
- try {
- nodeSession = repository.login();
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot initialize", e);
- }
- }
-
- public void destroy() {
- JcrUtils.logoutQuietly(nodeSession);
- }
-
- public Authentication authenticate(Authentication authentication)
- throws AuthenticationException {
- if (authentication instanceof UsernamePasswordAuthenticationToken) {
- // deal with remote access to internal server
- // FIXME very primitive and unsecure at this sSession adminSession
- // =tage
- // consider using the keyring for username / password authentication
- // or certificate
- UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication;
- if (!upat.getPrincipal().toString().equals(JVM_OSUSER))
- throw new BadCredentialsException("Wrong credentials");
- UsernamePasswordAuthenticationToken authen = new UsernamePasswordAuthenticationToken(
- authentication.getPrincipal(),
- authentication.getCredentials(), getBaseAuthorities());
- authen.setDetails(userDetails);
- return authen;
- } else if (authentication instanceof OsAuthenticationToken) {
- OsAuthenticationToken authen = (OsAuthenticationToken) super
- .authenticate(authentication);
- try {
- // WARNING: at this stage we assume that the java properties
- // will have the same value
- Collection<? extends GrantedAuthority> authorities = getBaseAuthorities();
- String username = JVM_OSUSER;
- Node userProfile = jcrSecurityModel.sync(nodeSession, username,
- SecurityUtils.authoritiesToStringList(authorities));
- JcrUserDetails.checkAccountStatus(userProfile);
-
- userDetails = new JcrUserDetails(userProfile, authen
- .getCredentials().toString(), authorities);
- authen.setDetails(userDetails);
- return authen;
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(nodeSession);
- throw new ArgeoException(
- "Unexpected exception when synchronizing OS and JCR security ",
- e);
- }
- } else {
- throw new ArgeoException("Unsupported authentication "
- + authentication.getClass());
- }
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
- this.jcrSecurityModel = jcrSecurityModel;
- }
-
- @SuppressWarnings("rawtypes")
- public boolean supports(Class authentication) {
- return OsAuthenticationToken.class.isAssignableFrom(authentication)
- || UsernamePasswordAuthenticationToken.class
- .isAssignableFrom(authentication);
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-import org.argeo.security.UserAdminService;
-import org.springframework.dao.DataAccessException;
-import org.springframework.security.core.userdetails.User;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-/**
- * Dummy user service to be used when running as a single OS user (typically
- * desktop). TODO integrate with JCR user / groups
- */
-public class OsJcrUserAdminService implements UserAdminService {
- private Repository repository;
-
- /** In memory roles provided by applications. */
- private List<String> roles = new ArrayList<String>();
-
- // private Session adminSession;
-
- public void init() {
- // try {
- // adminSession = repository.login();
- // } catch (RepositoryException e) {
- // throw new ArgeoException("Cannot initialize", e);
- // }
- }
-
- public void destroy() {
- // JcrUtils.logoutQuietly(adminSession);
- }
-
- /** <b>Unsupported</b> */
- public void createUser(UserDetails user) {
- throw new UnsupportedOperationException();
- }
-
- /** Does nothing */
- public void updateUser(UserDetails user) {
-
- }
-
- /** <b>Unsupported</b> */
- public void deleteUser(String username) {
- throw new UnsupportedOperationException();
- }
-
- /** <b>Unsupported</b> */
- public void changePassword(String oldPassword, String newPassword) {
- throw new UnsupportedOperationException();
- }
-
- public boolean userExists(String username) {
- if (getSPropertyUsername().equals(username))
- return true;
- else
- return false;
- }
-
- public UserDetails loadUserByUsername(String username)
- throws UsernameNotFoundException, DataAccessException {
- if (getSPropertyUsername().equals(username)) {
- UserDetails userDetails;
- if (repository != null) {
- Session adminSession = null;
- try {
- adminSession = repository.login();
- Node userProfile = UserJcrUtils.getUserProfile(
- adminSession, username);
- userDetails = new JcrUserDetails(userProfile, "",
- OsJcrAuthenticationProvider.getBaseAuthorities());
- } catch (RepositoryException e) {
- throw new ArgeoException(
- "Cannot retrieve user profile for " + username, e);
- } finally {
- JcrUtils.logoutQuietly(adminSession);
- }
- } else {
- userDetails = new User(username, "", true, true, true, true,
- OsJcrAuthenticationProvider.getBaseAuthorities());
- }
- return userDetails;
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- protected final String getSPropertyUsername() {
- return System.getProperty("user.name");
- }
-
- public Set<String> listUsers() {
- Set<String> set = new HashSet<String>();
- set.add(getSPropertyUsername());
- return set;
- }
-
- public Set<String> listUsersInRole(String role) {
- Set<String> set = new HashSet<String>();
- set.add(getSPropertyUsername());
- return set;
- }
-
- /** Does nothing */
- public void synchronize() {
- }
-
- /** <b>Unsupported</b> */
- public void newRole(String role) {
- roles.add(role);
- }
-
- public Set<String> listEditableRoles() {
- return new HashSet<String>(roles);
- }
-
- /** <b>Unsupported</b> */
- public void deleteRole(String role) {
- roles.remove(role);
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr;
-
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.jcr.Value;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoJcrConstants;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.UserJcrUtils;
-import org.argeo.security.NodeAuthenticationToken;
-import org.osgi.framework.BundleContext;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
-/** Connects to a JCR repository and delegates authentication to it. */
-public class RemoteJcrAuthenticationProvider implements AuthenticationProvider,
- ArgeoNames {
- private RepositoryFactory repositoryFactory;
- private BundleContext bundleContext;
-
- public final static String ROLE_REMOTE = "ROLE_REMOTE";
-
- public Authentication authenticate(Authentication authentication)
- throws AuthenticationException {
- NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication;
- String url = siteAuth.getUrl();
- if (url == null)// TODO? login on own node
- throw new ArgeoException("No url set in " + siteAuth);
- Session session;
-
- Node userProfile;
- try {
- SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
- siteAuth.getCredentials().toString().toCharArray());
- // get repository
- Repository repository = new RemoteJcrRepositoryWrapper(
- repositoryFactory, url, sp);
- if (bundleContext != null) {
- Dictionary<String, String> serviceProperties = new Hashtable<String, String>();
- serviceProperties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS,
- ArgeoJcrConstants.ALIAS_NODE);
- serviceProperties
- .put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url);
- bundleContext.registerService(Repository.class.getName(),
- repository, serviceProperties);
- }
- // Repository repository = ArgeoJcrUtils.getRepositoryByUri(
- // repositoryFactory, url);
- // if (repository == null)
- // throw new ArgeoException("Cannot connect to " + url);
-
- session = repository.login(sp, null);
-
- userProfile = UserJcrUtils.getUserProfile(session, sp.getUserID());
- JcrUserDetails.checkAccountStatus(userProfile);
-
- // Node userHome = UserJcrUtils.getUserHome(session);
- // if (userHome == null ||
- // !userHome.hasNode(ArgeoNames.ARGEO_PROFILE))
- // throw new ArgeoException("No profile for user "
- // + siteAuth.getName() + " in security workspace "
- // + siteAuth.getSecurityWorkspace() + " of "
- // + siteAuth.getUrl());
- // userProfile = userHome.getNode(ArgeoNames.ARGEO_PROFILE);
- } catch (RepositoryException e) {
- throw new BadCredentialsException(
- "Cannot authenticate " + siteAuth, e);
- }
-
- try {
- // Node userHome = UserJcrUtils.getUserHome(session);
- // retrieve remote roles
- List<GrantedAuthority> authoritiesList = new ArrayList<GrantedAuthority>();
- if (userProfile != null
- && userProfile.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
- Value[] roles = userProfile.getProperty(
- ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
- for (int i = 0; i < roles.length; i++)
- authoritiesList.add(new SimpleGrantedAuthority(roles[i]
- .getString()));
- }
- authoritiesList.add(new SimpleGrantedAuthority(ROLE_REMOTE));
-
- // create authenticated objects
- // GrantedAuthority[] authorities = authoritiesList
- // .toArray(new GrantedAuthority[authoritiesList.size()]);
- JcrUserDetails userDetails = new JcrUserDetails(userProfile,
- siteAuth.getCredentials().toString(), authoritiesList);
- NodeAuthenticationToken authenticated = new NodeAuthenticationToken(
- siteAuth, authoritiesList);
- authenticated.setDetails(userDetails);
- return authenticated;
- } catch (RepositoryException e) {
- throw new ArgeoException(
- "Unexpected exception when authenticating to " + url, e);
- }
- }
-
- @SuppressWarnings("rawtypes")
- public boolean supports(Class authentication) {
- return NodeAuthenticationToken.class.isAssignableFrom(authentication);
- }
-
- public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
- this.repositoryFactory = repositoryFactory;
- }
-
- public void setBundleContext(BundleContext bundleContext) {
- this.bundleContext = bundleContext;
- }
-
-}
* 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);
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr;
-
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.security.Privilege;
-import javax.jcr.version.VersionManager;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoJcrConstants;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-
-/**
- * Manages data expected by the Argeo security model, such as user home and
- * profile.
- */
-public class SimpleJcrSecurityModel implements JcrSecurityModel {
- private final static Log log = LogFactory
- .getLog(SimpleJcrSecurityModel.class);
- // ArgeoNames not implemented as interface in order to ease derivation by
- // Jackrabbit bundles
-
- /** The home base path. */
- private String homeBasePath = "/home";
-
- public synchronized Node sync(Session session, String username,
- List<String> roles) {
- // TODO check user name validity (e.g. should not start by ROLE_)
-
- try {
- Node userHome = UserJcrUtils.getUserHome(session, username);
- if (userHome == null) {
- String homePath = generateUserPath(homeBasePath, username);
- userHome = JcrUtils.mkdirs(session, homePath);
- // userHome = JcrUtils.mkfolders(session, homePath);
- userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
- userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
- session.save();
-
- JcrUtils.clearAccessControList(session, homePath, username);
- JcrUtils.addPrivilege(session, homePath, username,
- Privilege.JCR_ALL);
- } else {
- // for backward compatibility with pre 1.0 security model
- if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) {
- userHome.getNode(ArgeoNames.ARGEO_PROFILE).remove();
- userHome.getSession().save();
- }
- }
-
- // Remote roles
- if (roles != null) {
- // writeRemoteRoles(userHome, roles);
- }
-
- Node userProfile = UserJcrUtils.getUserProfile(session, username);
- if (userProfile == null) {
- String personPath = generateUserPath(
- ArgeoJcrConstants.PEOPLE_BASE_PATH, username);
- Node personBase = JcrUtils.mkdirs(session, personPath);
- userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE);
- userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
- userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
- userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true);
- userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED,
- true);
- userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED,
- true);
- userProfile.setProperty(
- ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true);
- session.save();
-
- JcrUtils.clearAccessControList(session, userProfile.getPath(),
- username);
- JcrUtils.addPrivilege(session, userProfile.getPath(), username,
- Privilege.JCR_READ);
-
- VersionManager versionManager = session.getWorkspace()
- .getVersionManager();
- if (versionManager.isCheckedOut(userProfile.getPath()))
- versionManager.checkin(userProfile.getPath());
-
- }
-
- // Remote roles
- if (roles != null) {
- writeRemoteRoles(userProfile, roles);
- }
- return userProfile;
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(session);
- throw new ArgeoException("Cannot sync node security model for "
- + username, e);
- }
- }
-
- /** Generate path for a new user home */
- protected String generateUserPath(String base, String username) {
- int atIndex = username.indexOf('@');
- if (atIndex > 0) {
- String domain = username.substring(0, atIndex);
- String name = username.substring(atIndex + 1);
- return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/'
- + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/'
- + name;
- } else if (atIndex == 0 || atIndex == (username.length() - 1)) {
- throw new ArgeoException("Unsupported username " + username);
- } else {
- return base + '/' + JcrUtils.firstCharsToPath(username, 2) + '/'
- + username;
- }
- }
-
- /** Write remote roles used by remote access in the home directory */
- protected void writeRemoteRoles(Node userHome, List<String> roles)
- throws RepositoryException {
- boolean writeRoles = false;
- if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
- Value[] remoteRoles = userHome.getProperty(
- ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
- if (remoteRoles.length != roles.size())
- writeRoles = true;
- else
- for (int i = 0; i < remoteRoles.length; i++)
- if (!remoteRoles[i].getString().equals(roles.get(i)))
- writeRoles = true;
- } else
- writeRoles = true;
-
- if (writeRoles) {
- userHome.getSession().getWorkspace().getVersionManager()
- .checkout(userHome.getPath());
- String[] roleIds = roles.toArray(new String[roles.size()]);
- userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds);
- JcrUtils.updateLastModified(userHome);
- userHome.getSession().save();
- userHome.getSession().getWorkspace().getVersionManager()
- .checkin(userHome.getPath());
- if (log.isDebugEnabled())
- log.debug("Wrote remote roles " + roles + " for "
- + userHome.getProperty(ArgeoNames.ARGEO_USER_ID));
- }
-
- }
-
- public void setHomeBasePath(String homeBasePath) {
- this.homeBasePath = homeBasePath;
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr.jackrabbit;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.security.jcr.SimpleJcrSecurityModel;
-
-/** Make sure that user authorizable exists before syncing user directories. */
-public class JackrabbitSecurityModel extends SimpleJcrSecurityModel {
- private final static Log log = LogFactory
- .getLog(JackrabbitSecurityModel.class);
-
- @Override
- public synchronized Node sync(Session session, String username,
- List<String> roles) {
- if (!(session instanceof JackrabbitSession))
- return super.sync(session, username, roles);
-
- try {
- UserManager userManager = ((JackrabbitSession) session)
- .getUserManager();
- User user = (User) userManager.getAuthorizable(username);
- if (user != null) {
- String principalName = user.getPrincipal().getName();
- if (!principalName.equals(username)) {
- log.warn("Jackrabbit principal is '" + principalName
- + "' but username is '" + username
- + "'. Recreating...");
- user.remove();
- user = userManager.createUser(username, "");
- }
- } else {
- // create new principal
- user = userManager.createUser(username, "");
- log.info(username + " added as Jackrabbit user " + user);
- }
-
- // generic JCR sync
- Node userProfile = super.sync(session, username, roles);
-
- Boolean enabled = userProfile.getProperty(ArgeoNames.ARGEO_ENABLED)
- .getBoolean();
- if (enabled && user.isDisabled())
- user.disable(null);
- else if (!enabled && !user.isDisabled())
- user.disable(userProfile.getPath() + " is disabled");
-
- // Sync Jackrabbit roles
- if (roles != null)
- syncRoles(userManager, user, roles);
-
- return userProfile;
- } catch (RepositoryException e) {
- throw new ArgeoException(
- "Cannot perform Jackrabbit specific operations", e);
- }
- }
-
- /** Make sure Jackrabbit roles are in line with authentication */
- void syncRoles(UserManager userManager, User user, List<String> roles)
- throws RepositoryException {
- List<String> userGroupIds = new ArrayList<String>();
- for (String role : roles) {
- Group group = (Group) userManager.getAuthorizable(role);
- if (group == null) {
- group = userManager.createGroup(role);
- log.info(role + " added as " + group);
- }
- if (!group.isMember(user))
- group.addMember(user);
- userGroupIds.add(role);
- }
-
- // check if user has not been removed from some groups
- for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
- Group group = it.next();
- if (!userGroupIds.contains(group.getID()))
- group.removeMember(user);
- }
- }
-}
+++ /dev/null
-package org.argeo.security.jcr.jackrabbit;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.user.Authorizable;
-import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.core.security.authentication.CryptedSimpleCredentials;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-import org.argeo.security.NodeAuthenticationToken;
-import org.argeo.security.UserAdminService;
-import org.argeo.security.jcr.JcrSecurityModel;
-import org.argeo.security.jcr.JcrUserDetails;
-import org.argeo.security.login.GrantedAuthorityPrincipal;
-import org.springframework.dao.DataAccessException;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-/**
- * An implementation of {@link UserAdminService} which closely wraps Jackrabbits
- * implementation. Roles are implemented with Groups.
- */
-public class JackrabbitUserAdminService implements UserAdminService,
- AuthenticationProvider {
- final static String userRole = "ROLE_USER";
- final static String adminRole = "ROLE_ADMIN";
-
- private Repository repository;
- private JcrSecurityModel securityModel;
-
- private JackrabbitSession adminSession = null;
-
- private String superUsername = "root";
- private String superUserInitialPassword = "demo";
-
- public void init() throws RepositoryException {
- Authentication authentication = SecurityContextHolder.getContext()
- .getAuthentication();
- authentication.getName();
- adminSession = (JackrabbitSession) repository.login();
- Authorizable adminGroup = getUserManager().getAuthorizable(adminRole);
- if (adminGroup == null) {
- adminGroup = getUserManager().createGroup(adminRole);
- adminSession.save();
- }
- Authorizable superUser = getUserManager()
- .getAuthorizable(superUsername);
- if (superUser == null) {
- superUser = getUserManager().createUser(superUsername,
- superUserInitialPassword);
- ((Group) adminGroup).addMember(superUser);
- securityModel.sync(adminSession, superUsername, null);
- adminSession.save();
- }
- }
-
- public void destroy() throws RepositoryException {
- JcrUtils.logoutQuietly(adminSession);
- }
-
- private UserManager getUserManager() throws RepositoryException {
- return adminSession.getUserManager();
- }
-
- @Override
- public void createUser(UserDetails user) {
- try {
- // FIXME workaround for issue in new user wizard where
- // security model is hardcoded and it already exists
- if (getUserManager().getAuthorizable(user.getUsername()) == null) {
- getUserManager().createUser(user.getUsername(),
- user.getPassword());
- securityModel.sync(adminSession, user.getUsername(), null);
- }
- updateUser(user);
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot create user " + user, e);
- }
- }
-
- @Override
- public void updateUser(UserDetails userDetails) {
- try {
- User user = (User) getUserManager().getAuthorizable(
- userDetails.getUsername());
- if (user == null)
- throw new ArgeoException("No user " + userDetails.getUsername());
-
- // new password
- String newPassword = userDetails.getPassword();
- if (!newPassword.trim().equals("")) {
- SimpleCredentials sp = new SimpleCredentials(
- userDetails.getUsername(), newPassword.toCharArray());
- CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
- .getCredentials();
- if (!credentials.matches(sp))
- user.changePassword(new String(newPassword));
- }
-
- List<String> roles = new ArrayList<String>();
- for (GrantedAuthority ga : userDetails.getAuthorities()) {
- if (ga.getAuthority().equals(userRole))
- continue;
- roles.add(ga.getAuthority());
- }
-
- for (Iterator<Group> it = user.memberOf(); it.hasNext();) {
- Group group = it.next();
- if (roles.contains(group.getPrincipal().getName()))
- roles.remove(group.getPrincipal().getName());
- else
- group.removeMember(user);
- }
-
- // remaining (new ones)
- for (String role : roles) {
- Group group = (Group) getUserManager().getAuthorizable(role);
- if (group == null)
- throw new ArgeoException("Group " + role
- + " does not exist,"
- + " whereas it was granted to user " + userDetails);
- group.addMember(user);
- }
- } catch (Exception e) {
- throw new ArgeoException("Cannot update user details", e);
- }
-
- }
-
- @Override
- public void deleteUser(String username) {
- try {
- getUserManager().getAuthorizable(username).remove();
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot remove user " + username, e);
- }
- }
-
- @Override
- public void changePassword(String oldPassword, String newPassword) {
- Authentication authentication = SecurityContextHolder.getContext()
- .getAuthentication();
- String username = authentication.getName();
- try {
- SimpleCredentials sp = new SimpleCredentials(username,
- oldPassword.toCharArray());
- User user = (User) getUserManager().getAuthorizable(username);
- CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
- .getCredentials();
- if (credentials.matches(sp))
- user.changePassword(newPassword);
- else
- throw new BadCredentialsException("Bad credentials provided");
- } catch (Exception e) {
- throw new ArgeoException("Cannot change password for user "
- + username, e);
- }
- }
-
- @Override
- public boolean userExists(String username) {
- try {
- Authorizable authorizable = getUserManager().getAuthorizable(
- username);
- if (authorizable != null && authorizable instanceof User)
- return true;
- return false;
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot check whether user " + username
- + " exists ", e);
- }
- }
-
- @Override
- public Set<String> listUsers() {
- LinkedHashSet<String> res = new LinkedHashSet<String>();
- try {
- Iterator<Authorizable> users = getUserManager().findAuthorizables(
- "rep:principalName", null, UserManager.SEARCH_TYPE_USER);
- while (users.hasNext()) {
- res.add(users.next().getPrincipal().getName());
- }
- return res;
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot list users", e);
- }
- }
-
- @Override
- public Set<String> listUsersInRole(String role) {
- LinkedHashSet<String> res = new LinkedHashSet<String>();
- try {
- Group group = (Group) getUserManager().getAuthorizable(role);
- Iterator<Authorizable> users = group.getMembers();
- // NB: not recursive
- while (users.hasNext()) {
- res.add(users.next().getPrincipal().getName());
- }
- return res;
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot list users in role " + role, e);
- }
- }
-
- @Override
- public void synchronize() {
- }
-
- @Override
- public void newRole(String role) {
- try {
- getUserManager().createGroup(role);
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot create role " + role, e);
- }
- }
-
- @Override
- public Set<String> listEditableRoles() {
- LinkedHashSet<String> res = new LinkedHashSet<String>();
- try {
- Iterator<Authorizable> groups = getUserManager().findAuthorizables(
- "rep:principalName", null, UserManager.SEARCH_TYPE_GROUP);
- while (groups.hasNext()) {
- res.add(groups.next().getPrincipal().getName());
- }
- return res;
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot list groups", e);
- }
- }
-
- @Override
- public void deleteRole(String role) {
- try {
- getUserManager().getAuthorizable(role).remove();
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot remove role " + role, e);
- }
- }
-
- @Override
- public UserDetails loadUserByUsername(String username)
- throws UsernameNotFoundException, DataAccessException {
- try {
- User user = (User) getUserManager().getAuthorizable(username);
- if (user == null)
- throw new UsernameNotFoundException("User " + username
- + " cannot be found");
- return loadJcrUserDetails(adminSession, username);
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot load user " + username, e);
- }
- }
-
- protected JcrUserDetails loadJcrUserDetails(Session session, String username)
- throws RepositoryException {
- if (username == null)
- username = session.getUserID();
- User user = (User) getUserManager().getAuthorizable(username);
- ArrayList<GrantedAuthorityPrincipal> authorities = new ArrayList<GrantedAuthorityPrincipal>();
- // FIXME make it more generic
- authorities.add(new GrantedAuthorityPrincipal("ROLE_USER"));
- Iterator<Group> groups = user.declaredMemberOf();
- while (groups.hasNext()) {
- Group group = groups.next();
- // String role = "ROLE_"
- // + group.getPrincipal().getName().toUpperCase();
- String role = group.getPrincipal().getName();
- authorities.add(new GrantedAuthorityPrincipal(role));
- }
-
- Node userProfile = UserJcrUtils.getUserProfile(session, username);
- JcrUserDetails userDetails = new JcrUserDetails(userProfile, "",
- authorities);
- return userDetails;
- }
-
- // AUTHENTICATION PROVIDER
- public synchronized Authentication authenticate(
- Authentication authentication) throws AuthenticationException {
- NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication;
- String username = siteAuth.getName();
- if (!(siteAuth.getCredentials() instanceof char[]))
- throw new ArgeoException("Only char array passwords are supported");
- char[] password = (char[]) siteAuth.getCredentials();
- try {
- SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(),
- password);
- User user = (User) getUserManager().getAuthorizable(username);
- if (user == null)
- throw new BadCredentialsException("Bad credentials");
- CryptedSimpleCredentials credentials = (CryptedSimpleCredentials) user
- .getCredentials();
- // String providedPassword = siteAuth.getCredentials().toString();
- if (!credentials.matches(sp))
- throw new BadCredentialsException("Bad credentials");
-
- // session = repository.login(sp, null);
-
- Node userProfile = UserJcrUtils.getUserProfile(adminSession,
- username);
- JcrUserDetails.checkAccountStatus(userProfile);
- } catch (BadCredentialsException e) {
- throw e;
- } catch (Exception e) {
- throw new BadCredentialsException(
- "Cannot authenticate " + siteAuth, e);
- } finally {
- Arrays.fill(password, '*');
- }
-
- try {
- JcrUserDetails userDetails = loadJcrUserDetails(adminSession,
- username);
- NodeAuthenticationToken authenticated = new NodeAuthenticationToken(
- siteAuth, userDetails.getAuthorities());
- authenticated.setDetails(userDetails);
- return authenticated;
- } catch (RepositoryException e) {
- throw new ArgeoException(
- "Unexpected exception when authenticating " + siteAuth, e);
- }
- }
-
- @SuppressWarnings("rawtypes")
- public boolean supports(Class authentication) {
- return UsernamePasswordAuthenticationToken.class
- .isAssignableFrom(authentication);
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setSecurityModel(JcrSecurityModel securityModel) {
- this.securityModel = securityModel;
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jcr.jackrabbit;
-
-import java.io.Serializable;
-
-import javax.jcr.LoginException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.server.SessionProvider;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoJcrConstants;
-import org.argeo.jcr.JcrUtils;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-
-/**
- * Session provider assuming a single workspace and a short life cycle,
- * typically a Spring bean of scope (web) 'session'.
- */
-public class ScopedSessionProvider implements SessionProvider, Serializable {
- private static final long serialVersionUID = 6589775984177317058L;
- private static final Log log = LogFactory
- .getLog(ScopedSessionProvider.class);
- private transient HttpSession httpSession = null;
- private transient Session jcrSession = null;
-
- private transient String currentRepositoryName = null;
- private transient String currentWorkspaceName = null;
- private transient String currentJcrUser = null;
-
- // private transient String anonymousUserId = "anonymous";
-
- public Session getSession(HttpServletRequest request, Repository rep,
- String workspace) throws LoginException, ServletException,
- RepositoryException {
-
- Authentication authentication = SecurityContextHolder.getContext()
- .getAuthentication();
- if (authentication == null)
- throw new ArgeoException(
- "Request not authenticated by Spring Security");
- String springUser = authentication.getName();
-
- // HTTP
- String requestJcrRepository = (String) request
- .getAttribute(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
-
- // HTTP session
- if (httpSession != null
- && !httpSession.getId().equals(request.getSession().getId()))
- throw new ArgeoException(
- "Only session scope is supported in this mode");
- if (httpSession == null)
- httpSession = request.getSession();
-
- // Initializes current values
- if (currentRepositoryName == null)
- currentRepositoryName = requestJcrRepository;
- if (currentWorkspaceName == null)
- currentWorkspaceName = workspace;
- if (currentJcrUser == null)
- currentJcrUser = springUser;
-
- // logout if there was a change in session coordinates
- if (jcrSession != null)
- if (!currentRepositoryName.equals(requestJcrRepository)) {
- if (log.isDebugEnabled())
- log.debug(getHttpSessionId() + " Changed from repository '"
- + currentRepositoryName + "' to '"
- + requestJcrRepository
- + "', logging out cached JCR session.");
- logout();
- } else if (!currentWorkspaceName.equals(workspace)) {
- if (log.isDebugEnabled())
- log.debug(getHttpSessionId() + " Changed from workspace '"
- + currentWorkspaceName + "' to '" + workspace
- + "', logging out cached JCR session.");
- logout();
- } else if (!currentJcrUser.equals(springUser)) {
- if (log.isDebugEnabled())
- log.debug(getHttpSessionId() + " Changed from user '"
- + currentJcrUser + "' to '" + springUser
- + "', logging out cached JCR session.");
- logout();
- }
-
- // login if needed
- if (jcrSession == null)
- try {
- Session session = login(rep, workspace);
- if (!session.getUserID().equals(springUser)) {
- JcrUtils.logoutQuietly(session);
- throw new ArgeoException("Spring Security user '"
- + springUser + "' not in line with JCR user '"
- + session.getUserID() + "'");
- }
- currentRepositoryName = requestJcrRepository;
- // do not use workspace variable which may be null
- currentWorkspaceName = session.getWorkspace().getName();
- currentJcrUser = session.getUserID();
-
- jcrSession = session;
- return jcrSession;
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot open session to workspace "
- + workspace, e);
- }
-
- // returns cached session
- return jcrSession;
- }
-
- protected Session login(Repository repository, String workspace)
- throws RepositoryException {
- Session session = repository.login(workspace);
- if (log.isDebugEnabled())
- log.debug(getHttpSessionId() + " User '" + session.getUserID()
- + "' logged in workspace '"
- + session.getWorkspace().getName() + "' of repository '"
- + currentRepositoryName + "'");
- return session;
- }
-
- public void releaseSession(Session session) {
- if (log.isTraceEnabled())
- log.trace(getHttpSessionId() + " Releasing JCR session " + session);
- }
-
- protected void logout() {
- JcrUtils.logoutQuietly(jcrSession);
- jcrSession = null;
- }
-
- protected final String getHttpSessionId() {
- return httpSession != null ? httpSession.getId() : "<null>";
- }
-
- public void init() {
- }
-
- public void destroy() {
- logout();
- if (getHttpSessionId() != null)
- if (log.isDebugEnabled())
- log.debug(getHttpSessionId()
- + " Cleaned up provider for web session ");
- httpSession = null;
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ldap;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.argeo.ArgeoException;
-import org.argeo.security.UserAdminService;
-import org.springframework.ldap.core.ContextSource;
-import org.springframework.security.authentication.encoding.PasswordEncoder;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.ldap.userdetails.LdapUserDetailsManager;
-
-/** Extends {@link LdapUserDetailsManager} by adding password encoding support. */
-public class ArgeoLdapUserDetailsManager extends LdapUserDetailsManager
- implements UserAdminService {
- private String superUsername = "root";
- private ArgeoUserAdminDaoLdap userAdminDao;
- private PasswordEncoder passwordEncoder;
- private final Random random;
-
- public ArgeoLdapUserDetailsManager(ContextSource contextSource) {
- super(contextSource);
- this.random = createRandom();
- }
-
- private static Random createRandom() {
- try {
- return SecureRandom.getInstance("SHA1PRNG");
- } catch (NoSuchAlgorithmException e) {
- return new Random(System.currentTimeMillis());
- }
- }
-
- @Override
- public void changePassword(String oldPassword, String newPassword) {
- Authentication authentication = SecurityContextHolder.getContext()
- .getAuthentication();
- if (authentication == null)
- throw new ArgeoException(
- "Cannot change password without authentication");
- String username = authentication.getName();
- UserDetails userDetails = loadUserByUsername(username);
- String currentPassword = userDetails.getPassword();
- if (currentPassword == null)
- throw new ArgeoException("Cannot access current password");
- if (!passwordEncoder
- .isPasswordValid(currentPassword, oldPassword, null))
- throw new ArgeoException("Old password invalid");
- // Spring Security LDAP 2.0 is buggy when used with OpenLDAP and called
- // with oldPassword argument
- super.changePassword(null, encodePassword(newPassword));
- }
-
- public void newRole(String role) {
- userAdminDao.createRole(role, superUsername);
- }
-
- public void synchronize() {
- for (String username : userAdminDao.listUsers())
- loadUserByUsername(username);
- // TODO: find a way to remove from JCR
- }
-
- public void deleteRole(String role) {
- userAdminDao.deleteRole(role);
- }
-
- public Set<String> listUsers() {
- return userAdminDao.listUsers();
- }
-
- public Set<String> listUsersInRole(String role) {
- Set<String> lst = new TreeSet<String>(
- userAdminDao.listUsersInRole(role));
- Iterator<String> it = lst.iterator();
- while (it.hasNext()) {
- if (it.next().equals(superUsername)) {
- it.remove();
- break;
- }
- }
- return lst;
- }
-
- public List<String> listUserRoles(String username) {
- UserDetails userDetails = loadUserByUsername(username);
- List<String> roles = new ArrayList<String>();
- for (GrantedAuthority ga : userDetails.getAuthorities()) {
- roles.add(ga.getAuthority());
- }
- return Collections.unmodifiableList(roles);
- }
-
- public Set<String> listEditableRoles() {
- return userAdminDao.listEditableRoles();
- }
-
- protected String encodePassword(String password) {
- if (!password.startsWith("{")) {
- byte[] salt = new byte[16];
- random.nextBytes(salt);
- return passwordEncoder.encodePassword(password, salt);
- } else {
- return password;
- }
- }
-
- public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
- this.passwordEncoder = passwordEncoder;
- }
-
- public void setSuperUsername(String superUsername) {
- this.superUsername = superUsername;
- }
-
- public void setUserAdminDao(ArgeoUserAdminDaoLdap userAdminDao) {
- this.userAdminDao = userAdminDao;
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ldap;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.naming.Name;
-import javax.naming.NamingException;
-import javax.naming.directory.DirContext;
-
-import org.springframework.ldap.core.ContextExecutor;
-import org.springframework.ldap.core.ContextMapper;
-import org.springframework.ldap.core.DirContextAdapter;
-import org.springframework.ldap.core.DistinguishedName;
-import org.springframework.ldap.core.LdapTemplate;
-import org.springframework.ldap.core.support.BaseLdapPathContextSource;
-import org.springframework.security.ldap.LdapUsernameToDnMapper;
-import org.springframework.security.ldap.LdapUtils;
-
-/**
- * Wraps low-level LDAP operation on user and roles, used by
- * {@link ArgeoLdapUserDetailsManager}
- */
-public class ArgeoUserAdminDaoLdap {
- private String userBase;
- private String usernameAttribute;
- private String groupBase;
- private String[] groupClasses;
-
- private String groupRoleAttribute;
- private String groupMemberAttribute;
- private String defaultRole;
- private String rolePrefix;
-
- private final LdapTemplate ldapTemplate;
- private LdapUsernameToDnMapper usernameMapper;
-
- /**
- * Standard constructor, using the LDAP context source shared with Spring
- * Security components.
- */
- public ArgeoUserAdminDaoLdap(BaseLdapPathContextSource contextSource) {
- this.ldapTemplate = new LdapTemplate(contextSource);
- }
-
- @SuppressWarnings("unchecked")
- public synchronized Set<String> listUsers() {
- List<String> usernames = (List<String>) ldapTemplate.listBindings(
- new DistinguishedName(userBase), new ContextMapper() {
- public Object mapFromContext(Object ctxArg) {
- DirContextAdapter ctx = (DirContextAdapter) ctxArg;
- return ctx.getStringAttribute(usernameAttribute);
- }
- });
-
- return Collections
- .unmodifiableSortedSet(new TreeSet<String>(usernames));
- }
-
- @SuppressWarnings("unchecked")
- public Set<String> listEditableRoles() {
- return Collections.unmodifiableSortedSet(new TreeSet<String>(
- ldapTemplate.listBindings(groupBase, new ContextMapper() {
- public Object mapFromContext(Object ctxArg) {
- String groupName = ((DirContextAdapter) ctxArg)
- .getStringAttribute(groupRoleAttribute);
- String roleName = convertGroupToRole(groupName);
- return roleName;
- }
- })));
- }
-
- @SuppressWarnings("unchecked")
- public Set<String> listUsersInRole(String role) {
- return (Set<String>) ldapTemplate.lookup(
- buildGroupDn(convertRoleToGroup(role)), new ContextMapper() {
- public Object mapFromContext(Object ctxArg) {
- DirContextAdapter ctx = (DirContextAdapter) ctxArg;
- String[] userDns = ctx
- .getStringAttributes(groupMemberAttribute);
- TreeSet<String> set = new TreeSet<String>();
- for (String userDn : userDns) {
- DistinguishedName dn = new DistinguishedName(userDn);
- String username = dn.getValue(usernameAttribute);
- set.add(username);
- }
- return Collections.unmodifiableSortedSet(set);
- }
- });
- }
-
- public void createRole(String role, final String superuserName) {
- String group = convertRoleToGroup(role);
- DistinguishedName superuserDn = (DistinguishedName) ldapTemplate
- .executeReadWrite(new ContextExecutor() {
- public Object executeWithContext(DirContext ctx)
- throws NamingException {
- return LdapUtils.getFullDn(
- usernameMapper.buildDn(superuserName), ctx);
- }
- });
-
- Name groupDn = buildGroupDn(group);
- DirContextAdapter context = new DirContextAdapter();
- context.setAttributeValues("objectClass", groupClasses);
- context.setAttributeValue("cn", group);
- // Add superuser because cannot create empty group
- context.setAttributeValue(groupMemberAttribute, superuserDn.toString());
- ldapTemplate.bind(groupDn, context, null);
- }
-
- public void deleteRole(String role) {
- String group = convertRoleToGroup(role);
- Name dn = buildGroupDn(group);
- ldapTemplate.unbind(dn);
- }
-
- /** Maps a role (ROLE_XXX) to the related LDAP group (xxx) */
- protected String convertRoleToGroup(String role) {
- String group = role;
- if (group.startsWith(rolePrefix)) {
- group = group.substring(rolePrefix.length());
- group = group.toLowerCase();
- }
- return group;
- }
-
- /** Maps anLDAP group (xxx) to the related role (ROLE_XXX) */
- protected String convertGroupToRole(String groupName) {
- groupName = groupName.toUpperCase();
-
- return rolePrefix + groupName;
- }
-
- protected Name buildGroupDn(String name) {
- return new DistinguishedName(groupRoleAttribute + "=" + name + ","
- + groupBase);
- }
-
- public void setUserBase(String userBase) {
- this.userBase = userBase;
- }
-
- public void setUsernameAttribute(String usernameAttribute) {
- this.usernameAttribute = usernameAttribute;
- }
-
- public void setGroupBase(String groupBase) {
- this.groupBase = groupBase;
- }
-
- public void setGroupRoleAttribute(String groupRoleAttributeName) {
- this.groupRoleAttribute = groupRoleAttributeName;
- }
-
- public void setGroupMemberAttribute(String groupMemberAttributeName) {
- this.groupMemberAttribute = groupMemberAttributeName;
- }
-
- public void setDefaultRole(String defaultRole) {
- this.defaultRole = defaultRole;
- }
-
- public void setRolePrefix(String rolePrefix) {
- this.rolePrefix = rolePrefix;
- }
-
- public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
- this.usernameMapper = usernameMapper;
- }
-
- public String getDefaultRole() {
- return defaultRole;
- }
-
- public void setGroupClasses(String[] groupClasses) {
- this.groupClasses = groupClasses;
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ldap.jcr;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.SortedSet;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-import javax.jcr.version.VersionManager;
-import javax.naming.Name;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.ModificationItem;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.security.SecurityUtils;
-import org.argeo.security.jcr.JcrSecurityModel;
-import org.argeo.security.jcr.JcrUserDetails;
-import org.argeo.security.jcr.SimpleJcrSecurityModel;
-import org.springframework.ldap.core.ContextMapper;
-import org.springframework.ldap.core.DirContextAdapter;
-import org.springframework.ldap.core.DirContextOperations;
-import org.springframework.ldap.core.DistinguishedName;
-import org.springframework.ldap.core.LdapTemplate;
-import org.springframework.security.authentication.encoding.PasswordEncoder;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.ldap.LdapUsernameToDnMapper;
-import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
-
-/** Makes sure that LDAP and JCR are in line. */
-public class JcrLdapSynchronizer implements UserDetailsContextMapper,
- ArgeoNames {
- private final static Log log = LogFactory.getLog(JcrLdapSynchronizer.class);
-
- // LDAP
- private LdapTemplate ldapTemplate;
- /**
- * LDAP template whose context source has an object factory set to null. see
- * <a href=
- * "http://forum.springsource.org/showthread.php?55955-Persistent-search-with-spring-ldap"
- * >this</a>
- */
- // private LdapTemplate rawLdapTemplate;
-
- private String userBase;
- private String usernameAttribute;
- private String passwordAttribute;
- private String[] userClasses;
- // private String defaultUserRole ="ROLE_USER";
-
- // private NamingListener ldapUserListener;
- // private SearchControls subTreeSearchControls;
- private LdapUsernameToDnMapper usernameMapper;
-
- private PasswordEncoder passwordEncoder;
- private final Random random;
-
- // JCR
- /** Admin session on the main workspace */
- private Session nodeSession;
- private Repository repository;
-
- // private JcrProfileListener jcrProfileListener;
- private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
-
- // Mapping
- private Map<String, String> propertyToAttributes = new HashMap<String, String>();
-
- public JcrLdapSynchronizer() {
- random = createRandom();
- }
-
- public void init() {
- try {
- nodeSession = repository.login();
-
- // TODO put this in a different thread, and poll the LDAP server
- // until it is up
- try {
- synchronize();
-
- // LDAP
- // subTreeSearchControls = new SearchControls();
- // subTreeSearchControls
- // .setSearchScope(SearchControls.SUBTREE_SCOPE);
- // LDAP listener
- // ldapUserListener = new LdapUserListener();
- // rawLdapTemplate.executeReadOnly(new ContextExecutor() {
- // public Object executeWithContext(DirContext ctx)
- // throws NamingException {
- // EventDirContext ectx = (EventDirContext) ctx.lookup("");
- // ectx.addNamingListener(userBase, "("
- // + usernameAttribute + "=*)",
- // subTreeSearchControls, ldapUserListener);
- // return null;
- // }
- // });
- } catch (Exception e) {
- log.error("Could not synchronize and listen to LDAP,"
- + " probably because the LDAP server is not available."
- + " Restart the system as soon as possible.", e);
- }
-
- // JCR
- // String[] nodeTypes = { ArgeoTypes.ARGEO_USER_PROFILE };
- // jcrProfileListener = new JcrProfileListener();
- // noLocal is used so that we are not notified when we modify JCR
- // from LDAP
- // nodeSession
- // .getWorkspace()
- // .getObservationManager()
- // .addEventListener(jcrProfileListener,
- // Event.PROPERTY_CHANGED | Event.NODE_ADDED, "/",
- // true, null, nodeTypes, true);
- } catch (Exception e) {
- JcrUtils.logoutQuietly(nodeSession);
- throw new ArgeoException("Cannot initialize LDAP/JCR synchronizer",
- e);
- }
- }
-
- public void destroy() {
- // JcrUtils.removeListenerQuietly(nodeSession, jcrProfileListener);
- JcrUtils.logoutQuietly(nodeSession);
- // try {
- // rawLdapTemplate.executeReadOnly(new ContextExecutor() {
- // public Object executeWithContext(DirContext ctx)
- // throws NamingException {
- // EventDirContext ectx = (EventDirContext) ctx.lookup("");
- // ectx.removeNamingListener(ldapUserListener);
- // return null;
- // }
- // });
- // } catch (Exception e) {
- // // silent (LDAP server may have been shutdown already)
- // if (log.isTraceEnabled())
- // log.trace("Cannot remove LDAP listener", e);
- // }
- }
-
- /*
- * LDAP TO JCR
- */
- /** Full synchronization between LDAP and JCR. LDAP has priority. */
- protected void synchronize() {
- try {
- Name userBaseName = new DistinguishedName(userBase);
- // TODO subtree search?
- @SuppressWarnings("unchecked")
- List<String> userPaths = (List<String>) ldapTemplate.listBindings(
- userBaseName, new ContextMapper() {
- public Object mapFromContext(Object ctxObj) {
- try {
- return mapLdapToJcr((DirContextAdapter) ctxObj);
- } catch (Exception e) {
- // do not break process because of error
- log.error(
- "Could not LDAP->JCR synchronize user "
- + ctxObj, e);
- return null;
- }
- }
- });
-
- // create accounts which are not in LDAP
- Query query = nodeSession
- .getWorkspace()
- .getQueryManager()
- .createQuery(
- "select * from [" + ArgeoTypes.ARGEO_USER_PROFILE
- + "]", Query.JCR_SQL2);
- NodeIterator it = query.execute().getNodes();
- while (it.hasNext()) {
- Node userProfile = it.nextNode();
- String path = userProfile.getPath();
- try {
- if (!userPaths.contains(path)) {
- String username = userProfile
- .getProperty(ARGEO_USER_ID).getString();
- // GrantedAuthority[] authorities = {new
- // GrantedAuthorityImpl(defaultUserRole)};
- List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
- JcrUserDetails userDetails = new JcrUserDetails(
- userProfile, username, authorities);
- String dn = createLdapUser(userDetails);
- log.warn("Created ldap entry '" + dn + "' for user '"
- + username + "'");
-
- // if(!userProfile.getProperty(ARGEO_ENABLED).getBoolean()){
- // continue profiles;
- // }
- //
- // log.warn("Path "
- // + path
- // + " not found in LDAP, disabling user "
- // + userProfile.getProperty(ArgeoNames.ARGEO_USER_ID)
- // .getString());
-
- // Temporary hack to repair previous behaviour
- if (!userProfile.getProperty(ARGEO_ENABLED)
- .getBoolean()) {
- VersionManager versionManager = nodeSession
- .getWorkspace().getVersionManager();
- versionManager.checkout(userProfile.getPath());
- userProfile.setProperty(ArgeoNames.ARGEO_ENABLED,
- true);
- nodeSession.save();
- versionManager.checkin(userProfile.getPath());
- }
- }
- } catch (Exception e) {
- log.error("Cannot process " + path, e);
- }
- }
- } catch (Exception e) {
- JcrUtils.discardQuietly(nodeSession);
- log.error("Cannot synchronize LDAP and JCR", e);
- // throw new ArgeoException("Cannot synchronize LDAP and JCR", e);
- }
- }
-
- private String createLdapUser(UserDetails user) {
- DirContextAdapter ctx = new DirContextAdapter();
- mapUserToContext(user, ctx);
- DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
- ldapTemplate.bind(dn, ctx, null);
- return dn.toString();
- }
-
- /** Called during authentication in order to retrieve user details */
- public UserDetails mapUserFromContext(final DirContextOperations ctx,
- final String username,
- Collection<? extends GrantedAuthority> authorities) {
- if (ctx == null)
- throw new ArgeoException("No LDAP information for user " + username);
-
- String ldapUsername = ctx.getStringAttribute(usernameAttribute);
- if (!ldapUsername.equals(username))
- throw new ArgeoException("Logged in with username " + username
- + " but LDAP user is " + ldapUsername);
-
- Node userProfile = jcrSecurityModel.sync(nodeSession, username,
- SecurityUtils.authoritiesToStringList(authorities));
- // JcrUserDetails.checkAccountStatus(userProfile);
-
- // password
- SortedSet<?> passwordAttributes = ctx
- .getAttributeSortedStringSet(passwordAttribute);
- String password;
- if (passwordAttributes == null || passwordAttributes.size() == 0) {
- // throw new ArgeoException("No password found for user " +
- // username);
- password = "NULL";
- } else {
- byte[] arr = (byte[]) passwordAttributes.first();
- password = new String(arr);
- // erase password
- Arrays.fill(arr, (byte) 0);
- }
-
- try {
- return new JcrUserDetails(userProfile, password, authorities);
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot retrieve user details for "
- + username, e);
- }
- }
-
- /**
- * Writes an LDAP context to the JCR user profile.
- *
- * @return path to user profile
- */
- protected synchronized String mapLdapToJcr(DirContextAdapter ctx) {
- Session session = nodeSession;
- try {
- // process
- String username = ctx.getStringAttribute(usernameAttribute);
-
- Node userProfile = jcrSecurityModel.sync(session, username, null);
- Map<String, String> modifications = new HashMap<String, String>();
- for (String jcrProperty : propertyToAttributes.keySet())
- ldapToJcr(userProfile, jcrProperty, ctx, modifications);
-
- int modifCount = modifications.size();
- if (modifCount > 0) {
- session.getWorkspace().getVersionManager()
- .checkout(userProfile.getPath());
- for (String prop : modifications.keySet())
- userProfile.setProperty(prop, modifications.get(prop));
- JcrUtils.updateLastModified(userProfile);
- session.save();
- session.getWorkspace().getVersionManager()
- .checkin(userProfile.getPath());
- if (log.isDebugEnabled())
- log.debug("Mapped " + modifCount + " LDAP modification"
- + (modifCount == 1 ? "" : "s") + " from "
- + ctx.getDn() + " to " + userProfile);
- }
- return userProfile.getPath();
- } catch (Exception e) {
- JcrUtils.discardQuietly(session);
- throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
- }
- }
-
- /** Maps an LDAP property to a JCR property */
- protected void ldapToJcr(Node userProfile, String jcrProperty,
- DirContextOperations ctx, Map<String, String> modifications) {
- // TODO do we really need DirContextOperations?
- try {
- String ldapAttribute;
- if (propertyToAttributes.containsKey(jcrProperty))
- ldapAttribute = propertyToAttributes.get(jcrProperty);
- else
- throw new ArgeoException(
- "No LDAP attribute mapped for JCR proprty "
- + jcrProperty);
-
- String value = ctx.getStringAttribute(ldapAttribute);
- String jcrValue = userProfile.hasProperty(jcrProperty) ? userProfile
- .getProperty(jcrProperty).getString() : null;
- if (value != null && jcrValue != null) {
- if (!value.equals(jcrValue))
- modifications.put(jcrProperty, value);
- } else if (value != null && jcrValue == null) {
- modifications.put(jcrProperty, value);
- } else if (value == null && jcrValue != null) {
- modifications.put(jcrProperty, value);
- }
- } catch (Exception e) {
- throw new ArgeoException("Cannot map JCR property " + jcrProperty
- + " from LDAP", e);
- }
- }
-
- /*
- * JCR to LDAP
- */
-
- public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
- if (!(user instanceof JcrUserDetails))
- throw new ArgeoException("Unsupported user details: "
- + user.getClass());
-
- ctx.setAttributeValues("objectClass", userClasses);
- ctx.setAttributeValue(usernameAttribute, user.getUsername());
- ctx.setAttributeValue(passwordAttribute,
- encodePassword(user.getPassword()));
-
- final JcrUserDetails jcrUserDetails = (JcrUserDetails) user;
- try {
- Node userProfile = nodeSession
- .getNode(jcrUserDetails.getHomePath()).getNode(
- ARGEO_PROFILE);
- for (String jcrProperty : propertyToAttributes.keySet()) {
- if (userProfile.hasProperty(jcrProperty)) {
- ModificationItem mi = jcrToLdap(jcrProperty, userProfile
- .getProperty(jcrProperty).getString());
- if (mi != null)
- ctx.setAttribute(mi.getAttribute());
- }
- }
- if (log.isTraceEnabled())
- log.trace("Mapped " + userProfile + " to " + ctx.getDn());
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
- }
-
- }
-
- /** Maps a JCR property to an LDAP property */
- protected ModificationItem jcrToLdap(String jcrProperty, String value) {
- // TODO do we really need DirContextOperations?
- try {
- String ldapAttribute;
- if (propertyToAttributes.containsKey(jcrProperty))
- ldapAttribute = propertyToAttributes.get(jcrProperty);
- else
- return null;
-
- // fix issue with empty 'sn' in LDAP
- if (ldapAttribute.equals("sn") && (value.trim().equals("")))
- return null;
- // fix issue with empty 'description' in LDAP
- if (ldapAttribute.equals("description") && value.trim().equals(""))
- return null;
- BasicAttribute attr = new BasicAttribute(
- propertyToAttributes.get(jcrProperty), value);
- ModificationItem mi = new ModificationItem(
- DirContext.REPLACE_ATTRIBUTE, attr);
- return mi;
- } catch (Exception e) {
- throw new ArgeoException("Cannot map JCR property " + jcrProperty
- + " from LDAP", e);
- }
- }
-
- /*
- * UTILITIES
- */
- protected String encodePassword(String password) {
- if (!password.startsWith("{")) {
- byte[] salt = new byte[16];
- random.nextBytes(salt);
- return passwordEncoder.encodePassword(password, salt);
- } else {
- return password;
- }
- }
-
- private static Random createRandom() {
- try {
- return SecureRandom.getInstance("SHA1PRNG");
- } catch (NoSuchAlgorithmException e) {
- return new Random(System.currentTimeMillis());
- }
- }
-
- /*
- * DEPENDENCY INJECTION
- */
-
- public void setLdapTemplate(LdapTemplate ldapTemplate) {
- this.ldapTemplate = ldapTemplate;
- }
-
- public void setRawLdapTemplate(LdapTemplate rawLdapTemplate) {
- // this.rawLdapTemplate = rawLdapTemplate;
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setUserBase(String userBase) {
- this.userBase = userBase;
- }
-
- public void setUsernameAttribute(String usernameAttribute) {
- this.usernameAttribute = usernameAttribute;
- }
-
- public void setPropertyToAttributes(Map<String, String> propertyToAttributes) {
- this.propertyToAttributes = propertyToAttributes;
- }
-
- public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
- this.usernameMapper = usernameMapper;
- }
-
- public void setPasswordAttribute(String passwordAttribute) {
- this.passwordAttribute = passwordAttribute;
- }
-
- public void setUserClasses(String[] userClasses) {
- this.userClasses = userClasses;
- }
-
- public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
- this.passwordEncoder = passwordEncoder;
- }
-
- public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
- this.jcrSecurityModel = jcrSecurityModel;
- }
-
- /** Listen to LDAP */
- // class LdapUserListener implements ObjectChangeListener,
- // NamespaceChangeListener, UnsolicitedNotificationListener {
- //
- // public void namingExceptionThrown(NamingExceptionEvent evt) {
- // evt.getException().printStackTrace();
- // }
- //
- // public void objectChanged(NamingEvent evt) {
- // Binding user = evt.getNewBinding();
- // // TODO find a way not to be called when JCR is the source of the
- // // modification
- // DirContextAdapter ctx = (DirContextAdapter) ldapTemplate
- // .lookup(user.getName());
- // mapLdapToJcr(ctx);
- // }
- //
- // public void objectAdded(NamingEvent evt) {
- // Binding user = evt.getNewBinding();
- // DirContextAdapter ctx = (DirContextAdapter) ldapTemplate
- // .lookup(user.getName());
- // mapLdapToJcr(ctx);
- // }
- //
- // public void objectRemoved(NamingEvent evt) {
- // if (log.isDebugEnabled())
- // log.debug(evt);
- // }
- //
- // public void objectRenamed(NamingEvent evt) {
- // if (log.isDebugEnabled())
- // log.debug(evt);
- // }
- //
- // public void notificationReceived(UnsolicitedNotificationEvent evt) {
- // UnsolicitedNotification notification = evt.getNotification();
- // NamingException ne = notification.getException();
- // String msg = "LDAP notification " + "ID=" + notification.getID()
- // + ", referrals=" + notification.getReferrals();
- // if (ne != null) {
- // if (log.isTraceEnabled())
- // log.trace(msg + ", exception= " + ne, ne);
- // else
- // log.warn(msg + ", exception= " + ne);
- // } else if (log.isDebugEnabled()) {
- // log.debug("Unsollicited LDAP notification " + msg);
- // }
- // }
- //
- // }
-
- /** Listen to JCR */
- // class JcrProfileListener implements EventListener {
- //
- // public void onEvent(EventIterator events) {
- // try {
- // final Map<Name, List<ModificationItem>> modifications = new HashMap<Name,
- // List<ModificationItem>>();
- // while (events.hasNext()) {
- // Event event = events.nextEvent();
- // try {
- // if (Event.PROPERTY_CHANGED == event.getType()) {
- // Property property = (Property) nodeSession
- // .getItem(event.getPath());
- // String propertyName = property.getName();
- // Node userProfile = property.getParent();
- // String username = userProfile.getProperty(
- // ARGEO_USER_ID).getString();
- // if (propertyToAttributes.containsKey(propertyName)) {
- // Name name = usernameMapper.buildDn(username);
- // if (!modifications.containsKey(name))
- // modifications.put(name,
- // new ArrayList<ModificationItem>());
- // String value = property.getString();
- // ModificationItem mi = jcrToLdap(propertyName,
- // value);
- // if (mi != null)
- // modifications.get(name).add(mi);
- // }
- // } else if (Event.NODE_ADDED == event.getType()) {
- // Node userProfile = nodeSession.getNode(event
- // .getPath());
- // String username = userProfile.getProperty(
- // ARGEO_USER_ID).getString();
- // Name name = usernameMapper.buildDn(username);
- // for (String propertyName : propertyToAttributes
- // .keySet()) {
- // if (!modifications.containsKey(name))
- // modifications.put(name,
- // new ArrayList<ModificationItem>());
- // String value = userProfile.getProperty(
- // propertyName).getString();
- // ModificationItem mi = jcrToLdap(propertyName,
- // value);
- // if (mi != null)
- // modifications.get(name).add(mi);
- // }
- // }
- // } catch (RepositoryException e) {
- // throw new ArgeoException("Cannot process event "
- // + event, e);
- // }
- // }
- //
- // for (Name name : modifications.keySet()) {
- // List<ModificationItem> userModifs = modifications.get(name);
- // int modifCount = userModifs.size();
- // ldapTemplate.modifyAttributes(name, userModifs
- // .toArray(new ModificationItem[modifCount]));
- // if (log.isDebugEnabled())
- // log.debug("Mapped " + modifCount + " JCR modification"
- // + (modifCount == 1 ? "" : "s") + " to " + name);
- // }
- // } catch (Exception e) {
- // // if (log.isDebugEnabled())
- // // e.printStackTrace();
- // throw new ArgeoException("Cannot process JCR events ("
- // + e.getMessage() + ")", e);
- // }
- // }
- //
- // }
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.ldap.jcr;
-
-import java.util.Collection;
-import java.util.UUID;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-import org.argeo.security.jcr.JcrUserDetails;
-import org.springframework.ldap.core.DirContextAdapter;
-import org.springframework.ldap.core.DirContextOperations;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
-
-/** @deprecated Read only mapping from LDAP to user details */
-@Deprecated
-public class JcrUserDetailsContextMapper implements UserDetailsContextMapper,
- ArgeoNames {
- /** Admin session on the security workspace */
- private Session securitySession;
- private Repository repository;
- private String securityWorkspace = "security";
-
- public void init() {
- try {
- securitySession = repository.login(securityWorkspace);
- } catch (RepositoryException e) {
- JcrUtils.logoutQuietly(securitySession);
- throw new ArgeoException(
- "Cannot initialize LDAP/JCR user details context mapper", e);
- }
- }
-
- public void destroy() {
- JcrUtils.logoutQuietly(securitySession);
- }
-
- /** Called during authentication in order to retrieve user details */
- public UserDetails mapUserFromContext(final DirContextOperations ctx,
- final String username,
- Collection<? extends GrantedAuthority> authorities) {
- if (ctx == null)
- throw new ArgeoException("No LDAP information for user " + username);
- Node userHome = UserJcrUtils.getUserHome(securitySession, username);
- if (userHome == null)
- throw new ArgeoException("No JCR information for user " + username);
-
- // password
- // SortedSet<?> passwordAttributes = ctx
- // .getAttributeSortedStringSet(passwordAttribute);
- // String password;
- // if (passwordAttributes == null || passwordAttributes.size() == 0) {
- // throw new ArgeoException("No password found for user " + username);
- // } else {
- // byte[] arr = (byte[]) passwordAttributes.first();
- // password = new String(arr);
- // // erase password
- // Arrays.fill(arr, (byte) 0);
- // }
-
- try {
- // we don't have access to password, so let's not pretend
- String password = UUID.randomUUID().toString();
- return new JcrUserDetails(userHome.getNode(ARGEO_PROFILE),
- password, authorities);
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot retrieve user details for "
- + username, e);
- }
- }
-
- public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
- throw new UnsupportedOperationException("LDAP access is read-only");
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.login;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.security.SecurityUtils;
-import org.argeo.util.LocaleCallback;
-import org.argeo.util.LocaleUtils;
-import org.springframework.security.authentication.AnonymousAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
-/** Login module which caches one subject per thread. */
-public class AnonymousLoginModule extends AbstractSpringLoginModule {
- private String anonymousRole = "ROLE_ANONYMOUS";
- /** Comma separated list of locales */
- private String availableLocales = null;
-
- @Override
- protected Authentication processLogin(CallbackHandler callbackHandler)
- throws LoginException, UnsupportedCallbackException, IOException,
- InterruptedException {
- BundleContextCallback bundleContextCallback = new BundleContextCallback();
- Locale selectedLocale = null;
- // multi locale
- if (availableLocales != null && !availableLocales.trim().equals("")) {
- LocaleCallback localeCallback = new LocaleCallback(availableLocales);
- callbackHandler.handle(new Callback[] { localeCallback,
- bundleContextCallback });
- selectedLocale = localeCallback.getSelectedLocale();
- } else {
- callbackHandler.handle(new Callback[] { bundleContextCallback });
- }
-
- List<SimpleGrantedAuthority> authorities = Collections
- .singletonList(new SimpleGrantedAuthority(anonymousRole));
- AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken(
- SecurityUtils.getStaticKey(), null, authorities);
-
- Authentication auth = getAuthenticationManager(bundleContextCallback)
- .authenticate(anonymousToken);
-
- if (selectedLocale != null)
- LocaleUtils.threadLocale.set(selectedLocale);
- return auth;
- }
-}
+++ /dev/null
-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;
- }
-
-}
+++ /dev/null
-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;
- }
-
-}
+++ /dev/null
-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()));
- }
- }
- }
-
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-
-}
+++ /dev/null
-package org.argeo.security.login;
-
-import javax.security.auth.login.LoginException;
-
-public class LoginCanceledException extends LoginException {
- private static final long serialVersionUID = 8289162094013471043L;
-
-}
+++ /dev/null
-/*
- * 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);
- }
-}
<classpathentry kind="con"
path="org.eclipse.pde.core.requiredPlugins" />
<classpathentry kind="con"
- path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" />
+ path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7" />
<classpathentry kind="output" path="bin" />
</classpath>
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;
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;
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);
}
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
if (t instanceof BadCredentialsException)
return (BadCredentialsException) t;
- if (t instanceof LoginCanceledException)
+ if (t instanceof CredentialNotFoundException)
return new BadCredentialsException("Login canceled");
if (t.getCause() != null)
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;
// The plug-in ID
public final static String PLUGIN_ID = "org.argeo.security.ui"; //$NON-NLS-1$
- public final static String CONTEXT_KEYRING = "KEYRING";
+ final static String CONTEXT_KEYRING = "KEYRING";
private CallbackHandler defaultCallbackHandler;
private ServiceRegistration<CallbackHandler> defaultCallbackHandlerReg;
--- /dev/null
+/*
+ * 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");
+ }
+}
--- /dev/null
+package org.argeo.security.ui.auth;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.util.LocaleCallback;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * A composite that can populate itself based on {@link Callback}s. It can be
+ * used directly as a {@link CallbackHandler} or be used by one by calling the
+ * {@link #createCallbackHandlers(Callback[])}.
+ * <p>
+ * Supported standard {@link Callback}s are:<br>
+ * <ul>
+ * <li>{@link PasswordCallback}</li>
+ * <li>{@link NameCallback}</li>
+ * <li>{@link TextOutputCallback}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Supported Argeo {@link Callback}s are:<br>
+ * <ul>
+ * <li>{@link LocaleCallback}</li>
+ * </ul>
+ * </p>
+ */
+public class CompositeCallbackHandler extends Composite implements
+ CallbackHandler {
+ private static final long serialVersionUID = -928223893722723777L;
+
+ private boolean wasUsedAlready = false;
+ private boolean isSubmitted = false;
+ private boolean isCanceled = false;
+
+ public CompositeCallbackHandler(Composite parent, int style) {
+ super(parent, style);
+ }
+
+ @Override
+ public synchronized void handle(final Callback[] callbacks)
+ throws IOException, UnsupportedCallbackException {
+ // reset
+ if (wasUsedAlready && !isSubmitted() && !isCanceled()) {
+ cancel();
+ for (Control control : getChildren())
+ control.dispose();
+ isSubmitted = false;
+ isCanceled = false;
+ }
+
+ for (Callback callback : callbacks)
+ checkCallbackSupported(callback);
+ // create controls synchronously in the UI thread
+ getDisplay().syncExec(new Runnable() {
+
+ @Override
+ public void run() {
+ createCallbackHandlers(callbacks);
+ }
+ });
+
+ if (!wasUsedAlready)
+ wasUsedAlready = true;
+
+ while (!isSubmitted() && !isCanceled()) {
+ try {
+ wait(1000l);
+ } catch (InterruptedException e) {
+ // silent
+ }
+ }
+
+ cleanCallbacksAfterCancel(callbacks);
+ }
+
+ public void checkCallbackSupported(Callback callback)
+ throws UnsupportedCallbackException {
+ if (callback instanceof TextOutputCallback
+ || callback instanceof NameCallback
+ || callback instanceof PasswordCallback
+ || callback instanceof LocaleCallback) {
+ return;
+ } else {
+ throw new UnsupportedCallbackException(callback);
+ }
+ }
+
+ /**
+ * Set writable callbacks to null if the handle is canceled (check is done
+ * by the method)
+ */
+ public void cleanCallbacksAfterCancel(Callback[] callbacks) {
+ if (isCanceled()) {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback) {
+ ((NameCallback) callback).setName(null);
+ } else if (callback instanceof PasswordCallback) {
+ PasswordCallback pCallback = (PasswordCallback) callback;
+ char[] arr = pCallback.getPassword();
+ if (arr != null) {
+ Arrays.fill(arr, '*');
+ pCallback.setPassword(null);
+ }
+ }
+ }
+ }
+ }
+
+ public void createCallbackHandlers(Callback[] callbacks) {
+ Composite composite = this;
+ for (int i = 0; i < callbacks.length; i++) {
+ Callback callback = callbacks[i];
+ if (callback instanceof TextOutputCallback) {
+ createLabelTextoutputHandler(composite,
+ (TextOutputCallback) callback);
+ } else if (callback instanceof NameCallback) {
+ createNameHandler(composite, (NameCallback) callback);
+ } else if (callback instanceof PasswordCallback) {
+ createPasswordHandler(composite, (PasswordCallback) callback);
+ } else if (callback instanceof LocaleCallback) {
+ createLocaleHandler(composite, (LocaleCallback) callback);
+ }
+ }
+ }
+
+ protected Text createNameHandler(Composite composite,
+ final NameCallback callback) {
+ Label label = new Label(composite, SWT.NONE);
+ label.setText(callback.getPrompt());
+ final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD
+ | SWT.BORDER);
+ if (callback.getDefaultName() != null) {
+ // set default value, if provided
+ text.setText(callback.getDefaultName());
+ callback.setName(callback.getDefaultName());
+ }
+ text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ text.addModifyListener(new ModifyListener() {
+ private static final long serialVersionUID = 7300032545287292973L;
+
+ public void modifyText(ModifyEvent event) {
+ callback.setName(text.getText());
+ }
+ });
+ text.addSelectionListener(new SelectionListener() {
+ private static final long serialVersionUID = 1820530045857665111L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ submit();
+ }
+ });
+
+ text.addKeyListener(new KeyListener() {
+ private static final long serialVersionUID = -8698107785092095713L;
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ }
+ });
+ return text;
+ }
+
+ protected Text createPasswordHandler(Composite composite,
+ final PasswordCallback callback) {
+ Label label = new Label(composite, SWT.NONE);
+ label.setText(callback.getPrompt());
+ final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD
+ | SWT.PASSWORD | SWT.BORDER);
+ passwordText
+ .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ passwordText.addModifyListener(new ModifyListener() {
+ private static final long serialVersionUID = -7099363995047686732L;
+
+ public void modifyText(ModifyEvent event) {
+ callback.setPassword(passwordText.getTextChars());
+ }
+ });
+ passwordText.addSelectionListener(new SelectionListener() {
+ private static final long serialVersionUID = 1820530045857665111L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ submit();
+ }
+ });
+ return passwordText;
+ }
+
+ protected Combo createLocaleHandler(Composite composite,
+ final LocaleCallback callback) {
+ String[] labels = callback.getSupportedLocalesLabels();
+ if (labels.length == 0)
+ return null;
+ Label label = new Label(composite, SWT.NONE);
+ label.setText(callback.getPrompt());
+
+ final Combo combo = new Combo(composite, SWT.READ_ONLY);
+ combo.setItems(labels);
+ combo.select(callback.getDefaultIndex());
+ combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ combo.addSelectionListener(new SelectionListener() {
+ private static final long serialVersionUID = 38678989091946277L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ callback.setSelectedIndex(combo.getSelectionIndex());
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ });
+ return combo;
+ }
+
+ protected Label createLabelTextoutputHandler(Composite composite,
+ final TextOutputCallback callback) {
+ Label label = new Label(composite, SWT.NONE);
+ label.setText(callback.getMessage());
+ GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
+ data.horizontalSpan = 2;
+ label.setLayoutData(data);
+ return label;
+ // TODO: find a way to pass this information
+ // int messageType = callback.getMessageType();
+ // int dialogMessageType = IMessageProvider.NONE;
+ // switch (messageType) {
+ // case TextOutputCallback.INFORMATION:
+ // dialogMessageType = IMessageProvider.INFORMATION;
+ // break;
+ // case TextOutputCallback.WARNING:
+ // dialogMessageType = IMessageProvider.WARNING;
+ // break;
+ // case TextOutputCallback.ERROR:
+ // dialogMessageType = IMessageProvider.ERROR;
+ // break;
+ // }
+ // setMessage(callback.getMessage(), dialogMessageType);
+ }
+
+ synchronized boolean isSubmitted() {
+ return isSubmitted;
+ }
+
+ synchronized boolean isCanceled() {
+ return isCanceled;
+ }
+
+ protected synchronized void submit() {
+ isSubmitted = true;
+ notifyAll();
+ }
+
+ protected synchronized void cancel() {
+ isCanceled = true;
+ notifyAll();
+ }
+}
--- /dev/null
+/*
+ * 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() {
+ }
+}
+++ /dev/null
-/*
- * 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");
- }
-}
+++ /dev/null
-/*
- * 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() {
- }
-}
import org.argeo.jcr.spring.BeanNodeMapper;
+@Deprecated
public class MapperTest extends AbstractInternalJackrabbitTestCase {
public void testSimpleObject() throws Exception {
SimpleObject mySo = new SimpleObject();
* 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);
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;
resUrl = resUrl.substring(1);
String pkg = resUrl.substring(0, resUrl.lastIndexOf('/')).replace('/',
'.');
- ServiceReference paSr = bundleContext
- .getServiceReference(PackageAdmin.class.getName());
+ ServiceReference<PackageAdmin> paSr = bundleContext
+ .getServiceReference(PackageAdmin.class);
PackageAdmin packageAdmin = (PackageAdmin) bundleContext
.getService(paSr);
* files
* @return how many files were copied
*/
- @SuppressWarnings("resource")
public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive,
ArgeoMonitor monitor) {
long count = 0l;