You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by tr...@apache.org on 2014/02/10 23:57:59 UTC
svn commit: r1566806 - in /jackrabbit/oak/trunk: oak-auth-ldap/
oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/
oak-core/src/main/java/org/apache/jackrabbit/oak/security/
oak-core/src/main/java/org/apache/jackra...
Author: tripod
Date: Mon Feb 10 22:57:58 2014
New Revision: 1566806
URL: http://svn.apache.org/r1566806
Log:
OAK-516 Create LdapLoginModule based on ExternalLoginModule (wip)
Added:
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/PoolableUnboundConnectionFactory.java
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/UnboundLdapConnectionPool.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java
Removed:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/DefaultSyncHandler.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncConfig.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncMode.java
Modified:
jackrabbit/oak/trunk/oak-auth-ldap/pom.xml
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentity.java
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentity.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityRef.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestLoginModule.java
Modified: jackrabbit/oak/trunk/oak-auth-ldap/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/pom.xml?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-auth-ldap/pom.xml Mon Feb 10 22:57:58 2014
@@ -42,12 +42,11 @@
<Import-Package>
!antlr.*,
!org.dom4j.*,
- !org.apache.mina.*,
!org.xmlpull.v1,
*
</Import-Package>
<Embed-Dependency>
- api-all,commons-pool,commons-lang
+ api-all,commons-pool,commons-lang,mina-core
</Embed-Dependency>
</instructions>
</configuration>
@@ -90,6 +89,13 @@
<version>2.6</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.mina</groupId>
+ <artifactId>mina-core</artifactId>
+ <version>2.0.7</version>
+ <scope>provided</scope>
+ </dependency>
+
<!-- Optional OSGi dependencies, used only when running within OSGi -->
<dependency>
Modified: jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentity.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentity.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentity.java (original)
+++ jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentity.java Mon Feb 10 22:57:58 2014
@@ -16,7 +16,7 @@
*/
package org.apache.jackrabbit.oak.security.authentication.ldap.impl;
-import java.security.Principal;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -25,7 +25,6 @@ import javax.annotation.Nonnull;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
-import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
/**
* Implements an identity that is provided by the {@link LdapIdentityProvider}.
@@ -65,6 +64,16 @@ public abstract class LdapIdentity imple
}
/**
+ * Returns the DN as principal name.
+ * @return the DN
+ */
+ @Nonnull
+ @Override
+ public String getPrincipalName() {
+ return ref.getId();
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -78,7 +87,7 @@ public abstract class LdapIdentity imple
@Nonnull
@Override
public Iterable<? extends ExternalGroup> getGroups() {
- return null;
+ return Collections.emptyList();
}
/**
@@ -90,13 +99,12 @@ public abstract class LdapIdentity imple
return properties;
}
- private Principal principal;
@Override
- public Principal getPrincipal() {
- if (principal == null) {
- principal = new PrincipalImpl(getId());
- }
- return principal;
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("LdapIdentity{");
+ sb.append("ref=").append(ref);
+ sb.append(", id='").append(id).append('\'');
+ sb.append('}');
+ return sb.toString();
}
-
}
Modified: jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java (original)
+++ jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java Mon Feb 10 22:57:58 2014
@@ -16,17 +16,20 @@
*/
package org.apache.jackrabbit.oak.security.authentication.ldap.impl;
-import java.io.IOException;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.jcr.Credentials;
+import javax.jcr.SimpleCredentials;
import javax.security.auth.login.LoginException;
+import org.apache.commons.pool.impl.GenericObjectPool;
+import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.SearchCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
import org.apache.directory.api.ldap.model.message.Response;
@@ -36,10 +39,13 @@ import org.apache.directory.api.ldap.mod
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.ldap.client.api.LdapConnection;
-import org.apache.directory.ldap.client.api.LdapNetworkConnection;
+import org.apache.directory.ldap.client.api.LdapConnectionConfig;
+import org.apache.directory.ldap.client.api.LdapConnectionPool;
+import org.apache.directory.ldap.client.api.PoolableLdapConnectionFactory;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
@@ -75,6 +81,16 @@ public class LdapIdentityProvider implem
private LdapProviderConfig config;
/**
+ * the connection pool with connections authenticated with the bind DN
+ */
+ private LdapConnectionPool adminPool;
+
+ /**
+ * the connection pool with unbound connections
+ */
+ private UnboundLdapConnectionPool userPool;
+
+ /**
* Default constructor for OSGi
*/
public LdapIdentityProvider() {
@@ -86,14 +102,77 @@ public class LdapIdentityProvider implem
*/
public LdapIdentityProvider(LdapProviderConfig config) {
this.config = config;
+ init();
}
@Activate
private void activate(Map<String, Object> properties) {
ConfigurationParameters cfg = ConfigurationParameters.of(properties);
config = LdapProviderConfig.of(cfg);
+ init();
+ }
+
+ @Deactivate
+ private void deactivate() {
+ close();
+ }
+
+ /**
+ * Initializes the ldap identity provider.
+ */
+ private void init() {
+ if (adminPool != null) {
+ throw new IllegalStateException("Provider already initialized.");
+ }
+
+ // setup admin connection pool
+ LdapConnectionConfig cc = new LdapConnectionConfig();
+ cc.setLdapHost(config.getHostname());
+ cc.setLdapPort(config.getPort());
+ if (!config.getBindDN().isEmpty()) {
+ cc.setName(config.getBindDN());
+ cc.setCredentials(config.getBindPassword());
+ }
+ cc.setUseSsl(config.useSSL());
+ PoolableLdapConnectionFactory factory = new PoolableLdapConnectionFactory(cc);
+ adminPool = new LdapConnectionPool(factory);
+ adminPool.setTestOnBorrow(true);
+ adminPool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_GROW);
+
+ // setup unbound connection pool. let's create a new version of the config
+ cc = new LdapConnectionConfig();
+ cc.setLdapHost(config.getHostname());
+ cc.setLdapPort(config.getPort());
+ cc.setUseSsl(config.useSSL());
+ userPool = new UnboundLdapConnectionPool(new PoolableUnboundConnectionFactory(cc));
+ userPool.setTestOnBorrow(true);
+ userPool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_GROW);
+ }
+
+ /**
+ * Closes this provider and releases the internal pool. This should be called by Non-OSGi users of this provider.
+ */
+ public void close() {
+ if (adminPool != null) {
+ try {
+ adminPool.close();
+ } catch (Exception e) {
+ log.warn("Error while closing LDAP connection pool", e);
+ }
+ adminPool = null;
+ }
+ if (userPool != null) {
+ try {
+ userPool.close();
+ } catch (Exception e) {
+ log.warn("Error while closing LDAP connection pool", e);
+ }
+ userPool = null;
+ }
}
+
+
@Nonnull
@Override
public String getName() {
@@ -127,9 +206,15 @@ public class LdapIdentityProvider implem
@Override
public ExternalUser getUser(@Nonnull String userId) throws ExternalIdentityException {
+ long t0 = System.nanoTime();
LdapConnection connection = connect();
+ long t1 = System.nanoTime();
try {
Entry entry = getEntry(connection, config.getUserConfig(), userId);
+ long t2 = System.nanoTime();
+ if (log.isDebugEnabled()) {
+ log.debug("getUser({}) connect: {}us, lookup: {}us", new Object[]{userId, (t1-t0)/1000, (t2-t1)/1000});
+ }
if (entry != null) {
return createUser(entry, userId);
} else {
@@ -174,13 +259,11 @@ public class LdapIdentityProvider implem
// Create the SearchRequest object
SearchRequest req = new SearchRequestImpl();
req.setScope(SearchScope.SUBTREE);
- req.addAttributes("*");
+ req.addAttributes(SchemaConstants.ALL_USER_ATTRIBUTES);
req.setTimeLimit(config.getSearchTimeout());
req.setBase(new Dn(idConfig.getBaseDN()));
req.setFilter(searchFilter);
- log.debug("Searching entries below {} with {}", idConfig.getBaseDN(), searchFilter);
-
// Process the request
SearchCursor searchCursor = connection.search(req);
while (searchCursor.next()) {
@@ -192,9 +275,16 @@ public class LdapIdentityProvider implem
if (searchCursor.next()) {
log.warn("search for {} returned more than one entry. discarding additional ones.", searchFilter);
}
+ if (log.isDebugEnabled()) {
+ log.debug("search below {} with {} found {}",
+ new Object[]{idConfig.getBaseDN(), searchFilter, resultEntry.getDn()});
+ }
return resultEntry;
}
}
+ if (log.isDebugEnabled()) {
+ log.debug("search below {} with {} found 0 entries.", idConfig.getBaseDN(), searchFilter);
+ }
return null;
}
@@ -233,14 +323,8 @@ public class LdapIdentityProvider implem
private LdapConnection connect() throws ExternalIdentityException {
try {
- LdapConnection connection = new LdapNetworkConnection(config.getHostname(), config.getPort(), config.useSSL());
- if (config.getBindDN().length() > 0) {
- connection.bind(config.getBindDN(), config.getBindPassword());
- } else {
- connection.bind();
- }
- return connection;
- } catch (LdapException e) {
+ return adminPool.getConnection();
+ } catch (Throwable e) {
log.error("Error while connecting to the ldap server.", e);
throw new ExternalIdentityException("Error while connecting and binding to the ldap server", e);
}
@@ -248,20 +332,47 @@ public class LdapIdentityProvider implem
private void disconnect(LdapConnection connection) throws ExternalIdentityException {
try {
- connection.unBind();
- } catch (LdapException e) {
- log.error("Error while unbinding from the ldap server.", e);
- }
- try {
- connection.close();
- } catch (IOException e) {
- log.error("Error while disconnecting from the ldap server.", e);
+ adminPool.releaseConnection(connection);
+ } catch (Exception e) {
+ log.warn("Error while disconnecting from the ldap server.", e);
}
}
@Override
public ExternalUser authenticate(@Nonnull Credentials credentials) throws ExternalIdentityException, LoginException {
- return null;
+ if (!(credentials instanceof SimpleCredentials)) {
+ log.debug("LDAP IDP can only authenticate SimpleCredentials.");
+ return null;
+ }
+ final SimpleCredentials creds = (SimpleCredentials) credentials;
+ final ExternalUser user = getUser(creds.getUserID());
+ if (user != null) {
+ // authenticate
+ LdapConnection connection = null;
+ try {
+ long t0 = System.nanoTime();
+ connection = userPool.getConnection();
+ long t1 = System.nanoTime();
+ connection.bind(user.getExternalId().getId(), new String(creds.getPassword()));
+ long t2 = System.nanoTime();
+ if (log.isDebugEnabled()) {
+ log.debug("authenticate({}) connect: {}us, bind: {}us", new Object[]{user.getId(), (t1-t0)/1000, (t2-t1)/1000});
+ }
+ } catch (LdapAuthenticationException e) {
+ throw new LoginException("Unable to authenticate against LDAP server: " + e.getMessage());
+ } catch (Exception e) {
+ throw new ExternalIdentityException("Error while binding user credentials", e);
+ } finally {
+ if (connection != null) {
+ try {
+ userPool.releaseConnection(connection);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+ }
+ return user;
}
private boolean isMyRef(ExternalIdentityRef ref) {
Modified: jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java (original)
+++ jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java Mon Feb 10 22:57:58 2014
@@ -204,6 +204,21 @@ public class LdapProviderConfig {
public static final String PARAM_USER_EXTRA_FILTER = "user.extraFilter";
/**
+ * @see Identity#makeDnPath()
+ */
+ public static final boolean PARAM_USER_MAKE_DN_PATH_DEFAULT = false;
+
+ /**
+ * @see Identity#makeDnPath()
+ */
+ @Property(
+ label = "User DN paths",
+ description = "Controls if the DN should be used for calculating a portion of the intermediate path.",
+ boolValue = PARAM_USER_MAKE_DN_PATH_DEFAULT
+ )
+ public static final String PARAM_USER_MAKE_DN_PATH = "user.makeDnPath";
+
+ /**
* @see Identity#getBaseDN()
*/
public static final String PARAM_GROUP_BASE_DN_DEFAULT = "ou=groups,o=example,dc=com";
@@ -266,6 +281,21 @@ public class LdapProviderConfig {
public static final String PARAM_GROUP_EXTRA_FILTER = "group.extraFilter";
/**
+ * @see Identity#makeDnPath()
+ */
+ public static final boolean PARAM_GROUP_MAKE_DN_PATH_DEFAULT = false;
+
+ /**
+ * @see Identity#makeDnPath()
+ */
+ @Property(
+ label = "Group DN paths",
+ description = "Controls if the DN should be used for calculating a portion of the intermediate path.",
+ boolValue = PARAM_GROUP_MAKE_DN_PATH_DEFAULT
+ )
+ public static final String PARAM_GROUP_MAKE_DN_PATH = "group.makeDnPath";
+
+ /**
* @see #getGroupMemberAttribute()
*/
public static final String PARAM_GROUP_MEMBER_ATTRIBUTE_DEFAULT = "uniquemember";
@@ -295,6 +325,8 @@ public class LdapProviderConfig {
private String filterTemplate;
+ private boolean makeDnPath;
+
/**
* Configures the base DN for searches of this kind of identity
* @return the base DN
@@ -388,6 +420,27 @@ public class LdapProviderConfig {
return this;
}
+
+ /**
+ * Configures if the identities DN should be used to generate a portion of the authorizables intermediate path.
+ * @return {@code true} if the DN is used a intermediate path.
+ */
+ public boolean makeDnPath() {
+ return makeDnPath;
+ }
+
+ /**
+ * Sets the intermediate path flag.
+ * @param makeDnPath {@code true} to use the DN as intermediate path
+ * @return {@code this}
+ * @see #makeDnPath()
+ */
+ @Nonnull
+ public Identity setMakeDnPath(boolean makeDnPath) {
+ this.makeDnPath = makeDnPath;
+ return this;
+ }
+
/**
* Returns the LDAP filter that is used when searching this type of identity. The filter is based on the
* configuration and has the following format:
@@ -491,13 +544,15 @@ public class LdapProviderConfig {
.setBaseDN(params.getConfigValue(PARAM_USER_BASE_DN, PARAM_USER_BASE_DN))
.setIdAttribute(params.getConfigValue(PARAM_USER_ID_ATTRIBUTE, PARAM_USER_ID_ATTRIBUTE_DEFAULT))
.setExtraFilter(params.getConfigValue(PARAM_USER_EXTRA_FILTER, PARAM_USER_EXTRA_FILTER_DEFAULT))
- .setObjectClasses(params.getConfigValue(PARAM_USER_OBJECTCLASS, PARAM_USER_OBJECTCLASS_DEFAULT));
+ .setObjectClasses(params.getConfigValue(PARAM_USER_OBJECTCLASS, PARAM_USER_OBJECTCLASS_DEFAULT))
+ .setMakeDnPath(params.getConfigValue(PARAM_USER_MAKE_DN_PATH, PARAM_USER_MAKE_DN_PATH_DEFAULT));
cfg.getGroupConfig()
.setBaseDN(params.getConfigValue(PARAM_GROUP_BASE_DN, PARAM_GROUP_BASE_DN))
.setIdAttribute(params.getConfigValue(PARAM_GROUP_NAME_ATTRIBUTE, PARAM_GROUP_NAME_ATTRIBUTE_DEFAULT))
.setExtraFilter(params.getConfigValue(PARAM_GROUP_EXTRA_FILTER, PARAM_GROUP_EXTRA_FILTER_DEFAULT))
- .setObjectClasses(params.getConfigValue(PARAM_GROUP_OBJECTCLASS, PARAM_GROUP_OBJECTCLASS_DEFAULT));
+ .setObjectClasses(params.getConfigValue(PARAM_GROUP_OBJECTCLASS, PARAM_GROUP_OBJECTCLASS_DEFAULT))
+ .setMakeDnPath(params.getConfigValue(PARAM_GROUP_MAKE_DN_PATH, PARAM_GROUP_MAKE_DN_PATH_DEFAULT));
return cfg;
}
Added: jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/PoolableUnboundConnectionFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/PoolableUnboundConnectionFactory.java?rev=1566806&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/PoolableUnboundConnectionFactory.java (added)
+++ jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/PoolableUnboundConnectionFactory.java Mon Feb 10 22:57:58 2014
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.oak.security.authentication.ldap.impl;
+
+
+import org.apache.commons.pool.PoolableObjectFactory;
+import org.apache.directory.ldap.client.api.LdapConnection;
+import org.apache.directory.ldap.client.api.LdapConnectionConfig;
+import org.apache.directory.ldap.client.api.LdapNetworkConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * A factory for creating unbound LdapConnection objects managed by LdapConnectionPool.
+ */
+public class PoolableUnboundConnectionFactory implements PoolableObjectFactory<LdapConnection> {
+
+ /**
+ * the logger
+ */
+ private static final Logger log = LoggerFactory.getLogger(PoolableUnboundConnectionFactory.class);
+
+ /**
+ * configuration object for the connection
+ */
+ private LdapConnectionConfig config;
+
+ /**
+ * Creates a new instance of PoolableUnboundConnectionFactory
+ *
+ * @param config the configuration for creating LdapConnections
+ */
+ public PoolableUnboundConnectionFactory(LdapConnectionConfig config) {
+ this.config = config;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void activateObject(LdapConnection connection) throws Exception {
+ log.debug("Activating {}", connection);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void destroyObject(LdapConnection connection) throws Exception {
+ log.debug("Destroying {}", connection);
+ connection.close();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public LdapConnection makeObject() throws Exception {
+ log.debug("Creating a LDAP connection");
+ LdapNetworkConnection connection = new LdapNetworkConnection(config);
+ connection.connect();
+ return connection;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void passivateObject(LdapConnection connection) throws Exception {
+ log.debug("Passivating {}", connection);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean validateObject(LdapConnection connection) {
+ log.debug("Validating {}", connection);
+ return connection.isConnected();
+ }
+}
Added: jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/UnboundLdapConnectionPool.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/UnboundLdapConnectionPool.java?rev=1566806&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/UnboundLdapConnectionPool.java (added)
+++ jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/UnboundLdapConnectionPool.java Mon Feb 10 22:57:58 2014
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.oak.security.authentication.ldap.impl;
+
+import org.apache.commons.pool.impl.GenericObjectPool;
+import org.apache.directory.ldap.client.api.LdapConnection;
+
+/**
+ * A pool implementation for LdapConnection objects.
+ * <p/>
+ * This class is just a wrapper around the commons GenericObjectPool, and has
+ * a more meaningful name to represent the pool type.
+ */
+public class UnboundLdapConnectionPool extends GenericObjectPool<LdapConnection> {
+ /**
+ * Instantiates a new LDAP connection pool.
+ *
+ * @param factory the LDAP connection factory
+ */
+ public UnboundLdapConnectionPool(PoolableUnboundConnectionFactory factory) {
+ super(factory);
+ }
+
+
+ /**
+ * Gives a Unbound LdapConnection fetched from the pool.
+ *
+ * @return an LdapConnection object from pool
+ * @throws Exception if an error occurs while obtaining a connection from the factory
+ */
+ public LdapConnection getConnection() throws Exception {
+ return super.borrowObject();
+ }
+
+
+ /**
+ * Places the given LdapConnection back in the pool.
+ *
+ * @param connection the LdapConnection to be released
+ * @throws Exception if an error occurs while releasing the connection
+ */
+ public void releaseConnection(LdapConnection connection) throws Exception {
+ super.returnObject(connection);
+ }
+}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java Mon Feb 10 22:57:58 2014
@@ -43,8 +43,10 @@ import org.apache.jackrabbit.oak.spi.sec
import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.authentication.AuthenticationConfiguration;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncManager;
import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIDPManagerImpl;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProviderManager;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.SyncManagerImpl;
import org.apache.jackrabbit.oak.spi.security.authentication.token.CompositeTokenConfiguration;
import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConfiguration;
import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
@@ -115,6 +117,9 @@ public class SecurityProviderImpl implem
@Reference
private ExternalIdentityProviderManager identityProviderManager;
+ @Reference
+ private SyncManager syncManager;
+
/**
* Default constructor used in OSGi environments.
*/
@@ -136,6 +141,7 @@ public class SecurityProviderImpl implem
privilegeConfiguration = new PrivilegeConfigurationImpl();
tokenConfiguration = new TokenConfigurationImpl(this);
identityProviderManager = new ExternalIDPManagerImpl();
+ syncManager = new SyncManagerImpl();
}
@Nonnull
@@ -184,6 +190,8 @@ public class SecurityProviderImpl implem
return (T) tokenConfiguration;
} else if (ExternalIdentityProviderManager.class == configClass) {
return (T) identityProviderManager;
+ } else if (SyncManager.class == configClass) {
+ return (T) syncManager;
} else {
throw new IllegalArgumentException("Unsupported security configuration class " + configClass);
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentity.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentity.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentity.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentity.java Mon Feb 10 22:57:58 2014
@@ -44,6 +44,14 @@ public interface ExternalIdentity {
String getId();
/**
+ * Returns the principal name of this identity. This usually corresponds to
+ * {@link org.apache.jackrabbit.api.security.user.Authorizable#getPrincipal()}.{@link java.security.Principal#getName()}
+ * @return the principal name
+ */
+ @Nonnull
+ String getPrincipalName();
+
+ /**
* Returns the desired intermediate relative path of the authorizable to be created. For example, one could map
* an external hierarchy into the local users and groups hierarchy.
*
@@ -66,7 +74,4 @@ public interface ExternalIdentity {
@Nonnull
Map<String, ?> getProperties();
- // todo: really?
- Principal getPrincipal();
-
}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityRef.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityRef.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityRef.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalIdentityRef.java Mon Feb 10 22:57:58 2014
@@ -92,4 +92,13 @@ public class ExternalIdentityRef {
);
}
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ExternalIdentityRef{");
+ sb.append("id='").append(id).append('\'');
+ sb.append(", providerName='").append(providerName).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
}
\ No newline at end of file
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java?rev=1566806&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncContext.java Mon Feb 10 22:57:58 2014
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.oak.spi.security.authentication.external;
+
+import javax.annotation.Nonnull;
+
+/**
+ * {@code SyncContext} is used as scope for sync operations. Implementations are free to associate any resources with
+ * the sync context. The sync context must not be accessed concurrently and must be closed after use.
+ */
+public interface SyncContext {
+
+ /**
+ * Synchronizes an external identity with the repository based on the respective configuration.
+ *
+ * @param identity the identity to sync.
+ * @return {@code true} if the given identity was synced; {@code false} if for no change.
+ * @throws SyncException if an error occurrs
+ */
+ boolean sync(@Nonnull ExternalIdentity identity) throws SyncException;
+
+ /**
+ * Closes this context and releases any resources bound to it. Note that an implementation must not commit the
+ * {@link org.apache.jackrabbit.oak.api.Root} passed during the creation call. This is the responsibility of the
+ * application.
+ */
+ void close();
+}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/SyncHandler.java Mon Feb 10 22:57:58 2014
@@ -20,21 +20,39 @@ import javax.annotation.Nonnull;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.oak.api.Root;
-import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
/**
- * SyncHandler is used to sync users and groups from an external source an {@link ExternalUserProvider}.
- * One sync task always operates within a {@link SyncContext} and is associated with a {@link SyncConfig}.
+ * SyncHandler is used to sync users and groups from an {@link ExternalIdentityProvider}.
+ * The synchronization performed within the scope of a {@link SyncContext} which is acquired during the
+ * {@link #createContext(ExternalIdentityProvider, org.apache.jackrabbit.api.security.user.UserManager, org.apache.jackrabbit.oak.api.Root)} call.
*
- * todo:
- * - cleanup expired authorizables ?
+ * The exact configuration is managed by the sync handler instance. The system may contain several sync handler
+ * implementations with different configurations. those are managed by the {@link SyncManager}.
*
+ * @see org.apache.jackrabbit.oak.spi.security.authentication.external.SyncContext
+ * @see org.apache.jackrabbit.oak.spi.security.authentication.external.SyncManager
*/
public interface SyncHandler {
- boolean initialize(@Nonnull UserManager userManager, @Nonnull Root root,
- @Nonnull SyncMode mode,
- @Nonnull ConfigurationParameters options) throws SyncException;
+ /**
+ * Returns the name of this sync handler.
+ * @return sync handler name
+ */
+ @Nonnull
+ String getName();
+
+ /**
+ * Initializes a sync context which is used to start the sync operations.
+ *
+ * @param idp the external identity provider used for syncing
+ * @param userManager user manager for managing authorizables
+ * @param root root of the current tree
+ * @return the sync context
+ * @throws SyncException if an error occurs
+ */
+ @Nonnull
+ SyncContext createContext(@Nonnull ExternalIdentityProvider idp,
+ @Nonnull UserManager userManager,
+ @Nonnull Root root) throws SyncException;
- boolean sync(@Nonnull ExternalUser externalUser) throws SyncException;
}
\ No newline at end of file
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java?rev=1566806&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java Mon Feb 10 22:57:58 2014
@@ -0,0 +1,485 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.oak.spi.security.authentication.external.impl;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+
+/**
+ * {@code DefaultSyncConfig} defines how users and groups from an external source are synced into the repository using
+ * the {@link DefaultSyncHandler}.
+ */
+@Component(
+ label = "Apache Jackrabbit Oak Default Sync Handler",
+ name = "org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler",
+ configurationFactory = true,
+ metatype = true,
+ ds = false
+)
+public class DefaultSyncConfig {
+
+ /**
+ * @see #getName()
+ */
+ public static final String PARAM_NAME_DEFAULT = "default";
+
+ /**
+ * @see #getName()
+ */
+ @Property(
+ label = "Sync Handler Name",
+ description = "Name of this sync configuration. This is used to reference this handler by the login modules.",
+ value = PARAM_NAME_DEFAULT
+ )
+ public static final String PARAM_NAME = "handler.name";
+
+ /**
+ * @see DefaultSyncConfig.User#getExpirationTime()
+ */
+ public static final long PARAM_USER_EXPIRATION_TIME_DEFAULT = 3600*1000;
+
+ /**
+ * @see DefaultSyncConfig.User#getExpirationTime()
+ */
+ @Property(
+ label = "User Expiration Time",
+ description = "Duration in milliseconds until a synced user gets expired.",
+ longValue = PARAM_USER_EXPIRATION_TIME_DEFAULT
+ )
+ public static final String PARAM_USER_EXPIRATION_TIME = "user.expirationTime";
+
+ /**
+ * @see DefaultSyncConfig.User#getAutoMembership()
+ */
+ public static final String[] PARAM_USER_AUTO_MEMBERSHIP_DEFAULT = {};
+
+ /**
+ * @see DefaultSyncConfig.User#getAutoMembership()
+ */
+ @Property(
+ label = "User auto membership",
+ description = "List of groups that a synced user is added to automatically",
+ value = {},
+ cardinality = Integer.MAX_VALUE
+ )
+ public static final String PARAM_USER_AUTO_MEMBERSHIP = "user.autoMembership";
+
+ /**
+ * @see DefaultSyncConfig.User#getPropertyMapping()
+ */
+ public static final String[] PARAM_USER_PROPERTY_MAPPING_DEFAULT = {"rep:fullname=cn"};
+
+ /**
+ * @see DefaultSyncConfig.User#getPropertyMapping()
+ */
+ @Property(
+ label = "User property mapping",
+ description = "List mapping definition of local properties from external ones. eg: 'profile/email=mail'." +
+ "Use double quotes for fixed values. eg: 'profile/nt:primaryType=\"nt:unstructured\"",
+ value = {"rep:fullname=cn"},
+ cardinality = Integer.MAX_VALUE
+ )
+ public static final String PARAM_USER_PROPERTY_MAPPING = "user.propertyMapping";
+
+ /**
+ * @see DefaultSyncConfig.User#getPathPrefix()
+ */
+ public static final String PARAM_USER_PATH_PREFIX_DEFAULT = "";
+
+ /**
+ * @see DefaultSyncConfig.User#getPathPrefix()
+ */
+ @Property(
+ label = "User Path Prefix",
+ description = "The path prefix used when creating new users.",
+ value = PARAM_USER_PATH_PREFIX_DEFAULT
+ )
+ public static final String PARAM_USER_PATH_PREFIX = "user.pathPrefix";
+
+ /**
+ * @see DefaultSyncConfig.User#getMembershipExpirationTime()
+ */
+ public static final long PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT = 3600*1000;
+
+ /**
+ * @see DefaultSyncConfig.User#getMembershipExpirationTime()
+ */
+ @Property(
+ label = "User Membership Expiration",
+ description = "Time in milliseconds after which membership expires.",
+ longValue = PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT
+ )
+ public static final String PARAM_USER_MEMBERSHIP_EXPIRATION_TIME = "user.membershipExpTime";
+
+ /**
+ * @see User#getMembershipNestingDepth()
+ */
+ public static final int PARAM_USER_MEMBERSHIP_NESTING_DEPTH_DEFAULT = 0;
+
+ /**
+ * @see User#getMembershipNestingDepth()
+ */
+ @Property(
+ label = "User membership nesting depth",
+ description = "Returns the maximum depth of group nesting when membership relations are synced. " +
+ "A value of 0 effectively disables group membership lookup. A value of 1 only adds the direct " +
+ "groups of a user. This value has no effect when syncing individual groups only when syncing a " +
+ "users membership ancestry.",
+ intValue = PARAM_USER_MEMBERSHIP_NESTING_DEPTH_DEFAULT
+ )
+ public static final String PARAM_USER_MEMBERSHIP_NESTING_DEPTH = "user.membershipNestingDepth";
+
+ /**
+ * @see DefaultSyncConfig.Group#getExpirationTime()
+ */
+ public static final long PARAM_GROUP_EXPIRATION_TIME_DEFAULT = 24*3600*1000;
+
+ /**
+ * @see DefaultSyncConfig.Group#getExpirationTime()
+ */
+ @Property(
+ label = "Group Expiration Time",
+ description = "Duration in milliseconds until a synced group expires.",
+ longValue = PARAM_GROUP_EXPIRATION_TIME_DEFAULT
+ )
+ public static final String PARAM_GROUP_EXPIRATION_TIME = "group.expirationTime";
+
+ /**
+ * @see DefaultSyncConfig.Group#getAutoMembership()
+ */
+ public static final String[] PARAM_GROUP_AUTO_MEMBERSHIP_DEFAULT = {};
+
+ /**
+ * @see DefaultSyncConfig.Group#getAutoMembership()
+ */
+ @Property(
+ label = "Group auto membership",
+ description = "List of groups that a synced group is added to automatically",
+ value = {},
+ cardinality = Integer.MAX_VALUE
+ )
+ public static final String PARAM_GROUP_AUTO_MEMBERSHIP = "group.autoMembership";
+
+ /**
+ * @see DefaultSyncConfig.Group#getPropertyMapping()
+ */
+ public static final String[] PARAM_GROUP_PROPERTY_MAPPING_DEFAULT = {};
+
+ /**
+ * @see DefaultSyncConfig.Group#getPropertyMapping()
+ */
+ @Property(
+ label = "Group property mapping",
+ description = "List mapping definition of local properties from external ones.",
+ value = {},
+ cardinality = Integer.MAX_VALUE
+ )
+ public static final String PARAM_GROUP_PROPERTY_MAPPING = "group.propertyMapping";
+
+ /**
+ * @see DefaultSyncConfig.Group#getPathPrefix()
+ */
+ public static final String PARAM_GROUP_PATH_PREFIX_DEFAULT = "";
+
+ /**
+ * @see DefaultSyncConfig.Group#getPathPrefix()
+ */
+ @Property(
+ label = "Group Path Prefix",
+ description = "The path prefix used when creating new groups.",
+ value = PARAM_GROUP_PATH_PREFIX_DEFAULT
+ )
+ public static final String PARAM_GROUP_PATH_PREFIX = "group.pathPrefix";
+
+ /**
+ * Base config class for users and groups
+ */
+ public abstract static class Authorizable {
+
+ private long expirationTime;
+
+ private Set<String> autoMembership;
+
+ private Map<String, String> propertyMapping;
+
+ private String pathPrefix;
+
+ /**
+ * Returns the duration in milliseconds until a synced authorizable gets expired. An expired authorizable will
+ * be re-synced.
+ * @return the expiration time in milliseconds.
+ */
+ public long getExpirationTime() {
+ return expirationTime;
+ }
+
+ /**
+ * Sets the expiration time.
+ * @param expirationTime time in milliseconds.
+ * @return {@code this}
+ * @see #getExpirationTime()
+ */
+ @Nonnull
+ public Authorizable setExpirationTime(long expirationTime) {
+ this.expirationTime = expirationTime;
+ return this;
+ }
+
+ /**
+ * Defines the set of group names that are automatically added to synced authorizable.
+ * @return set of group names.
+ */
+ @Nonnull
+ public Set<String> getAutoMembership() {
+ return autoMembership;
+ }
+
+ /**
+ * Sets the auto membership
+ * @param autoMembership the membership
+ * @return {@code this}
+ * @see #getAutoMembership()
+ */
+ @Nonnull
+ public Authorizable setAutoMembership(String ... autoMembership) {
+ this.autoMembership = new HashSet<String>(Arrays.asList(autoMembership));
+ return this;
+ }
+
+ /**
+ * Defines the mapping of internal property names from external values. Only the external properties defined as
+ * keys of this map are synced with the mapped internal properties. note that the property names can be relative
+ * paths. the intermediate nodes will be created accordingly.
+ *
+ * Example:
+ * <xmp>
+ * {
+ * "rep:fullname": "cn",
+ * "country", "c",
+ * "profile/email": "mail",
+ * "profile/givenName": "cn"
+ * }
+ * </xmp>
+ *
+ * The implicit properties like userid, groupname, password must not be mapped.
+ *
+ * @return the property mapping where the keys are the local property names and the values the external ones.
+ */
+ @Nonnull
+ public Map<String, String> getPropertyMapping() {
+ return propertyMapping;
+ }
+
+ /**
+ * Sets the property mapping.
+ * @param propertyMapping the mapping
+ * @return {@code this}
+ * @see #getPropertyMapping()
+ */
+ @Nonnull
+ public Authorizable setPropertyMapping(Map<String, String> propertyMapping) {
+ this.propertyMapping = propertyMapping;
+ return this;
+ }
+
+ /**
+ * Defines the authorizables intermediate path prefix that is used when creating new authorizables. This prefix
+ * is always prepended to the path provided by the {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity}.
+ * @return the intermediate path prefix.
+ */
+ @Nonnull
+ public String getPathPrefix() {
+ return pathPrefix;
+ }
+
+ /**
+ * Sets the path prefix.
+ * @param pathPrefix the path prefix.
+ * @return {@code this}
+ * @see #getPathPrefix()
+ */
+ @Nonnull
+ public Authorizable setPathPrefix(String pathPrefix) {
+ this.pathPrefix = pathPrefix == null ? "" : pathPrefix;
+ return this;
+ }
+ }
+
+ /**
+ * User specific config.
+ */
+ public static class User extends Authorizable {
+
+ private long membershipExpirationTime;
+
+ private long membershipNestingDepth;
+
+ /**
+ * Returns the duration in milliseconds until the group membership of a user is expired. If the
+ * membership information is expired it is re-synced according to the maximum nesting depth.
+ * Note that the membership is the groups an authorizable is member of, not the list of members of a group.
+ * Also note, that the group membership expiration time can be higher than the user expiration time itself and
+ * that value has no effect when syncing individual groups only when syncing a users membership ancestry.
+ *
+ * @return the expiration time in milliseconds.
+ */
+ public long getMembershipExpirationTime() {
+ return membershipExpirationTime;
+ }
+
+ /**
+ * Sets the membership expiration time
+ * @param membershipExpirationTime the time in milliseconds.
+ * @return {@code this}
+ * @see #getMembershipExpirationTime()
+ */
+ @Nonnull
+ public User setMembershipExpirationTime(long membershipExpirationTime) {
+ this.membershipExpirationTime = membershipExpirationTime;
+ return this;
+ }
+
+ /**
+ * Returns the maximum depth of group nesting when membership relations are synced. A value of 0 effectively
+ * disables group membership lookup. A value of 1 only adds the direct groups of a user. This value has no effect
+ * when syncing individual groups only when syncing a users membership ancestry.
+ * @return the group nesting depth
+ */
+ public long getMembershipNestingDepth() {
+ return membershipNestingDepth;
+ }
+
+ /**
+ * Sets the group nesting depth.
+ * @param membershipNestingDepth the depth.
+ * @return {@code this}
+ * @see #getMembershipNestingDepth()
+ */
+ @Nonnull
+ public User setMembershipNestingDepth(long membershipNestingDepth) {
+ this.membershipNestingDepth = membershipNestingDepth;
+ return this;
+ }
+
+ }
+
+ /**
+ * Group specific config
+ */
+ public static class Group extends Authorizable {
+
+ }
+
+ /**
+ * Creates a new LDAP provider configuration based on the properties store in the given parameters.
+ * @param params the configuration parameters.
+ * @return the config
+ */
+ public static DefaultSyncConfig of(ConfigurationParameters params) {
+ DefaultSyncConfig cfg = new DefaultSyncConfig()
+ .setName(params.getConfigValue(PARAM_NAME, PARAM_NAME_DEFAULT));
+
+ cfg.user()
+ .setMembershipExpirationTime(params.getConfigValue(PARAM_USER_MEMBERSHIP_EXPIRATION_TIME, PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT))
+ .setMembershipNestingDepth(params.getConfigValue(PARAM_USER_MEMBERSHIP_NESTING_DEPTH, PARAM_USER_MEMBERSHIP_NESTING_DEPTH_DEFAULT))
+ .setExpirationTime(params.getConfigValue(PARAM_USER_EXPIRATION_TIME, PARAM_USER_EXPIRATION_TIME_DEFAULT))
+ .setPathPrefix(params.getConfigValue(PARAM_USER_PATH_PREFIX, PARAM_USER_PATH_PREFIX_DEFAULT))
+ .setAutoMembership(params.getConfigValue(PARAM_USER_AUTO_MEMBERSHIP, PARAM_USER_AUTO_MEMBERSHIP_DEFAULT))
+ .setPropertyMapping(createMapping(
+ params.getConfigValue(PARAM_USER_PROPERTY_MAPPING, PARAM_USER_PROPERTY_MAPPING_DEFAULT)));
+
+ cfg.group()
+ .setExpirationTime(params.getConfigValue(PARAM_GROUP_EXPIRATION_TIME, PARAM_GROUP_EXPIRATION_TIME_DEFAULT))
+ .setPathPrefix(params.getConfigValue(PARAM_GROUP_PATH_PREFIX, PARAM_GROUP_PATH_PREFIX_DEFAULT))
+ .setAutoMembership(params.getConfigValue(PARAM_GROUP_AUTO_MEMBERSHIP, PARAM_GROUP_AUTO_MEMBERSHIP_DEFAULT))
+ .setPropertyMapping(createMapping(
+ params.getConfigValue(PARAM_GROUP_PROPERTY_MAPPING, PARAM_GROUP_PROPERTY_MAPPING_DEFAULT)));
+
+ return cfg;
+ }
+
+ /**
+ * Creates a new property mapping map from a list of patterns.
+ * @param patterns the patterns
+ * @return the mapping map
+ */
+ private static Map<String, String> createMapping(String[] patterns) {
+ Map<String, String> mapping = new HashMap<String, String>();
+ for (String pattern: patterns) {
+ int idx = pattern.indexOf('=');
+ if (idx > 0) {
+ String relPath = pattern.substring(0, idx).trim();
+ String value = pattern.substring(idx+1).trim();
+ mapping.put(relPath, value);
+ }
+ }
+ return mapping;
+ }
+
+ private String name = PARAM_NAME_DEFAULT;
+
+ private final User user = new User();
+
+ private final Group group = new Group();
+
+ /**
+ * Configures the name of this configuration
+ * @return the name
+ */
+ @Nonnull
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name
+ * @param name the name
+ * @return {@code this}
+ * @see #getName()
+ */
+ public DefaultSyncConfig setName(@Nonnull String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Returns the sync configuration for users.
+ * @return the user sync configuration.
+ */
+ public User user() {
+ return user;
+ }
+
+ /**
+ * Returns the sync configuration for groups.
+ * @return the group sync configuration.
+ */
+ public Group group() {
+ return group;
+ }
+
+}
\ No newline at end of file
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java?rev=1566806&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java Mon Feb 10 22:57:58 2014
@@ -0,0 +1,298 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.oak.spi.security.authentication.external.impl;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Service;
+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.oak.api.Root;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.plugins.value.ValueFactoryImpl;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncContext;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncException;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@code DefaultSyncHandler} implements an sync handler that synchronizes users and groups from an external identity
+ * provider with the repository users.
+ * <p/>
+ * Please refer to {@link DefaultSyncConfig} for configuration options.
+ */
+@Component(
+ // note that the metatype information is generated from DefaultSyncConfig
+ policy = ConfigurationPolicy.REQUIRE
+)
+@Service
+public class DefaultSyncHandler implements SyncHandler {
+
+ /**
+ * logger instance
+ */
+ private static final Logger log = LoggerFactory.getLogger(DefaultSyncHandler.class);
+
+ /**
+ * internal configuration
+ */
+ private DefaultSyncConfig config;
+
+ /**
+ * Default constructor for OSGi
+ */
+ public DefaultSyncHandler() {
+ }
+
+ /**
+ * Constructor for non-OSGi cases.
+ *
+ * @param config the configuration
+ */
+ public DefaultSyncHandler(DefaultSyncConfig config) {
+ this.config = config;
+ }
+
+ @Activate
+ private void activate(Map<String, Object> properties) {
+ ConfigurationParameters cfg = ConfigurationParameters.of(properties);
+ config = DefaultSyncConfig.of(cfg);
+ }
+
+ @Nonnull
+ @Override
+ public String getName() {
+ return config.getName();
+ }
+
+ @Nonnull
+ @Override
+ public SyncContext createContext(@Nonnull ExternalIdentityProvider idp, @Nonnull UserManager userManager, @Nonnull Root root) throws SyncException {
+ return new ContextImpl(idp, userManager, root);
+ }
+
+ private class ContextImpl implements SyncContext {
+
+ private final ExternalIdentityProvider idp;
+
+ private final UserManager userManager;
+
+ private final Root root;
+
+ private final ValueFactory valueFactory;
+
+ private ContextImpl(ExternalIdentityProvider idp, UserManager userManager, Root root) {
+ this.idp = idp;
+ this.userManager = userManager;
+ this.root = root;
+ valueFactory = new ValueFactoryImpl(root, NamePathMapper.DEFAULT);
+ }
+
+ @Override
+ public void close() {
+ // nothing to do
+ }
+
+ @Override
+ public boolean sync(@Nonnull ExternalIdentity identity) throws SyncException {
+ try {
+ if (identity instanceof ExternalUser) {
+ User user = getUser(identity);
+ if (user == null) {
+ createUser((ExternalUser) identity);
+ } else {
+ updateUser((ExternalUser) identity, user);
+ }
+ return true;
+ } else if (identity instanceof ExternalGroup) {
+ // todo
+ return false;
+
+ } else {
+ throw new IllegalArgumentException("identity must be user or group but was: " + identity);
+ }
+ } catch (RepositoryException e) {
+ throw new SyncException(e);
+ }
+ }
+
+ @CheckForNull
+ private User getUser(@Nonnull ExternalIdentity externalUser) throws RepositoryException {
+ Authorizable authorizable = userManager.getAuthorizable(externalUser.getId());
+ if (authorizable == null) {
+ authorizable = userManager.getAuthorizable(externalUser.getPrincipalName());
+ }
+ if (authorizable == null) {
+ return null;
+ } else if (authorizable instanceof User) {
+ return (User) authorizable;
+ } else {
+ // TODO: deal with colliding authorizable that is group.
+ log.warn("unexpected authorizable: {}", authorizable);
+ return null;
+ }
+ }
+
+ @CheckForNull
+ private User createUser(ExternalUser externalUser) throws RepositoryException, SyncException {
+ String password = externalUser.getPassword(); // todo: make configurable
+ Principal principal = new PrincipalImpl(externalUser.getPrincipalName());
+ User user = userManager.createUser(
+ externalUser.getId(),
+ password,
+ principal,
+ concatPaths(config.user().getPathPrefix(), externalUser.getIntermediatePath())
+ );
+ syncAuthorizable(externalUser, user);
+ return user;
+ }
+
+ @CheckForNull
+ private Group createGroup(ExternalGroup externalGroup) throws RepositoryException, SyncException {
+ Principal principal = new PrincipalImpl(externalGroup.getPrincipalName());
+ Group group = userManager.createGroup(
+ externalGroup.getId(),
+ principal,
+ concatPaths(config.user().getPathPrefix(), externalGroup.getIntermediatePath()));
+ syncAuthorizable(externalGroup, group);
+ return group;
+ }
+
+ private void updateUser(ExternalUser externalUser, User user) throws RepositoryException, SyncException {
+ syncAuthorizable(externalUser, user);
+ }
+
+ private void syncAuthorizable(ExternalIdentity externalUser, Authorizable authorizable) throws RepositoryException, SyncException {
+ for (ExternalGroup externalGroup : externalUser.getGroups()) {
+ String groupId = externalGroup.getId();
+ Group group;
+ Authorizable a = userManager.getAuthorizable(groupId);
+ if (a == null) {
+ group = createGroup(externalGroup);
+ } else {
+ group = (a.isGroup()) ? (Group) a : null;
+ }
+
+ if (group != null) {
+ group.addMember(authorizable);
+ } else {
+ log.debug("No such group " + groupId + "; Ignoring group membership.");
+ }
+ }
+
+ Map<String, ?> properties = externalUser.getProperties();
+ for (String key : properties.keySet()) {
+ Object prop = properties.get(key);
+ if (prop instanceof Collection) {
+ Value[] values = createValues((Collection) prop);
+ if (values != null) {
+ authorizable.setProperty(key, values);
+ }
+ } else {
+ Value value = createValue(prop);
+ if (value != null) {
+ authorizable.setProperty(key, value);
+ }
+ }
+ }
+ }
+
+ @CheckForNull
+ private Value createValue(Object propValue) throws ValueFormatException {
+ int type = getType(propValue);
+ if (type == PropertyType.UNDEFINED) {
+ return null;
+ } else {
+ return valueFactory.createValue(propValue.toString(), type);
+ }
+ }
+
+ @CheckForNull
+ private Value[] createValues(Collection<?> propValues) throws ValueFormatException {
+ List<Value> values = new ArrayList<Value>();
+ for (Object obj : propValues) {
+ Value v = createValue(obj);
+ if (v != null) {
+ values.add(v);
+ }
+ }
+ return values.toArray(new Value[values.size()]);
+ }
+
+ private int getType(Object propValue) {
+ // TODO: add proper type detection
+ if (propValue == null) {
+ return PropertyType.UNDEFINED;
+ } else {
+ return PropertyType.STRING;
+ }
+ }
+
+ }
+
+ /**
+ * Robust relative path concatenation.
+ * @param paths relative paths
+ * @return the concatenated path
+ */
+ private static String concatPaths(String ... paths) {
+ StringBuilder result = new StringBuilder();
+ for (String path: paths) {
+ if (path != null && !path.isEmpty()) {
+ int i0 = 0;
+ int i1 = path.length();
+ while (i0 < i1 && path.charAt(i0) == '/') {
+ i0++;
+ }
+ while (i1 > i0 && path.charAt(i1-1) == '/') {
+ i1--;
+ }
+ if (i1 > i0) {
+ if (result.length() > 0) {
+ result.append('/');
+ }
+ result.append(path.substring(i0, i1));
+ }
+ }
+ }
+ return result.length() == 0 ? null : result.toString();
+ }
+}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java Mon Feb 10 22:57:58 2014
@@ -20,7 +20,6 @@ import java.util.Collections;
import java.util.Map;
import java.util.Set;
-import javax.annotation.Nonnull;
import javax.jcr.Credentials;
import javax.jcr.SimpleCredentials;
import javax.security.auth.Subject;
@@ -32,14 +31,14 @@ import org.apache.jackrabbit.oak.api.Com
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule;
-import org.apache.jackrabbit.oak.spi.security.authentication.external.DefaultSyncHandler;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProviderManager;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncContext;
import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncException;
import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
-import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncMode;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,11 +49,6 @@ public class ExternalLoginModule extends
private static final Logger log = LoggerFactory.getLogger(ExternalLoginModule.class);
- public static final SyncMode DEFAULT_SYNC_MODE = SyncMode.DEFAULT_SYNC;
-
- private static final String PARAM_SYNC_HANDLER = "syncHandler";
- private static final String DEFAULT_SYNC_HANDLER = DefaultSyncHandler.class.getName();
-
/**
* Name of the parameter that configures the name of the external identity provider.
*/
@@ -66,11 +60,6 @@ public class ExternalLoginModule extends
public static final String PARAM_SYNC_HANDLER_NAME = "sync.handlerName";
/**
- * Name of the parameter that configures the synchronization mode.
- */
- public static final String PARAM_SYNC_MODE = "sync.mode";
-
- /**
* internal configuration when invoked from a factory rather than jaas
*/
private ConfigurationParameters osgiConfig;
@@ -81,6 +70,11 @@ public class ExternalLoginModule extends
private ExternalIdentityProvider idp;
/**
+ * The configured sync handler as specified by the {@link #PARAM_SYNC_HANDLER_NAME}
+ */
+ private SyncHandler syncHandler;
+
+ /**
* The external user as resolved in the login call.
*/
private ExternalUser externalUser;
@@ -118,23 +112,38 @@ public class ExternalLoginModule extends
log.error("No IDP found with name {}. Will not be used for login.", idpName);
}
}
+
+ String syncHandlerName = options.getConfigValue(PARAM_SYNC_HANDLER_NAME, "");
+ if (syncHandlerName.length() == 0) {
+ log.error("External login module needs SyncHandler name. Will not be used for login.");
+ } else {
+ SyncManager syncMgr = getSecurityProvider().getConfiguration(SyncManager.class);
+ syncHandler = syncMgr.getSyncHandler(syncHandlerName);
+ if (syncHandler == null) {
+ log.error("No SyncHandler found with name {}. Will not be used for login.", idpName);
+ }
+ }
}
@Override
public boolean login() throws LoginException {
- if (idp == null) {
+ if (idp == null || syncHandler == null) {
return false;
}
Credentials credentials = getCredentials();
if (credentials == null) {
- log.info("No credentials found for external login module. ignoring.");
+ log.debug("No credentials found for external login module. ignoring.");
return false;
}
try {
externalUser = idp.authenticate(credentials);
if (externalUser != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("IDP {} returned valid user {}", idp.getName(), externalUser);
+ }
+
//noinspection unchecked
sharedState.put(SHARED_KEY_CREDENTIALS, credentials);
@@ -142,11 +151,24 @@ public class ExternalLoginModule extends
sharedState.put(SHARED_KEY_LOGIN_NAME, externalUser.getId());
return true;
+ } else {
+ if (log.isDebugEnabled()) {
+ if (credentials instanceof SimpleCredentials) {
+ log.debug("IDP {} returned null for simple creds of {}", idp.getName(), ((SimpleCredentials) credentials).getUserID());
+ } else {
+ log.debug("IDP {} returned null for {}", idp.getName(), credentials);
+ }
+ }
}
} catch (ExternalIdentityException e) {
log.error("Error while authenticating credentials {} with {}: {}", new Object[]{
credentials, idp.getName(), e.toString()});
return false;
+ } catch (LoginException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("IDP {} throws login exception for {}", idp.getName(), credentials);
+ }
+ throw e;
}
return false;
}
@@ -165,56 +187,28 @@ public class ExternalLoginModule extends
@Override
public boolean commit() throws LoginException {
- if (externalUser == null) {
+ if (externalUser == null || syncHandler == null) {
return false;
}
+ SyncContext context = null;
try {
- SyncHandler handler = getSyncHandler();
Root root = getRoot();
UserManager userManager = getUserManager();
if (root == null || userManager == null) {
throw new LoginException("Cannot synchronize user.");
}
- Object smValue = options.getConfigValue(PARAM_SYNC_MODE, null, null);
- SyncMode syncMode;
- if (smValue == null) {
- syncMode = DEFAULT_SYNC_MODE;
- } else {
- syncMode = SyncMode.fromObject(smValue);
- }
- if (externalUser != null && handler.initialize(userManager, root, syncMode, options)) {
- handler.sync(externalUser);
- root.commit();
- return true;
- } else {
- log.warn("Failed to initialize sync handler.");
- return false;
- }
+ context = syncHandler.createContext(idp, userManager, root);
+ context.sync(externalUser);
+ root.commit();
+ return true;
} catch (SyncException e) {
throw new LoginException("User synchronization failed: " + e);
} catch (CommitFailedException e) {
throw new LoginException("User synchronization failed: " + e);
- }
- }
-
- @Nonnull
- protected SyncHandler getSyncHandler() throws SyncException {
- Object syncHandler = options.getConfigValue(PARAM_SYNC_HANDLER, null, null);
- if (syncHandler == null) {
- return new DefaultSyncHandler();
- } else if (syncHandler instanceof SyncHandler) {
- return (SyncHandler) syncHandler;
- } else {
- try {
- Object sh = Class.forName(syncHandler.toString()).newInstance();
- if (sh instanceof SyncHandler) {
- return (SyncHandler) sh;
- } else {
- throw new SyncException("Invalid SyncHandler configuration: " + sh);
- }
- } catch (Exception e) {
- throw new SyncException("Error while getting SyncHandler:", e);
+ } finally {
+ if (context != null) {
+ context.close();
}
}
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleFactory.java Mon Feb 10 22:57:58 2014
@@ -79,14 +79,6 @@ public class ExternalLoginModuleFactory
)
public static final String PARAM_SYNC_HANDLER_NAME = ExternalLoginModule.PARAM_SYNC_HANDLER_NAME;
- @Property(
- value = "default",
- label = "Sync Mode",
- // TODO!!!
- description = "a comma separated list of sync modes. allowed values: 'user','group','default','update'"
- )
- public static final String PARAM_SYNC_MODE = ExternalLoginModule.PARAM_SYNC_MODE;
-
/**
* default configuration for the login modules
*/
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java?rev=1566806&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java Mon Feb 10 22:57:58 2014
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.oak.spi.security.authentication.external.impl;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Nonnull;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncManager;
+
+/**
+ * {@code SyncManagerImpl} is used to manage registered sync handlers. This class automatically
+ * tracks the SyncHandlers that are registered via OSGi but can also be used in non-OSGi environments by manually
+ * adding and removing the handlers.
+ */
+@Component
+@Service
+public class SyncManagerImpl implements SyncManager {
+
+ @Reference(
+ name = "syncHandler",
+ bind = "addHandler",
+ unbind = "removeHandler",
+ referenceInterface = SyncHandler.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC
+ )
+ final private Map<String, SyncHandler> handlers = new ConcurrentHashMap<String, SyncHandler>();
+
+ public void addHandler(SyncHandler handler, final Map<String, Object> props) {
+ handlers.put(handler.getName(), handler);
+ }
+
+ public void removeHandler(SyncHandler handler, final Map<String, Object> props) {
+ handlers.remove(handler.getName());
+ }
+
+ @Override
+ public SyncHandler getSyncHandler(@Nonnull String name) {
+ return handlers.get(name);
+ }
+}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestLoginModule.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestLoginModule.java?rev=1566806&r1=1566805&r2=1566806&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestLoginModule.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestLoginModule.java Mon Feb 10 22:57:58 2014
@@ -102,7 +102,7 @@ public class TestLoginModule extends Ext
}
@Override
- public Principal getPrincipal() {
+ public Principal getPrincipalName() {
return new PrincipalImpl(userId);
}