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/11 01:18:09 UTC

svn commit: r1566885 - in /jackrabbit/oak/trunk: oak-auth-ldap/ oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/ oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/ oak-core/ oak-c...

Author: tripod
Date: Tue Feb 11 00:18:08 2014
New Revision: 1566885

URL: http://svn.apache.org/r1566885
Log:
OAK-516 Create LdapLoginModule based on ExternalLoginModule (wip)

Added:
    jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/InternalLdapServer.java
    jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginStandaloneTest.java
    jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginTestBase.java
    jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginWithRepoLoginTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java
Removed:
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/
    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
    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/LdapProviderConfig.java
    jackrabbit/oak/trunk/oak-core/pom.xml
    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/impl/DefaultSyncHandler.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIDPManagerImpl.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/SyncManagerImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUtils.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTest.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=1566885&r1=1566884&r2=1566885&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-auth-ldap/pom.xml Tue Feb 11 00:18:08 2014
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.jackrabbit</groupId>
         <artifactId>oak-parent</artifactId>
-        <version>0.16-SNAPSHOT</version>
+        <version>0.17-SNAPSHOT</version>
         <relativePath>../oak-parent/pom.xml</relativePath>
     </parent>
 
@@ -212,5 +212,13 @@
             <version>1.5.5</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-core</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 </project>

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=1566885&r1=1566884&r2=1566885&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 Tue Feb 11 00:18:08 2014
@@ -86,7 +86,7 @@ public abstract class LdapIdentity imple
      */
     @Nonnull
     @Override
-    public Iterable<? extends ExternalGroup> getGroups() {
+    public Iterable<? extends ExternalIdentityRef> getGroups() {
         return Collections.emptyList();
     }
 

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=1566885&r1=1566884&r2=1566885&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 Tue Feb 11 00:18:08 2014
@@ -573,9 +573,19 @@ public class LdapProviderConfig {
 
     private String groupMemberAttribute = PARAM_GROUP_MEMBER_ATTRIBUTE;
 
-    private final Identity userConfig = new Identity();
-
-    private final Identity groupConfig = new Identity();
+    private final Identity userConfig = new Identity()
+            .setBaseDN(PARAM_USER_BASE_DN_DEFAULT)
+            .setExtraFilter(PARAM_USER_EXTRA_FILTER_DEFAULT)
+            .setIdAttribute(PARAM_USER_ID_ATTRIBUTE_DEFAULT)
+            .setMakeDnPath(PARAM_USER_MAKE_DN_PATH_DEFAULT)
+            .setObjectClasses(PARAM_USER_OBJECTCLASS_DEFAULT);
+
+    private final Identity groupConfig = new Identity()
+            .setBaseDN(PARAM_GROUP_BASE_DN_DEFAULT)
+            .setExtraFilter(PARAM_GROUP_EXTRA_FILTER_DEFAULT)
+            .setIdAttribute(PARAM_GROUP_NAME_ATTRIBUTE_DEFAULT)
+            .setMakeDnPath(PARAM_GROUP_MAKE_DN_PATH_DEFAULT)
+            .setObjectClasses(PARAM_GROUP_OBJECTCLASS_DEFAULT);
 
     /**
      * Returns the name of this provider configuration.

Added: jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/InternalLdapServer.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/InternalLdapServer.java?rev=1566885&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/InternalLdapServer.java (added)
+++ jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/InternalLdapServer.java Tue Feb 11 00:18:08 2014
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import java.io.File;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.directory.server.unit.AbstractServerTest;
+
+class InternalLdapServer extends AbstractServerTest {
+
+    public static final String GROUP_MEMBER_ATTR = "member";
+    public static final String GROUP_CLASS_ATTR = "groupOfNames";
+
+    public static final String ADMIN_PW = "secret";
+
+    public void setUp() throws Exception {
+        super.setUp();
+        doDelete = true;
+    }
+
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Override
+    protected void configureDirectoryService() throws Exception {
+        directoryService.setWorkingDirectory(new File("target", "apacheds"));
+        doDelete(directoryService.getWorkingDirectory());
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public String addUser(String firstName, String lastName, String userId, String password)
+            throws Exception {
+        String cn = firstName + ' ' + lastName;
+        String dn = buildDn(cn, false);
+        StringBuilder entries = new StringBuilder();
+        entries.append("dn: ").append(dn).append('\n')
+                .append("objectClass: inetOrgPerson\n").append("cn: ").append(cn)
+                .append('\n').append("sn: ").append(lastName)
+                .append('\n').append("givenName:").append(firstName)
+                .append('\n').append("uid: ").append(userId)
+                .append('\n').append("userPassword: ").append(password).append("\n\n");
+        injectEntries(entries.toString());
+        return dn;
+    }
+
+    public String addGroup(String name) throws Exception {
+        String dn = buildDn(name, true);
+        StringBuilder entries = new StringBuilder();
+        entries.append("dn: ").append(dn).append('\n').append("objectClass: ")
+                .append(GROUP_CLASS_ATTR).append('\n').append(GROUP_MEMBER_ATTR)
+                .append(":\n").append("cn: ").append(name).append("\n\n");
+        injectEntries(entries.toString());
+        return dn;
+    }
+
+    public void addMember(String groupDN, String memberDN) throws Exception {
+        LdapContext ctxt = getWiredContext();
+        BasicAttributes attrs = new BasicAttributes();
+        attrs.put("member", memberDN);
+        ctxt.modifyAttributes(groupDN, DirContext.ADD_ATTRIBUTE, attrs);
+    }
+
+    public void removeMember(String groupDN, String memberDN) throws Exception {
+        LdapContext ctxt = getWiredContext();
+        BasicAttributes attrs = new BasicAttributes();
+        attrs.put("member", memberDN);
+        ctxt.modifyAttributes(groupDN, DirContext.REMOVE_ATTRIBUTE, attrs);
+    }
+
+    private static String buildDn(String name, boolean isGroup) {
+        StringBuilder dn = new StringBuilder();
+        dn.append("cn=").append(name).append(',');
+        if (isGroup) {
+            dn.append(ServerDNConstants.GROUPS_SYSTEM_DN);
+        } else {
+            dn.append(ServerDNConstants.USERS_SYSTEM_DN);
+        }
+        return dn.toString();
+    }
+}

Added: jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginStandaloneTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginStandaloneTest.java?rev=1566885&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginStandaloneTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginStandaloneTest.java Tue Feb 11 00:18:08 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.security.authentication.ldap;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule;
+import org.junit.Ignore;
+
+@Ignore //ignore for the moment because "mvn test" runs into PermGen memory issues
+public class LdapLoginStandaloneTest extends LdapLoginTestBase {
+
+    @Override
+    protected Configuration getConfiguration() {
+        return new Configuration() {
+            @Override
+            public AppConfigurationEntry[] getAppConfigurationEntry(String s) {
+                return new AppConfigurationEntry[]{
+                        new AppConfigurationEntry(
+                                ExternalLoginModule.class.getName(),
+                                AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+                                options)
+                };
+            }
+        };
+    }
+}

Added: jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginTestBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginTestBase.java?rev=1566885&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginTestBase.java (added)
+++ jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginTestBase.java Tue Feb 11 00:18:08 2014
@@ -0,0 +1,371 @@
+/*
+ * 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;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.LoginException;
+
+import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.api.AuthInfo;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider;
+import org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapProviderConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalLoginModuleTestBase;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public abstract class LdapLoginTestBase extends ExternalLoginModuleTestBase {
+
+    protected static final InternalLdapServer LDAP_SERVER = new InternalLdapServer();
+
+    protected static final String USER_ID = "foobar";
+    protected static final String USER_PWD = "foobar";
+    protected static final String USER_FIRSTNAME = "Foo";
+    protected static final String USER_LASTNAME = "Bar";
+    protected static final String USER_ATTR = "givenName";
+    protected static final String USER_PROP = "profile/name";
+    protected static final String GROUP_PROP = "profile/member";
+    protected static final String GROUP_NAME = "foobargroup";
+
+    protected static String GROUP_DN;
+
+    protected static int CONCURRENT_LOGINS = 10;
+
+    //initialize LDAP server only once (fast, but might turn out to be not sufficiently flexible in the future)
+    protected static final boolean USE_COMMON_LDAP_FIXTURE = false;
+
+    protected final HashMap<String, Object> options = new HashMap<String, Object>();
+
+    protected UserManager userManager;
+
+    protected LdapIdentityProvider idp;
+
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        if (USE_COMMON_LDAP_FIXTURE) {
+            LDAP_SERVER.setUp();
+            createLdapFixture();
+        }
+    }
+
+    @AfterClass
+    public static void afterClass() throws Exception {
+        if (USE_COMMON_LDAP_FIXTURE) {
+            LDAP_SERVER.tearDown();
+        }
+    }
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        if (!USE_COMMON_LDAP_FIXTURE) {
+            LDAP_SERVER.setUp();
+            createLdapFixture();
+        }
+
+        UserConfiguration uc = securityProvider.getConfiguration(UserConfiguration.class);
+        userManager = uc.getUserManager(root, NamePathMapper.DEFAULT);
+    }
+
+    @After
+    public void after() throws Exception {
+
+        if (!USE_COMMON_LDAP_FIXTURE) {
+            LDAP_SERVER.tearDown();
+        }
+
+        try {
+            Authorizable a = userManager.getAuthorizable(USER_ID);
+            if (a != null) {
+                a.remove();
+            }
+            if (GROUP_DN != null) {
+                a = userManager.getAuthorizable(GROUP_DN);
+                if (a != null) {
+                    a.remove();
+                }
+            }
+            root.commit();
+        } finally {
+            root.refresh();
+            super.after();
+        }
+    }
+
+    @Override
+    protected ExternalIdentityProvider createIDP() {
+        LdapProviderConfig cfg = new LdapProviderConfig()
+                .setName("ldap")
+                .setHostname("127.0.0.1")
+                .setPort(LDAP_SERVER.getPort())
+                .setBindDN(ServerDNConstants.ADMIN_SYSTEM_DN)
+                .setBindPassword(InternalLdapServer.ADMIN_PW)
+                .setGroupMemberAttribute(InternalLdapServer.GROUP_MEMBER_ATTR);
+
+        cfg.getUserConfig()
+                .setBaseDN(ServerDNConstants.USERS_SYSTEM_DN)
+                .setObjectClasses("inetOrgPerson");
+        cfg.getGroupConfig()
+                .setBaseDN(ServerDNConstants.GROUPS_SYSTEM_DN)
+                .setObjectClasses(InternalLdapServer.GROUP_CLASS_ATTR);
+
+        return new LdapIdentityProvider(cfg);
+    }
+
+    @Test
+    public void testLoginFailed() throws Exception {
+        try {
+            ContentSession cs = login(new SimpleCredentials(USER_ID, new char[0]));
+            cs.close();
+            fail("login failure expected");
+        } catch (LoginException e) {
+            // success
+        } finally {
+            assertNull(userManager.getAuthorizable(USER_ID));
+        }
+    }
+
+    @Test
+    public void testSyncCreateUser() throws Exception {
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, USER_PWD.toCharArray()));
+
+            root.refresh();
+            Authorizable user = userManager.getAuthorizable(USER_ID);
+            assertNotNull(user);
+            assertTrue(user.hasProperty(USER_PROP));
+            Tree userTree = cs.getLatestRoot().getTree(user.getPath());
+            assertFalse(userTree.hasProperty(UserConstants.REP_PASSWORD));
+
+            assertNull(userManager.getAuthorizable(GROUP_DN));
+        } finally {
+            if (cs != null) {
+                cs.close();
+            }
+            options.clear();
+        }
+    }
+
+    @Test
+    public void testSyncCreateGroup() throws Exception {
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, USER_PWD.toCharArray()));
+
+            root.refresh();
+            assertNull(userManager.getAuthorizable(USER_ID));
+            assertNull(userManager.getAuthorizable(GROUP_DN));
+        } finally {
+            if (cs != null) {
+                cs.close();
+            }
+            options.clear();
+        }
+    }
+
+    @Test
+    public void testSyncUpdate() throws Exception {
+        // create user upfront in order to test update mode
+        userManager.createUser(USER_ID, null);
+        root.commit();
+
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, USER_PWD.toCharArray()));
+
+            root.refresh();
+            Authorizable user = userManager.getAuthorizable(USER_ID);
+            assertNotNull(user);
+            assertTrue(user.hasProperty(USER_PROP));
+            assertNull(userManager.getAuthorizable(GROUP_DN));
+        } finally {
+            if (cs != null) {
+                cs.close();
+            }
+            options.clear();
+        }
+    }
+
+    @Test
+    public void testLoginSetsAuthInfo() throws Exception {
+        ContentSession cs = null;
+        try {
+            SimpleCredentials sc = new SimpleCredentials(USER_ID, USER_PWD.toCharArray());
+            sc.setAttribute("attr", "val");
+
+            cs = login(sc);
+            AuthInfo ai = cs.getAuthInfo();
+
+            assertEquals(USER_ID, ai.getUserID());
+            assertEquals("val", ai.getAttribute("attr"));
+        } finally {
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    @Test
+    public void testPrincipalsFromAuthInfo() throws Exception {
+        ContentSession cs = null;
+        try {
+            SimpleCredentials sc = new SimpleCredentials(USER_ID, USER_PWD.toCharArray());
+            sc.setAttribute("attr", "val");
+
+            cs = login(sc);
+            AuthInfo ai = cs.getAuthInfo();
+
+            root.refresh();
+            PrincipalProvider pp = getSecurityProvider().getConfiguration(PrincipalConfiguration.class).getPrincipalProvider(root, NamePathMapper.DEFAULT);
+            Set<? extends Principal> expected = pp.getPrincipals(USER_ID);
+            assertEquals(2, expected.size());
+            assertEquals(expected, ai.getPrincipals());
+
+        } finally {
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    @Test
+    public void testPrincipalsFromAuthInfo2() throws Exception {
+        ContentSession cs = null;
+        try {
+            SimpleCredentials sc = new SimpleCredentials(USER_ID, USER_PWD.toCharArray());
+            sc.setAttribute("attr", "val");
+
+            cs = login(sc);
+            AuthInfo ai = cs.getAuthInfo();
+
+            root.refresh();
+            PrincipalProvider pp = getSecurityProvider().getConfiguration(PrincipalConfiguration.class).getPrincipalProvider(root, NamePathMapper.DEFAULT);
+            Set<? extends Principal> expected = pp.getPrincipals(USER_ID);
+            assertEquals(3, expected.size());
+            assertEquals(expected, ai.getPrincipals());
+
+        } finally {
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    @Test
+    public void testReLogin() throws Exception {
+        ContentSession cs = null;
+        try {
+            cs = login(new SimpleCredentials(USER_ID, USER_PWD.toCharArray()));
+
+            root.refresh();
+            Authorizable user = userManager.getAuthorizable(USER_ID);
+            assertNotNull(user);
+            assertFalse(root.getTree(user.getPath()).hasProperty(UserConstants.REP_PASSWORD));
+
+            cs.close();
+            // login again
+            cs = login(new SimpleCredentials(USER_ID, USER_PWD.toCharArray()));
+            assertEquals(USER_ID, cs.getAuthInfo().getUserID());
+        } finally {
+            if (cs != null) {
+                cs.close();
+            }
+            options.clear();
+        }
+    }
+
+    @Ignore // FIXME
+    @Test
+    public void testConcurrentLogin() throws Exception {
+        concurrentLogin(false);
+    }
+
+    @Ignore // FIXME
+    @Test
+    public void testConcurrentLoginSameGroup() throws Exception {
+        concurrentLogin(true);
+    }
+
+    private void concurrentLogin(boolean sameGroup) throws Exception {
+        final List<Exception> exceptions = new ArrayList<Exception>();
+        List<Thread> workers = new ArrayList<Thread>();
+        for (int i = 0; i < CONCURRENT_LOGINS; i++) {
+            final String userId = "user-" + i;
+            final String pass = "secret";
+            String userDN = LDAP_SERVER.addUser(userId, "test", userId, pass);
+            if (sameGroup) {
+                LDAP_SERVER.addMember(GROUP_DN, userDN);
+            }
+            workers.add(new Thread(new Runnable() {
+                public void run() {
+                    try {
+                        login(new SimpleCredentials(
+                                userId, pass.toCharArray())).close();
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
+                }
+            }));
+        }
+        for (Thread t : workers) {
+            t.start();
+        }
+        for (Thread t : workers) {
+            t.join();
+        }
+        for (Exception e : exceptions) {
+            e.printStackTrace();
+        }
+        if (!exceptions.isEmpty()) {
+            throw exceptions.get(0);
+        }
+    }
+
+    protected static void createLdapFixture() throws Exception {
+        LDAP_SERVER.addMember(
+                GROUP_DN = LDAP_SERVER.addGroup(GROUP_NAME),
+                LDAP_SERVER.addUser(USER_FIRSTNAME, USER_LASTNAME, USER_ID, USER_PWD));
+    }
+}

Added: jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginWithRepoLoginTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginWithRepoLoginTest.java?rev=1566885&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginWithRepoLoginTest.java (added)
+++ jackrabbit/oak/trunk/oak-auth-ldap/src/test/java/org/apache/jackrabbit/oak/security/authentication/ldap/LdapLoginWithRepoLoginTest.java Tue Feb 11 00:18:08 2014
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+
+import org.apache.jackrabbit.oak.security.authentication.user.LoginModuleImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule;
+import org.junit.Ignore;
+
+@Ignore //ignore for the moment because "mvn test" runs into PermGen memory issues
+public class LdapLoginWithRepoLoginTest extends LdapLoginTestBase {
+
+    @Override
+    protected Configuration getConfiguration() {
+        return new Configuration() {
+            @Override
+            public AppConfigurationEntry[] getAppConfigurationEntry(String s) {
+                return new AppConfigurationEntry[]{
+                        new AppConfigurationEntry(
+                                LoginModuleImpl.class.getName(),
+                                AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT,
+                                Collections.<String, Object>emptyMap()),
+                        new AppConfigurationEntry(
+                                ExternalLoginModule.class.getName(),
+                                AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+                                options)
+                };
+            }
+        };
+    }
+}

Modified: jackrabbit/oak/trunk/oak-core/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/pom.xml?rev=1566885&r1=1566884&r2=1566885&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-core/pom.xml Tue Feb 11 00:18:08 2014
@@ -338,9 +338,9 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.apache.directory.server</groupId>
-      <artifactId>apacheds-server-unit</artifactId>
-      <version>1.5.5</version>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>2.4</version>
       <scope>test</scope>
     </dependency>
   </dependencies>

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=1566885&r1=1566884&r2=1566885&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 Tue Feb 11 00:18:08 2014
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.security;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -43,9 +44,9 @@ 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.ExternalIdentityProviderManager;
 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;
@@ -57,6 +58,7 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.security.user.AuthorizableNodeName;
 import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
+import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard;
 import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
 import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardAuthorizableActionProvider;
 import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardRestrictionProvider;
@@ -114,16 +116,12 @@ public class SecurityProviderImpl implem
 
     private ConfigurationParameters configuration;
 
-    @Reference
-    private ExternalIdentityProviderManager identityProviderManager;
-
-    @Reference
-    private SyncManager syncManager;
+    private Whiteboard whiteboard = new DefaultWhiteboard();
 
     /**
      * Default constructor used in OSGi environments.
      */
-    public SecurityProviderImpl() {
+    protected SecurityProviderImpl() {
         this(ConfigurationParameters.EMPTY);
     }
 
@@ -140,8 +138,10 @@ public class SecurityProviderImpl implem
         principalConfiguration = new PrincipalConfigurationImpl(this);
         privilegeConfiguration = new PrivilegeConfigurationImpl();
         tokenConfiguration = new TokenConfigurationImpl(this);
-        identityProviderManager = new ExternalIDPManagerImpl();
-        syncManager = new SyncManagerImpl();
+
+        // register non-OSGi managers
+        whiteboard.register(SyncManager.class, new SyncManagerImpl(whiteboard), Collections.emptyMap());
+        whiteboard.register(ExternalIdentityProviderManager.class, new ExternalIDPManagerImpl(whiteboard), Collections.emptyMap());
     }
 
     @Nonnull
@@ -188,10 +188,8 @@ public class SecurityProviderImpl implem
             return (T) privilegeConfiguration;
         } else if (TokenConfiguration.class == configClass) {
             return (T) tokenConfiguration;
-        } else if (ExternalIdentityProviderManager.class == configClass) {
-            return (T) identityProviderManager;
-        } else if (SyncManager.class == configClass) {
-            return (T) syncManager;
+        } else if (Whiteboard.class == configClass) {
+            return (T) whiteboard;
         } else {
             throw new IllegalArgumentException("Unsupported security configuration class " + configClass);
         }
@@ -199,7 +197,7 @@ public class SecurityProviderImpl implem
 
     @Activate
     protected void activate(ComponentContext context) throws Exception {
-        Whiteboard whiteboard = new OsgiWhiteboard(context.getBundleContext());
+        whiteboard = new OsgiWhiteboard(context.getBundleContext());
         authorizableActionProvider.start(whiteboard);
         restrictionProvider.start(whiteboard);
     }

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=1566885&r1=1566884&r2=1566885&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 Tue Feb 11 00:18:08 2014
@@ -65,7 +65,7 @@ public interface ExternalIdentity {
      * @return the declared groups
      */
     @Nonnull
-    Iterable<? extends ExternalGroup> getGroups();
+    Iterable<? extends ExternalIdentityRef> getGroups();
 
     /**
      * Returns a map of properties of this external identity.

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=1566885&r1=1566884&r2=1566885&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 Tue Feb 11 00:18:08 2014
@@ -101,4 +101,21 @@ public class ExternalIdentityRef {
         sb.append('}');
         return sb.toString();
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ExternalIdentityRef that = (ExternalIdentityRef) o;
+
+        if (!string.equals(that.string)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return string.hashCode();
+    }
 }
\ No newline at end of file

Modified: 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=1566885&r1=1566884&r2=1566885&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java Tue Feb 11 00:18:08 2014
@@ -44,7 +44,9 @@ import org.apache.jackrabbit.oak.plugins
 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.ExternalIdentityException;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
 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;
@@ -151,6 +153,8 @@ public class DefaultSyncHandler implemen
                 }
             } catch (RepositoryException e) {
                 throw new SyncException(e);
+            } catch (ExternalIdentityException e) {
+                throw new SyncException(e);
             }
         }
 
@@ -172,7 +176,8 @@ public class DefaultSyncHandler implemen
         }
 
         @CheckForNull
-        private User createUser(ExternalUser externalUser) throws RepositoryException, SyncException {
+        private User createUser(ExternalUser externalUser)
+                throws RepositoryException, SyncException, ExternalIdentityException {
             String password = externalUser.getPassword(); // todo: make configurable
             Principal principal = new PrincipalImpl(externalUser.getPrincipalName());
             User user = userManager.createUser(
@@ -186,7 +191,8 @@ public class DefaultSyncHandler implemen
         }
 
         @CheckForNull
-        private Group createGroup(ExternalGroup externalGroup) throws RepositoryException, SyncException {
+        private Group createGroup(ExternalGroup externalGroup)
+                throws RepositoryException, SyncException, ExternalIdentityException {
             Principal principal = new PrincipalImpl(externalGroup.getPrincipalName());
             Group group = userManager.createGroup(
                     externalGroup.getId(),
@@ -196,25 +202,31 @@ public class DefaultSyncHandler implemen
             return group;
         }
 
-        private void updateUser(ExternalUser externalUser, User user) throws RepositoryException, SyncException {
+        private void updateUser(ExternalUser externalUser, User user)
+                throws RepositoryException, SyncException, ExternalIdentityException {
             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;
-                }
+        private void syncAuthorizable(ExternalIdentity externalUser, Authorizable authorizable)
+                throws RepositoryException, SyncException, ExternalIdentityException {
+            for (ExternalIdentityRef externalGroupRef : externalUser.getGroups()) {
+                ExternalIdentity id = idp.getIdentity(externalGroupRef);
+                if (id instanceof ExternalGroup) {
+                    ExternalGroup externalGroup = (ExternalGroup) id;
+                    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.");
+                    if (group != null) {
+                        group.addMember(authorizable);
+                    } else {
+                        log.debug("No such group " + groupId + "; Ignoring group membership.");
+                    }
                 }
             }
 

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIDPManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIDPManagerImpl.java?rev=1566885&r1=1566884&r2=1566885&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIDPManagerImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIDPManagerImpl.java Tue Feb 11 00:18:08 2014
@@ -16,48 +16,61 @@
  */
 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.Activate;
 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.Deactivate;
 import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
 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.whiteboard.AbstractServiceTracker;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.osgi.service.component.ComponentContext;
 
 /**
  * {@code ExternalIDPManagerImpl} is used to manage registered external identity provider. This class automatically
  * tracks the IDPs that are registered via OSGi but can also be used in non-OSGi environments by manually adding and
  * removing the providers.
  */
-@Component
+@Component(immediate = true)
 @Service
-public class ExternalIDPManagerImpl implements ExternalIdentityProviderManager {
+public class ExternalIDPManagerImpl extends AbstractServiceTracker<ExternalIdentityProvider> implements ExternalIdentityProviderManager {
+
+    /**
+     * Default constructor used by OSGi
+     */
+    public ExternalIDPManagerImpl() {
+        super(ExternalIdentityProvider.class);
+    }
 
-    @Reference(
-            name = "idpProvider",
-            bind = "addProvider",
-            unbind = "removeProvider",
-            referenceInterface = ExternalIdentityProvider.class,
-            cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
-            policy = ReferencePolicy.DYNAMIC
-    )
-    final private Map<String, ExternalIdentityProvider> providers = new ConcurrentHashMap<String, ExternalIdentityProvider>();
+    /**
+     * Constructor used by non OSGi
+     * @param whiteboard the whiteboard
+     */
+    public ExternalIDPManagerImpl(Whiteboard whiteboard) {
+        super(ExternalIdentityProvider.class);
+        start(whiteboard);
+    }
 
-    public void addProvider(ExternalIdentityProvider provider, final Map<String, Object> props) {
-        providers.put(provider.getName(), provider);
+    @Activate
+    private void activate(ComponentContext ctx) {
+        start(new OsgiWhiteboard(ctx.getBundleContext()));
     }
 
-    public void removeProvider(ExternalIdentityProvider provider, final Map<String, Object> props) {
-        providers.remove(provider.getName());
+    @Deactivate
+    private void deactivate() {
+        stop();
     }
 
     @Override
     public ExternalIdentityProvider getProvider(@Nonnull String name) {
-        return providers.get(name);
+        for (ExternalIdentityProvider provider: getServices()) {
+            if (name.equals(provider.getName())) {
+                return provider;
+            }
+        }
+        return null;
     }
 }
\ 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=1566885&r1=1566884&r2=1566885&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 Tue Feb 11 00:18:08 2014
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authentication.external.impl;
 
+import java.security.Principal;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
@@ -31,6 +32,7 @@ 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.AuthInfoImpl;
 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;
@@ -39,6 +41,8 @@ import org.apache.jackrabbit.oak.spi.sec
 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.SyncManager;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -80,6 +84,11 @@ public class ExternalLoginModule extends
     private ExternalUser externalUser;
 
     /**
+     * Login credentials
+     */
+    private Credentials credentials;
+
+    /**
      * Default constructor for the OSGIi LoginModuleFactory case and the default non-OSGi JAAS case.
      */
     public ExternalLoginModule() {
@@ -102,14 +111,20 @@ public class ExternalLoginModule extends
             options = ConfigurationParameters.of(osgiConfig, options);
         }
 
+        Whiteboard whiteboard = getSecurityProvider().getConfiguration(Whiteboard.class);
+
         String idpName = options.getConfigValue(PARAM_IDP_NAME, "");
         if (idpName.length() == 0) {
             log.error("External login module needs IPD name. Will not be used for login.");
         } else {
-            ExternalIdentityProviderManager idpMgr = getSecurityProvider().getConfiguration(ExternalIdentityProviderManager.class);
-            idp = idpMgr.getProvider(idpName);
-            if (idp == null) {
-                log.error("No IDP found with name {}. Will not be used for login.", idpName);
+            ExternalIdentityProviderManager idpMgr = WhiteboardUtils.getService(whiteboard, ExternalIdentityProviderManager.class);
+            if (idpMgr == null) {
+                log.error("External login module needs IDPManager. Will not be used for login.");
+            } else {
+                idp = idpMgr.getProvider(idpName);
+                if (idp == null) {
+                    log.error("No IDP found with name {}. Will not be used for login.", idpName);
+                }
             }
         }
 
@@ -117,10 +132,14 @@ public class ExternalLoginModule extends
         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);
+            SyncManager syncMgr = WhiteboardUtils.getService(whiteboard, SyncManager.class);
+            if (syncMgr == null) {
+                log.error("External login module needs SyncManager. Will not be used for login.");
+            } else {
+                syncHandler = syncMgr.getSyncHandler(syncHandlerName);
+                if (syncHandler == null) {
+                    log.error("No SyncHandler found with name {}. Will not be used for login.", syncHandlerName);
+                }
             }
         }
     }
@@ -131,7 +150,7 @@ public class ExternalLoginModule extends
             return false;
         }
 
-        Credentials credentials = getCredentials();
+        credentials = getCredentials();
         if (credentials == null) {
             log.debug("No credentials found for external login module. ignoring.");
             return false;
@@ -201,7 +220,19 @@ public class ExternalLoginModule extends
             context = syncHandler.createContext(idp, userManager, root);
             context.sync(externalUser);
             root.commit();
-            return true;
+
+            Set<? extends Principal> principals = getPrincipals(externalUser.getId());
+            if (!principals.isEmpty()) {
+                if (!subject.isReadOnly()) {
+                    subject.getPrincipals().addAll(principals);
+                    subject.getPublicCredentials().add(credentials);
+                    setAuthInfo(new AuthInfoImpl(externalUser.getId(), null, principals), subject);
+                } else {
+                    log.debug("Could not add information to read only subject {}", subject);
+                }
+                return true;
+            }
+            return false;
         } catch (SyncException e) {
             throw new LoginException("User synchronization failed: " + e);
         } catch (CommitFailedException e) {
@@ -217,5 +248,6 @@ public class ExternalLoginModule extends
     protected void clearState() {
         super.clearState();
         externalUser = null;
+        credentials = null;
     }
 }
\ No newline at end of file

Modified: 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=1566885&r1=1566884&r2=1566885&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/SyncManagerImpl.java Tue Feb 11 00:18:08 2014
@@ -22,43 +22,62 @@ import java.util.concurrent.ConcurrentHa
 
 import javax.annotation.Nonnull;
 
+import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
 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.osgi.OsgiWhiteboard;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncHandler;
 import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncManager;
+import org.apache.jackrabbit.oak.spi.whiteboard.AbstractServiceTracker;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.osgi.service.component.ComponentContext;
 
 /**
  * {@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
+@Component(immediate = true)
 @Service
-public class SyncManagerImpl implements SyncManager {
+public class SyncManagerImpl extends AbstractServiceTracker<SyncHandler> 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>();
+    /**
+     * Default constructor used by OSGi
+     */
+    public SyncManagerImpl() {
+        super(SyncHandler.class);
+    }
+
+    /**
+     * Constructor used by non OSGi
+     * @param whiteboard the whiteboard
+     */
+    public SyncManagerImpl(Whiteboard whiteboard) {
+        super(SyncHandler.class);
+        start(whiteboard);
+    }
 
-    public void addHandler(SyncHandler handler, final Map<String, Object> props) {
-        handlers.put(handler.getName(), handler);
+    @Activate
+    private void activate(ComponentContext ctx) {
+        start(new OsgiWhiteboard(ctx.getBundleContext()));
     }
 
-    public void removeHandler(SyncHandler handler, final Map<String, Object> props) {
-        handlers.remove(handler.getName());
+    @Deactivate
+    private void deactivate() {
+        stop();
     }
 
     @Override
     public SyncHandler getSyncHandler(@Nonnull String name) {
-        return handlers.get(name);
+        for (SyncHandler handler: getServices()) {
+            if (name.equals(handler.getName())) {
+                return handler;
+            }
+        }
+        return null;
     }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java?rev=1566885&r1=1566884&r2=1566885&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java Tue Feb 11 00:18:08 2014
@@ -18,6 +18,10 @@ package org.apache.jackrabbit.oak.spi.wh
 
 import java.util.List;
 
+import javax.annotation.CheckForNull;
+
+import com.google.common.base.Predicate;
+
 /**
  * Tracker for whiteboard services.
  */

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUtils.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUtils.java?rev=1566885&r1=1566884&r2=1566885&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUtils.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUtils.java Tue Feb 11 00:18:08 2014
@@ -19,14 +19,25 @@ package org.apache.jackrabbit.oak.spi.wh
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.util.Collections.emptyMap;
 
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
 
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
 
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
 import org.apache.jackrabbit.oak.spi.commit.Observer;
 
 public class WhiteboardUtils {
@@ -74,4 +85,83 @@ public class WhiteboardUtils {
                 .register(Observer.class, checkNotNull(observer), emptyMap());
     }
 
+    /**
+     * Returns the currently available services from the whiteboard of the tracked type.
+     *
+     * Note that the underlying tracker is closed automatically.
+     *
+     * @param wb the whiteboard
+     * @param type the service type
+     * @return a list of services
+     */
+    @Nonnull
+    public static <T> List<T> getServices(@Nonnull Whiteboard wb, @Nonnull Class<T> type) {
+        return getServices(wb, type, null);
+    }
+
+    /**
+     * Returns the one of the currently available services from the whiteboard of the tracked type.
+     *
+     * Note that the underlying tracker is closed automatically.
+     *
+     * @return one service or {@code null}
+     */
+    @CheckForNull
+    public static <T> T getService(@Nonnull Whiteboard wb, @Nonnull Class<T> type) {
+        return getService(wb, type, null);
+    }
+
+    /**
+     * Returns the currently available services from the whiteboard of the tracked type. If {@code predicate} is
+     * not {@code null} the returned list is limited to the ones that match the predicate.
+     *
+     * Note that the underlying tracker is stopped automatically after the services are returned.
+     *
+     * @param wb the whiteboard
+     * @param type the service type
+     * @param predicate filtering predicate or {@code null}
+     * @return a list of services
+     */
+    @Nonnull
+    public static <T> List<T> getServices(@Nonnull Whiteboard wb, @Nonnull Class<T> type, @Nullable Predicate<T> predicate) {
+        Tracker<T> tracker = wb.track(type);
+        try {
+            if (predicate == null) {
+                return tracker.getServices();
+            } else {
+                return ImmutableList.copyOf(Iterables.filter(tracker.getServices(), predicate));
+            }
+        } finally {
+            tracker.stop();
+        }
+    }
+
+    /**
+     * Returns the one of the currently available services from the whiteboard of the tracked type. If {@code predicate} is
+     * not {@code null} only a service that match the predicate is returned.
+     *
+     * Note that the underlying tracker is closed automatically.
+     *
+     * @param wb the whiteboard
+     * @param type the service type
+     * @param predicate filtering predicate or {@code null}
+     * @return one service or {@code null}
+     */
+    @CheckForNull
+    public static <T> T getService(@Nonnull Whiteboard wb, @Nonnull Class<T> type, @Nullable Predicate<T> predicate) {
+        Tracker<T> tracker = wb.track(type);
+        try {
+            for (T service : tracker.getServices()) {
+                if (predicate == null || predicate.apply(service)) {
+                    return service;
+                }
+            }
+            return null;
+        } finally {
+            tracker.stop();
+        }
+
+    }
+
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTest.java?rev=1566885&r1=1566884&r2=1566885&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTest.java Tue Feb 11 00:18:08 2014
@@ -17,20 +17,16 @@
 package org.apache.jackrabbit.oak.spi.security.authentication.external;
 
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
+
 import javax.jcr.SimpleCredentials;
-import javax.security.auth.login.AppConfigurationEntry;
-import javax.security.auth.login.Configuration;
 import javax.security.auth.login.LoginException;
 
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.oak.AbstractSecurityTest;
 import org.apache.jackrabbit.oak.api.ContentSession;
-import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import static org.junit.Assert.assertNotNull;
@@ -41,45 +37,29 @@ import static org.junit.Assert.fail;
 /**
  * ExternalLoginModuleTest...
  */
-public class ExternalLoginModuleTest extends AbstractSecurityTest {
+public class ExternalLoginModuleTest extends ExternalLoginModuleTestBase {
 
     protected final HashMap<String, Object> options = new HashMap<String, Object>();
 
-    private String userId;
-    private Set<String> ids = new HashSet<String>();
-
-    private UserManager userManager;
+    private String userId = "testUser";
 
     @Before
     public void before() throws Exception {
         super.before();
-
-        userId = TestLoginModule.externalUser.getId();
-        ids.add(userId);
-        for (ExternalGroup group : TestLoginModule.externalGroups) {
-            ids.add(group.getId());
-        }
-        userManager = getUserManager(root);
     }
 
     @After
     public void after() throws Exception {
-        try {
-            for (String id : ids) {
-                Authorizable a = userManager.getAuthorizable(id);
-                if (a != null) {
-                    a.remove();
-                }
-            }
-            root.commit();
-        } finally {
-            root.refresh();
-            super.after();
-        }
+        super.after();
+    }
+
+    protected ExternalIdentityProvider createIDP() {
+        return new TestIdentityProvider();
     }
 
     @Test
     public void testLoginFailed() throws Exception {
+        UserManager userManager = getUserManager(root);
         try {
             ContentSession cs = login(new SimpleCredentials("unknown", new char[0]));
             cs.close();
@@ -93,71 +73,20 @@ public class ExternalLoginModuleTest ext
 
     @Test
     public void testSyncCreateUser() throws Exception {
-        options.put(ExternalLoginModule.PARAM_SYNC_MODE, SyncMode.CREATE_USER);
-
+        UserManager userManager = getUserManager(root);
         ContentSession cs = null;
         try {
-            cs = login(new SimpleCredentials(userId, new char[0]));
-
-            root.refresh();
-            for (String id : ids) {
-                if (id.equals(userId)) {
-                    Authorizable a = userManager.getAuthorizable(id);
-                    assertNotNull(a);
-                    for (String prop : TestLoginModule.externalUser.getProperties().keySet()) {
-                        assertTrue(a.hasProperty(prop));
-                    }
-                } else {
-                    assertNull(userManager.getAuthorizable(id));
-                }
-            }
-        } finally {
-            if (cs != null) {
-                cs.close();
-            }
-            options.clear();
-        }
-    }
-
-    @Test
-    public void testSyncCreateGroup() throws Exception {
-        options.put(ExternalLoginModule.PARAM_SYNC_MODE, SyncMode.CREATE_GROUP);
+            assertNull(userManager.getAuthorizable(userId));
 
-        ContentSession cs = null;
-        try {
             cs = login(new SimpleCredentials(userId, new char[0]));
 
             root.refresh();
-            for (String id : ids) {
-                assertNull(userManager.getAuthorizable(id));
-            }
-        } finally {
-            if (cs != null) {
-                cs.close();
-            }
-            options.clear();
-        }
-    }
 
-    @Test
-    public void testSyncCreateUserAndGroups() throws Exception {
-        options.put(ExternalLoginModule.PARAM_SYNC_MODE, new String[]{SyncMode.CREATE_USER, SyncMode.CREATE_GROUP});
-
-        ContentSession cs = null;
-        try {
-            cs = login(new SimpleCredentials(userId, new char[0]));
-
-            root.refresh();
-            for (String id : ids) {
-                if (id.equals(userId)) {
-                    Authorizable a = userManager.getAuthorizable(id);
-                    assertNotNull(a);
-                    for (String prop : TestLoginModule.externalUser.getProperties().keySet()) {
-                        assertTrue(a.hasProperty(prop));
-                    }
-                } else {
-                    assertNotNull(userManager.getAuthorizable(id));
-                }
+            Authorizable a = userManager.getAuthorizable(userId);
+            assertNotNull(a);
+            ExternalUser user = idp.getUser(userId);
+            for (String prop : user.getProperties().keySet()) {
+                assertTrue(a.hasProperty(prop));
             }
         } finally {
             if (cs != null) {
@@ -168,44 +97,30 @@ public class ExternalLoginModuleTest ext
     }
 
     @Test
-    public void testSyncUpdate() throws Exception {
-        options.put(ExternalLoginModule.PARAM_SYNC_MODE, SyncMode.UPDATE);
-
-        // create user upfront in order to test update mode
-        ExternalUser externalUser = TestLoginModule.externalUser;
-        Authorizable user = userManager.createUser(externalUser.getId(), externalUser.getPassword());
-        root.commit();
-
-        ContentSession cs = null;
-        try {
-            cs = login(new SimpleCredentials(userId, new char[0]));
-
-            root.refresh();
-            for (String id : ids) {
-                if (id.equals(userId)) {
-                    Authorizable a = userManager.getAuthorizable(id);
-                    assertNotNull(a);
-                    for (String prop : TestLoginModule.externalUser.getProperties().keySet()) {
-                        assertTrue(a.hasProperty(prop));
-                    }
-                } else {
-                    assertNull(userManager.getAuthorizable(id));
-                }
-            }
-        } finally {
-            if (cs != null) {
-                cs.close();
-            }
-            options.clear();
-        }
+    @Ignore("group sync not implemented yet")
+    public void testSyncCreateGroup() throws Exception {
+//        UserManager userManager = getUserManager(root);
+//        ContentSession cs = null;
+//        try {
+//            cs = login(new SimpleCredentials(userId, new char[0]));
+//
+//            root.refresh();
+//            for (String id : ids) {
+//                assertNull(userManager.getAuthorizable(id));
+//            }
+//        } finally {
+//            if (cs != null) {
+//                cs.close();
+//            }
+//            options.clear();
+//        }
     }
 
     @Test
-    public void testSyncUpdateAndGroups() throws Exception {
-        options.put(ExternalLoginModule.PARAM_SYNC_MODE, new String[]{SyncMode.UPDATE, SyncMode.CREATE_GROUP});
-
+    public void testSyncUpdate() throws Exception {
         // create user upfront in order to test update mode
-        ExternalUser externalUser = TestLoginModule.externalUser;
+        UserManager userManager = getUserManager(root);
+        ExternalUser externalUser = idp.getUser(userId);
         Authorizable user = userManager.createUser(externalUser.getId(), externalUser.getPassword());
         root.commit();
 
@@ -214,64 +129,11 @@ public class ExternalLoginModuleTest ext
             cs = login(new SimpleCredentials(userId, new char[0]));
 
             root.refresh();
-            for (String id : ids) {
-                if (id.equals(userId)) {
-                    Authorizable a = userManager.getAuthorizable(id);
-                    assertNotNull(a);
-                    for (String prop : TestLoginModule.externalUser.getProperties().keySet()) {
-                        assertTrue(a.hasProperty(prop));
-                    }
-                } else {
-                    assertNotNull(userManager.getAuthorizable(id));
-                }
-            }
-        } finally {
-            if (cs != null) {
-                cs.close();
-            }
-            options.clear();
-        }
-    }
 
-    @Test
-    public void testDefaultSync() throws Exception {
-        options.put(ExternalLoginModule.PARAM_SYNC_MODE, null);
-
-        ContentSession cs = null;
-        try {
-            cs = login(new SimpleCredentials(userId, new char[0]));
-
-            root.refresh();
-            for (String id : ids) {
-                if (id.equals(userId)) {
-                    Authorizable a = userManager.getAuthorizable(id);
-                    assertNotNull(a);
-                    for (String prop : TestLoginModule.externalUser.getProperties().keySet()) {
-                        assertTrue(a.hasProperty(prop));
-                    }
-                } else {
-                    assertNotNull(userManager.getAuthorizable(id));
-                }
-            }
-        } finally {
-            if (cs != null) {
-                cs.close();
-            }
-            options.clear();
-        }
-    }
-
-    @Test
-    public void testNoSync() throws Exception {
-        options.put(ExternalLoginModule.PARAM_SYNC_MODE, "");
-
-        ContentSession cs = null;
-        try {
-            cs = login(new SimpleCredentials(userId, new char[0]));
-
-            root.refresh();
-            for (String id : ids) {
-                assertNull(userManager.getAuthorizable(id));
+            Authorizable a = userManager.getAuthorizable(userId);
+            assertNotNull(a);
+            for (String prop : externalUser.getProperties().keySet()) {
+                assertTrue(a.hasProperty(prop));
             }
         } finally {
             if (cs != null) {
@@ -281,16 +143,4 @@ public class ExternalLoginModuleTest ext
         }
     }
 
-    protected Configuration getConfiguration() {
-        return new Configuration() {
-            @Override
-            public AppConfigurationEntry[] getAppConfigurationEntry(String s) {
-                AppConfigurationEntry entry = new AppConfigurationEntry(
-                        TestLoginModule.class.getName(),
-                        AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
-                        options);
-                return new AppConfigurationEntry[]{entry};
-            }
-        };
-    }
 }
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java?rev=1566885&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTestBase.java Tue Feb 11 00:18:08 2014
@@ -0,0 +1,129 @@
+/*
+ * 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 java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule;
+import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * ExternalLoginModuleTest...
+ */
+public abstract class ExternalLoginModuleTestBase extends AbstractSecurityTest {
+
+    protected final HashMap<String, Object> options = new HashMap<String, Object>();
+
+    private Set<String> ids = new HashSet<String>();
+
+    private Registration testIdpReg;
+
+    private Registration syncHandlerReg;
+
+    protected Whiteboard whiteboard;
+
+    protected ExternalIdentityProvider idp;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+        UserManager userManager = getUserManager(root);
+        Iterator<Authorizable> iter = userManager.findAuthorizables("jcr:primaryType", null);
+        while (iter.hasNext()) {
+            ids.add(iter.next().getID());
+        }
+        idp = createIDP();
+
+        whiteboard = getSecurityProvider().getConfiguration(Whiteboard.class);
+        testIdpReg = whiteboard.register(ExternalIdentityProvider.class, idp, Collections.<String, Object>emptyMap());
+
+        options.put(ExternalLoginModule.PARAM_SYNC_HANDLER_NAME, "default");
+        options.put(ExternalLoginModule.PARAM_IDP_NAME, idp.getName());
+
+        // set default sync config
+        setSyncConfig(new DefaultSyncConfig());
+    }
+
+    @After
+    public void after() throws Exception {
+        if (testIdpReg != null) {
+            testIdpReg.unregister();
+            testIdpReg = null;
+        }
+        idp = null;
+        setSyncConfig(null);
+
+        try {
+            UserManager userManager = getUserManager(root);
+            Iterator<Authorizable> iter = userManager.findAuthorizables("jcr:primaryType", null);
+            while (iter.hasNext()) {
+                ids.remove(iter.next().getID());
+            }
+            for (String id : ids) {
+                Authorizable a = userManager.getAuthorizable(id);
+                if (a != null) {
+                    a.remove();
+                }
+            }
+            root.commit();
+        } finally {
+            root.refresh();
+            super.after();
+        }
+    }
+
+    protected abstract ExternalIdentityProvider createIDP();
+
+    protected void setSyncConfig(DefaultSyncConfig cfg) {
+        if (syncHandlerReg != null) {
+            syncHandlerReg.unregister();
+            syncHandlerReg = null;
+        }
+        if (cfg != null) {
+            syncHandlerReg = whiteboard.register(SyncHandler.class, new DefaultSyncHandler(cfg), Collections.<String, Object>emptyMap());
+        }
+    }
+
+    protected Configuration getConfiguration() {
+        return new Configuration() {
+            @Override
+            public AppConfigurationEntry[] getAppConfigurationEntry(String s) {
+                AppConfigurationEntry entry = new AppConfigurationEntry(
+                        ExternalLoginModule.class.getName(),
+                        AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+                        options);
+                return new AppConfigurationEntry[]{entry};
+            }
+        };
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java?rev=1566885&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java Tue Feb 11 00:18:08 2014
@@ -0,0 +1,173 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Credentials;
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.login.LoginException;
+
+/**
+ * ExternalLoginModuleImpl... TODO
+ */
+public class TestIdentityProvider implements ExternalIdentityProvider {
+
+    private final Map<String, TestGroup> externalGroups = new HashMap<String, TestGroup>();
+    private final Map<String, TestUser> externalUsers = new HashMap<String, TestUser>();
+
+
+    public TestIdentityProvider() {
+        addGroup(new TestGroup("a").withGroups("aa", "aaa"));
+        addGroup(new TestGroup("b").withGroups("a"));
+        addGroup(new TestGroup("c"));
+
+        addUser(new TestUser("testUser")
+                .withProperty("name", "Test User")
+                .withProperty("profile/name", "Public Name")
+                .withProperty("profile/age", 72)
+                .withProperty("./email", "test@testuser.com")
+                .withGroups("a", "b", "c")
+        );
+    }
+
+    private void addUser(TestIdentity user) {
+        externalUsers.put(user.getId(), (TestUser) user);
+    }
+
+    private void addGroup(TestIdentity group) {
+        externalGroups.put(group.getId(), (TestGroup) group);
+    }
+
+    @Nonnull
+    @Override
+    public String getName() {
+        return "test";
+    }
+
+    @Override
+    public ExternalIdentity getIdentity(@Nonnull ExternalIdentityRef ref) throws ExternalIdentityException {
+        return null;
+    }
+
+    @Override
+    public ExternalUser getUser(@Nonnull String userId) throws ExternalIdentityException {
+        return externalUsers.get(userId);
+    }
+
+    @Override
+    public ExternalUser authenticate(@Nonnull Credentials credentials) throws ExternalIdentityException, LoginException {
+        if (!(credentials instanceof SimpleCredentials)) {
+            return null;
+        }
+        SimpleCredentials creds = (SimpleCredentials) credentials;
+        ExternalUser user = getUser(creds.getUserID());
+        if (user != null) {
+            if (!new String(creds.getPassword()).equals(user.getPassword())) {
+                throw new LoginException("Invalid User/Password");
+            }
+        }
+        return user;
+    }
+
+    @Override
+    public ExternalGroup getGroup(@Nonnull String name) throws ExternalIdentityException {
+        return externalGroups.get(name);
+    }
+
+    private static class TestIdentity implements ExternalIdentity {
+
+        private final String userId;
+        private final ExternalIdentityRef id;
+
+        private final Set<ExternalIdentityRef> groups = new HashSet<ExternalIdentityRef>();
+        private final Map<String, Object> props = new HashMap<String, Object>();
+
+        private TestIdentity(String userId) {
+            this.userId = userId;
+            id = new ExternalIdentityRef(userId, "test");
+        }
+
+        @Override
+        public String getId() {
+            return userId;
+        }
+
+        @Override
+        public String getPrincipalName() {
+            return userId;
+        }
+
+        @Nonnull
+        @Override
+        public ExternalIdentityRef getExternalId() {
+            return id;
+        }
+
+        @Override
+        public String getIntermediatePath() {
+            return null;
+        }
+
+        @Override
+        public Iterable<ExternalIdentityRef> getGroups() {
+            return groups;
+        }
+
+        @Override
+        public Map<String, ?> getProperties() {
+            return props;
+        }
+
+        protected TestIdentity withProperty(String name, Object value) {
+            props.put(name, value);
+            return this;
+        }
+
+        protected TestIdentity withGroups(String ... grps) {
+            for (String grp: grps) {
+                groups.add(new ExternalIdentityRef(grp, "test"));
+            }
+            return this;
+        }
+    }
+
+    private static class TestUser extends TestIdentity implements ExternalUser {
+
+        private TestUser(String userId) {
+            super(userId);
+        }
+
+        @Override
+        public String getPassword() {
+            return "";
+        }
+
+    }
+
+    private static class TestGroup extends TestIdentity implements ExternalGroup {
+
+        private TestGroup(String userId) {
+            super(userId);
+        }
+
+    }
+}
\ No newline at end of file