diff options
author | James Moger <james.moger@gmail.com> | 2016-11-18 18:48:38 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-18 18:48:38 -0500 |
commit | 855a19a242d9ce0ebbbfe7baa120603d3c598f05 (patch) | |
tree | aa160c8bdb2dc593af36c8646587b0c6544daf67 /src/test/java | |
parent | e5068d689d47747778dcb7a6b967abbd600a30a4 (diff) | |
parent | 7f33a835295216210b7ca63367a460d76f9738d0 (diff) | |
download | gitblit-wicket-7.tar.gz gitblit-wicket-7.zip |
Merge pull request #1153 from pingunaut/wicket-7wicket-7
Wicket Update and merge master
Diffstat (limited to 'src/test/java')
-rw-r--r-- | src/test/java/com/gitblit/tests/LdapAuthenticationTest.java | 352 |
1 files changed, 324 insertions, 28 deletions
diff --git a/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java index 84dd138d..2ade6819 100644 --- a/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java +++ b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java @@ -16,17 +16,26 @@ */ package com.gitblit.tests; +import static org.junit.Assume.*; + import java.io.File; -import java.io.FileInputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.FileUtils; +import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; import com.gitblit.Constants.AccountType; import com.gitblit.IStoredSettings; @@ -43,9 +52,24 @@ import com.gitblit.utils.XssFilter; import com.gitblit.utils.XssFilter.AllowXssFilter; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; +import com.unboundid.ldap.listener.InMemoryDirectoryServerSnapshot; import com.unboundid.ldap.listener.InMemoryListenerConfig; +import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedRequest; +import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedResult; +import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchEntry; +import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchRequest; +import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; +import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSimpleBindResult; +import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; +import com.unboundid.ldap.sdk.BindRequest; +import com.unboundid.ldap.sdk.BindResult; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPResult; +import com.unboundid.ldap.sdk.OperationType; +import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchScope; +import com.unboundid.ldap.sdk.SimpleBindRequest; import com.unboundid.ldif.LDIFReader; /** @@ -55,19 +79,71 @@ import com.unboundid.ldif.LDIFReader; * @author jcrygier * */ +@RunWith(Parameterized.class) public class LdapAuthenticationTest extends GitblitUnitTest { - @Rule - public TemporaryFolder folder = new TemporaryFolder(); private static final String RESOURCE_DIR = "src/test/resources/ldap/"; + private static final String DIRECTORY_MANAGER = "cn=Directory Manager"; + private static final String USER_MANAGER = "cn=UserManager"; + private static final String ACCOUNT_BASE = "OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain"; + private static final String GROUP_BASE = "OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain"; + + + /** + * Enumeration of different test modes, representing different use scenarios. + * With ANONYMOUS anonymous binds are used to search LDAP. + * DS_MANAGER will use a DIRECTORY_MANAGER to search LDAP. Normal users are prohibited to search the DS. + * With USR_MANAGER, a USER_MANAGER account is used to search in LDAP. This account can only search users + * but not groups. Normal users can search groups, though. + * + */ + enum AuthMode { + ANONYMOUS(1389), + DS_MANAGER(2389), + USR_MANAGER(3389); + + + private int ldapPort; + private InMemoryDirectoryServer ds; + private InMemoryDirectoryServerSnapshot dsSnapshot; + + AuthMode(int port) { + this.ldapPort = port; + } + + int ldapPort() { + return this.ldapPort; + } + + void setDS(InMemoryDirectoryServer ds) { + if (this.ds == null) { + this.ds = ds; + this.dsSnapshot = ds.createSnapshot(); + }; + } + + InMemoryDirectoryServer getDS() { + return ds; + } + + void restoreSnapshot() { + ds.restoreSnapshot(dsSnapshot); + } + }; + - private File usersConf; - private LdapAuthProvider ldap; + @Parameter + public AuthMode authMode; - static int ldapPort = 1389; + @Rule + public TemporaryFolder folder = new TemporaryFolder(); - private static InMemoryDirectoryServer ds; + private File usersConf; + + + + private LdapAuthProvider ldap; private IUserManager userManager; @@ -75,21 +151,82 @@ public class LdapAuthenticationTest extends GitblitUnitTest { private MemorySettings settings; + + /** + * Run the tests with each authentication scenario once. + */ + @Parameters(name = "{0}") + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { {AuthMode.ANONYMOUS}, {AuthMode.DS_MANAGER}, {AuthMode.USR_MANAGER} }); + } + + + + /** + * Create three different in memory DS. + * + * Each DS has a different configuration: + * The first allows anonymous binds. + * The second requires authentication for all operations. It will only allow the DIRECTORY_MANAGER account + * to search for users and groups. + * The third one is like the second, but it allows users to search for users and groups, and restricts the + * USER_MANAGER from searching for groups. + */ @BeforeClass - public static void createInMemoryLdapServer() throws Exception { + public static void init() throws Exception { + InMemoryDirectoryServer ds; + InMemoryDirectoryServerConfig config = createInMemoryLdapServerConfig(AuthMode.ANONYMOUS); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", AuthMode.ANONYMOUS.ldapPort())); + ds = createInMemoryLdapServer(config); + AuthMode.ANONYMOUS.setDS(ds); + + + config = createInMemoryLdapServerConfig(AuthMode.DS_MANAGER); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", AuthMode.DS_MANAGER.ldapPort())); + config.setAuthenticationRequiredOperationTypes(EnumSet.allOf(OperationType.class)); + ds = createInMemoryLdapServer(config); + AuthMode.DS_MANAGER.setDS(ds); + + + config = createInMemoryLdapServerConfig(AuthMode.USR_MANAGER); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", AuthMode.USR_MANAGER.ldapPort())); + config.setAuthenticationRequiredOperationTypes(EnumSet.allOf(OperationType.class)); + ds = createInMemoryLdapServer(config); + AuthMode.USR_MANAGER.setDS(ds); + + } + + @AfterClass + public static void destroy() throws Exception { + for (AuthMode am : AuthMode.values()) { + am.getDS().shutDown(true); + } + } + + public static InMemoryDirectoryServer createInMemoryLdapServer(InMemoryDirectoryServerConfig config) throws Exception { + InMemoryDirectoryServer imds = new InMemoryDirectoryServer(config); + imds.importFromLDIF(true, RESOURCE_DIR + "sampledata.ldif"); + imds.startListening(); + return imds; + } + + public static InMemoryDirectoryServerConfig createInMemoryLdapServerConfig(AuthMode authMode) throws Exception { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=MyDomain"); - config.addAdditionalBindCredentials("cn=Directory Manager", "password"); - config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", ldapPort)); + config.addAdditionalBindCredentials(DIRECTORY_MANAGER, "password"); + config.addAdditionalBindCredentials(USER_MANAGER, "passwd"); config.setSchema(null); - ds = new InMemoryDirectoryServer(config); - ds.startListening(); + config.addInMemoryOperationInterceptor(new AccessInterceptor(authMode)); + + return config; } + + @Before - public void init() throws Exception { - ds.clear(); - ds.importFromLDIF(true, new LDIFReader(new FileInputStream(RESOURCE_DIR + "sampledata.ldif"))); + public void setup() throws Exception { + authMode.restoreSnapshot(); + usersConf = folder.newFile("users.conf"); FileUtils.copyFile(new File(RESOURCE_DIR + "users.conf"), usersConf); settings = getSettings(); @@ -117,15 +254,30 @@ public class LdapAuthenticationTest extends GitblitUnitTest { private MemorySettings getSettings() { Map<String, Object> backingMap = new HashMap<String, Object>(); backingMap.put(Keys.realm.userService, usersConf.getAbsolutePath()); - backingMap.put(Keys.realm.ldap.server, "ldap://localhost:" + ldapPort); -// backingMap.put(Keys.realm.ldap.domain, ""); - backingMap.put(Keys.realm.ldap.username, "cn=Directory Manager"); - backingMap.put(Keys.realm.ldap.password, "password"); -// backingMap.put(Keys.realm.ldap.backingUserService, "users.conf"); + switch(authMode) { + case ANONYMOUS: + backingMap.put(Keys.realm.ldap.server, "ldap://localhost:" + authMode.ldapPort()); + backingMap.put(Keys.realm.ldap.username, ""); + backingMap.put(Keys.realm.ldap.password, ""); + break; + case DS_MANAGER: + backingMap.put(Keys.realm.ldap.server, "ldap://localhost:" + authMode.ldapPort()); + backingMap.put(Keys.realm.ldap.username, DIRECTORY_MANAGER); + backingMap.put(Keys.realm.ldap.password, "password"); + break; + case USR_MANAGER: + backingMap.put(Keys.realm.ldap.server, "ldap://localhost:" + authMode.ldapPort()); + backingMap.put(Keys.realm.ldap.username, USER_MANAGER); + backingMap.put(Keys.realm.ldap.password, "passwd"); + break; + default: + throw new RuntimeException("Unimplemented AuthMode case!"); + + } backingMap.put(Keys.realm.ldap.maintainTeams, "true"); - backingMap.put(Keys.realm.ldap.accountBase, "OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain"); + backingMap.put(Keys.realm.ldap.accountBase, ACCOUNT_BASE); backingMap.put(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); - backingMap.put(Keys.realm.ldap.groupBase, "OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain"); + backingMap.put(Keys.realm.ldap.groupBase, GROUP_BASE); backingMap.put(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))"); backingMap.put(Keys.realm.ldap.admins, "UserThree @Git_Admins \"@Git Admins\""); backingMap.put(Keys.realm.ldap.displayName, "displayName"); @@ -136,6 +288,8 @@ public class LdapAuthenticationTest extends GitblitUnitTest { return ms; } + + @Test public void testAuthenticate() { UserModel userOneModel = ldap.authenticate("UserOne", "userOnePassword".toCharArray()); @@ -159,6 +313,13 @@ public class LdapAuthenticationTest extends GitblitUnitTest { assertNotNull(userThreeModel.getTeam("git_users")); assertNull(userThreeModel.getTeam("git_admins")); assertTrue(userThreeModel.canAdmin); + + UserModel userFourModel = ldap.authenticate("UserFour", "userFourPassword".toCharArray()); + assertNotNull(userFourModel); + assertNotNull(userFourModel.getTeam("git_users")); + assertNull(userFourModel.getTeam("git_admins")); + assertNull(userFourModel.getTeam("git admins")); + assertFalse(userFourModel.canAdmin); } @Test @@ -204,13 +365,13 @@ public class LdapAuthenticationTest extends GitblitUnitTest { @Test public void checkIfUsersConfContainsAllUsersFromSampleDataLdif() throws Exception { - SearchResult searchResult = ds.search("OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain", SearchScope.SUB, "objectClass=person"); + SearchResult searchResult = getDS().search(ACCOUNT_BASE, SearchScope.SUB, "objectClass=person"); assertEquals("Number of ldap users in gitblit user model", searchResult.getEntryCount(), countLdapUsersInUserManager()); } @Test public void addingUserInLdapShouldNotUpdateGitBlitUsersAndGroups() throws Exception { - ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif")); + getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif")); ldap.sync(); assertEquals("Number of ldap users in gitblit user model", 5, countLdapUsersInUserManager()); } @@ -218,22 +379,25 @@ public class LdapAuthenticationTest extends GitblitUnitTest { @Test public void addingUserInLdapShouldUpdateGitBlitUsersAndGroups() throws Exception { settings.put(Keys.realm.ldap.synchronize, "true"); - ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif")); + getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "adduser.ldif")); ldap.sync(); assertEquals("Number of ldap users in gitblit user model", 6, countLdapUsersInUserManager()); } @Test public void addingGroupsInLdapShouldNotUpdateGitBlitUsersAndGroups() throws Exception { - ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif")); + getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif")); ldap.sync(); assertEquals("Number of ldap groups in gitblit team model", 0, countLdapTeamsInUserManager()); } @Test public void addingGroupsInLdapShouldUpdateGitBlitUsersAndGroups() throws Exception { + // This test only makes sense if the authentication mode allows for synchronization. + assumeTrue(authMode == AuthMode.ANONYMOUS || authMode == AuthMode.DS_MANAGER); + settings.put(Keys.realm.ldap.synchronize, "true"); - ds.addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif")); + getDS().addEntries(LDIFReader.readEntries(RESOURCE_DIR + "addgroup.ldif")); ldap.sync(); assertEquals("Number of ldap groups in gitblit team model", 1, countLdapTeamsInUserManager()); } @@ -261,11 +425,21 @@ public class LdapAuthenticationTest extends GitblitUnitTest { assertNotNull(userThreeModel.getTeam("git_users")); assertNull(userThreeModel.getTeam("git_admins")); assertTrue(userThreeModel.canAdmin); + + UserModel userFourModel = auth.authenticate("UserFour", "userFourPassword".toCharArray(), null); + assertNotNull(userFourModel); + assertNotNull(userFourModel.getTeam("git_users")); + assertNull(userFourModel.getTeam("git_admins")); + assertNull(userFourModel.getTeam("git admins")); + assertFalse(userFourModel.canAdmin); } @Test public void testBindWithUser() { - settings.put(Keys.realm.ldap.bindpattern, "CN=${username},OU=US,OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain"); + // This test only makes sense if the user is not prevented from reading users and teams. + assumeTrue(authMode != AuthMode.DS_MANAGER); + + settings.put(Keys.realm.ldap.bindpattern, "CN=${username},OU=US," + ACCOUNT_BASE); settings.put(Keys.realm.ldap.username, ""); settings.put(Keys.realm.ldap.password, ""); @@ -276,6 +450,12 @@ public class LdapAuthenticationTest extends GitblitUnitTest { assertNull(userOneModelFailedAuth); } + + private InMemoryDirectoryServer getDS() + { + return authMode.getDS(); + } + private int countLdapUsersInUserManager() { int ldapAccountCount = 0; for (UserModel userModel : userManager.getAllUsers()) { @@ -296,4 +476,120 @@ public class LdapAuthenticationTest extends GitblitUnitTest { return ldapAccountCount; } + + + + /** + * Operation interceptor for the in memory DS. This interceptor + * implements access restrictions for certain user/DN combinations. + * + * The USER_MANAGER is only allowed to search for users, but not for groups. + * This is to test the original behaviour where the teams were searched under + * the user binding. + * When running in a DIRECTORY_MANAGER scenario, only the manager account + * is allowed to search for users and groups, while a normal user may not do so. + * This tests the scenario where a normal user cannot read teams and thus the + * manager account needs to be used for all searches. + * + */ + private static class AccessInterceptor extends InMemoryOperationInterceptor { + AuthMode authMode; + Map<Long,String> lastSuccessfulBindDN = new HashMap<>(); + Map<Long,Boolean> resultProhibited = new HashMap<>(); + + public AccessInterceptor(AuthMode authMode) { + this.authMode = authMode; + } + + + @Override + public void processSimpleBindResult(InMemoryInterceptedSimpleBindResult bind) { + BindResult result = bind.getResult(); + if (result.getResultCode() == ResultCode.SUCCESS) { + BindRequest bindRequest = bind.getRequest(); + lastSuccessfulBindDN.put(bind.getConnectionID(), ((SimpleBindRequest)bindRequest).getBindDN()); + resultProhibited.remove(bind.getConnectionID()); + } + } + + + + @Override + public void processSearchRequest(InMemoryInterceptedSearchRequest request) throws LDAPException { + String bindDN = getLastBindDN(request); + + if (USER_MANAGER.equals(bindDN)) { + if (request.getRequest().getBaseDN().endsWith(GROUP_BASE)) { + throw new LDAPException(ResultCode.NO_SUCH_OBJECT); + } + } + else if(authMode == AuthMode.DS_MANAGER && !DIRECTORY_MANAGER.equals(bindDN)) { + throw new LDAPException(ResultCode.NO_SUCH_OBJECT); + } + } + + + @Override + public void processSearchEntry(InMemoryInterceptedSearchEntry entry) { + String bindDN = getLastBindDN(entry); + + boolean prohibited = false; + + if (USER_MANAGER.equals(bindDN)) { + if (entry.getSearchEntry().getDN().endsWith(GROUP_BASE)) { + prohibited = true; + } + } + else if(authMode == AuthMode.DS_MANAGER && !DIRECTORY_MANAGER.equals(bindDN)) { + prohibited = true; + } + + if (prohibited) { + // Found entry prohibited for bound user. Setting entry to null. + entry.setSearchEntry(null); + resultProhibited.put(entry.getConnectionID(), Boolean.TRUE); + } + } + + @Override + public void processSearchResult(InMemoryInterceptedSearchResult result) { + String bindDN = getLastBindDN(result); + + boolean prohibited = false; + + Boolean rspb = resultProhibited.get(result.getConnectionID()); + if (USER_MANAGER.equals(bindDN)) { + if (rspb != null && rspb) { + prohibited = true; + } + } + else if(authMode == AuthMode.DS_MANAGER && !DIRECTORY_MANAGER.equals(bindDN)) { + if (rspb != null && rspb) { + prohibited = true; + } + } + + if (prohibited) { + // Result prohibited for bound user. Returning error + result.setResult(new LDAPResult(result.getMessageID(), ResultCode.INSUFFICIENT_ACCESS_RIGHTS)); + resultProhibited.remove(result.getConnectionID()); + } + } + + private String getLastBindDN(InMemoryInterceptedResult result) { + String bindDN = lastSuccessfulBindDN.get(result.getConnectionID()); + if (bindDN == null) { + return "UNKNOWN"; + } + return bindDN; + } + private String getLastBindDN(InMemoryInterceptedRequest request) { + String bindDN = lastSuccessfulBindDN.get(request.getConnectionID()); + if (bindDN == null) { + return "UNKNOWN"; + } + return bindDN; + } + } + } |