protected GroupSyncService $groupSyncService;
protected UserAvatars $userAvatars;
- protected array $config;
+ protected LdapConfig $config;
public function __construct(LdapConnectionManager $ldap, UserAvatars $userAvatars, GroupSyncService $groupSyncService)
{
$this->ldap = $ldap;
$this->userAvatars = $userAvatars;
$this->groupSyncService = $groupSyncService;
- $this->config = config('services.ldap');
+ $this->config = new LdapConfig(config('services.ldap'));
}
/**
}
// Find user
- $userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
- $baseDn = $this->config['base_dn'];
+ $userFilter = $this->buildFilter($this->config->get('user_filter'), ['user' => $userName]);
+ $baseDn = $this->config->get('base_dn');
- $followReferrals = $this->config['follow_referrals'] ? 1 : 0;
+ $followReferrals = $this->config->get('follow_referrals') ? 1 : 0;
$connection->setOption(LDAP_OPT_REFERRALS, $followReferrals);
$users = $connection->searchAndGetEntries($baseDn, $userFilter, $attributes);
if ($users['count'] === 0) {
*/
public function getUserDetails(string $userName): ?array
{
- $idAttr = $this->config['id_attribute'];
- $emailAttr = $this->config['email_attribute'];
- $displayNameAttr = $this->config['display_name_attribute'];
- $thumbnailAttr = $this->config['thumbnail_attribute'];
+ $idAttr = $this->config->get('id_attribute');
+ $emailAttr = $this->config->get('email_attribute');
+ $displayNameAttr = $this->config->get('display_name_attribute');
+ $thumbnailAttr = $this->config->get('thumbnail_attribute');
$user = $this->getUserWithAttributes($userName, array_filter([
'cn', 'dn', $idAttr, $emailAttr, $displayNameAttr, $thumbnailAttr,
'avatar' => $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null,
];
- if ($this->config['dump_user_details']) {
+ if ($this->config->get('dump_user_details')) {
throw new JsonDebugException([
'details_from_ldap' => $user,
'details_bookstack_parsed' => $formatted,
*/
public function getUserGroups(string $userName): array
{
- $groupsAttr = $this->config['group_attribute'];
+ $groupsAttr = $this->config->get('group_attribute');
$user = $this->getUserWithAttributes($userName, [$groupsAttr]);
if ($user === null) {
$userGroups = $this->groupFilter($user);
$allGroups = $this->getGroupsRecursive($userGroups, []);
- if ($this->config['dump_user_groups']) {
+ if ($this->config->get('dump_user_groups')) {
throw new JsonDebugException([
'details_from_ldap' => $user,
'parsed_direct_user_groups' => $userGroups,
{
$connection = $this->ldap->startSystemBind($this->config);
- $followReferrals = $this->config['follow_referrals'] ? 1 : 0;
+ $followReferrals = $this->config->get('follow_referrals') ? 1 : 0;
$connection->setOption(LDAP_OPT_REFERRALS, $followReferrals);
- $baseDn = $this->config['base_dn'];
- $groupsAttr = strtolower($this->config['group_attribute']);
+ $baseDn = $this->config->get('base_dn');
+ $groupsAttr = strtolower($this->config->get('group_attribute'));
- $groupFilter = 'CN=' . $connection->escape($groupName);
+ $groupFilter = 'CN=' . LdapConnection::escape($groupName);
$groups = $connection->searchAndGetEntries($baseDn, $groupFilter, [$groupsAttr]);
if ($groups['count'] === 0) {
return [];
*/
protected function groupFilter(array $userGroupSearchResponse): array
{
- $groupsAttr = strtolower($this->config['group_attribute']);
+ $groupsAttr = strtolower($this->config->get('group_attribute'));
$ldapGroups = [];
$count = 0;
public function syncGroups(User $user, string $username)
{
$userLdapGroups = $this->getUserGroups($username);
- $this->groupSyncService->syncUserWithFoundGroups($user, $userLdapGroups, $this->config['remove_from_groups']);
+ $this->groupSyncService->syncUserWithFoundGroups($user, $userLdapGroups, $this->config->get('remove_from_groups'));
}
/**
*/
public function shouldSyncGroups(): bool
{
- return $this->config['user_to_groups'] !== false;
+ return $this->config->get('user_to_groups') !== false;
}
/**
*/
public function saveAndAttachAvatar(User $user, array $ldapUserDetails): void
{
- if (is_null($this->config['thumbnail_attribute']) || is_null($ldapUserDetails['avatar'])) {
+ if (is_null($this->config->get('thumbnail_attribute')) || is_null($ldapUserDetails['avatar'])) {
return;
}
namespace Tests\Auth;
+use BookStack\Auth\Access\Ldap\LdapConfig;
use BookStack\Auth\Access\Ldap\LdapConnection;
use BookStack\Auth\Access\Ldap\LdapConnectionManager;
use BookStack\Auth\Access\Ldap\LdapService;
'services.ldap.thumbnail_attribute' => null,
]);
- $lcm = $this->mock(LdapConnectionManager::class);
- // TODO - Properly mock
-
$this->mockLdap = \Mockery::mock(LdapConnection::class);
$this->app[LdapConnection::class] = $this->mockLdap;
$this->mockUser = User::factory()->make();
protected function runFailedAuthLogin()
{
- $this->commonLdapMocks(1, 1, 1, 1, 1);
+ $this->commonLdapMocks(1, 1, 1);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
->andReturn(['count' => 0]);
$this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
}
- protected function mockEscapes($times = 1)
- {
- $this->mockLdap->shouldReceive('escape')->times($times)->andReturnUsing(function ($val) {
- return ldap_escape($val);
- });
- }
-
- protected function mockExplodes($times = 1)
- {
- $this->mockLdap->shouldReceive('explodeDn')->times($times)->andReturnUsing(function ($dn, $withAttrib) {
- return ldap_explode_dn($dn, $withAttrib);
- });
- }
-
protected function mockUserLogin(?string $email = null): TestResponse
{
return $this->post('/login', [
/**
* Set LDAP method mocks for things we commonly call without altering.
*/
- protected function commonLdapMocks(int $connects = 1, int $versions = 1, int $options = 2, int $binds = 4, int $escapes = 2, int $explodes = 0)
+ protected function commonLdapMocks(int $versions = 1, int $options = 2, int $binds = 4)
{
$this->mockLdap->shouldReceive('setVersion')->times($versions);
$this->mockLdap->shouldReceive('setOption')->times($options);
$this->mockLdap->shouldReceive('bind')->times($binds)->andReturn(true);
- $this->mockEscapes($escapes);
- $this->mockExplodes($explodes);
}
public function test_login()
{
- $this->commonLdapMocks(1, 1, 2, 4, 2);
+ $this->commonLdapMocks(1, 2, 4);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'registration-restrict' => 'testing.com',
]);
- $this->commonLdapMocks(1, 1, 2, 4, 2);
+ $this->commonLdapMocks(1, 2, 4);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
{
$ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
- $this->commonLdapMocks(1, 1, 1, 2, 1);
+ $this->commonLdapMocks(1, 1, 2);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'cn' => [$this->mockUser->name],
'dn' => $ldapDn,
{
config()->set(['services.ldap.id_attribute' => 'my_custom_id']);
- $this->commonLdapMocks(1, 1, 1, 2, 1);
+ $this->commonLdapMocks(1, 1, 2);
$ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'cn' => [$this->mockUser->name],
'dn' => $ldapDn,
public function test_initial_incorrect_credentials()
{
- $this->commonLdapMocks(1, 1, 1, 0, 1);
+ $this->commonLdapMocks(1, 1, 0);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
public function test_login_not_found_username()
{
- $this->commonLdapMocks(1, 1, 1, 1, 1);
+ $this->commonLdapMocks(1, 1, 1);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 0]);
$resp = $this->mockUserLogin();
'services.ldap.remove_from_groups' => false,
]);
- $this->commonLdapMocks(1, 1, 4, 5, 4, 6);
+ $this->commonLdapMocks(1, 4, 5);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'services.ldap.remove_from_groups' => true,
]);
- $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
+ $this->commonLdapMocks(1, 3, 4);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(3)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'dn' => 'dc=test,' . config('services.ldap.base_dn'),
'mail' => [$this->mockUser->email],
]];
- $this->commonLdapMocks(1, 1, 4, 5, 4, 2);
+ $this->commonLdapMocks(1, 4, 5);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn($userResp, ['count' => 1,
0 => [
'dn' => 'dc=test,' . config('services.ldap.base_dn'),
'services.ldap.remove_from_groups' => true,
]);
- $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
+ $this->commonLdapMocks(1, 3, 4);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(3)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'services.ldap.remove_from_groups' => true,
]);
- $this->commonLdapMocks(1, 1, 4, 5, 4, 6);
+ $this->commonLdapMocks(1, 4, 5);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'services.ldap.display_name_attribute' => 'displayName',
]);
- $this->commonLdapMocks(1, 1, 2, 4, 2);
+ $this->commonLdapMocks(1, 2, 4);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'services.ldap.display_name_attribute' => 'displayName',
]);
- $this->commonLdapMocks(1, 1, 2, 4, 2);
+ $this->commonLdapMocks(1, 2, 4);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
]);
}
- protected function checkLdapReceivesCorrectDetails($serverString, $expectedHost, $expectedPort)
+ protected function checkLdapConfigHostParsing($serverString, ...$expectedHostPortPairs)
{
- app('config')->set([
- 'services.ldap.server' => $serverString,
- ]);
+ config()->set(['services.ldap.server' => $serverString]);
+ $ldapConfig = new LdapConfig(config('services.ldap'));
- // Standard mocks
- $this->commonLdapMocks(0, 1, 1, 2, 1);
- $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)->andReturn(['count' => 1, 0 => [
- 'uid' => [$this->mockUser->name],
- 'cn' => [$this->mockUser->name],
- 'dn' => 'dc=test' . config('services.ldap.base_dn'),
- ]]);
-
- $this->mockLdap->shouldReceive('connect')->once()
- ->with($expectedHost, $expectedPort)->andReturn($this->resourceId);
- $this->mockUserLogin();
+ $servers = $ldapConfig->getServers();
+ $this->assertCount(count($expectedHostPortPairs), $servers);
+ foreach ($expectedHostPortPairs as $i => $expected) {
+ $server = $servers[$i];
+ $this->assertEquals($expected[0], $server['host']);
+ $this->assertEquals($expected[1], $server['port']);
+ }
}
public function test_ldap_port_provided_on_host_if_host_is_full_uri()
{
$hostName = 'ldaps://bookstack:8080';
- $this->checkLdapReceivesCorrectDetails($hostName, $hostName, 389);
+ $this->checkLdapConfigHostParsing($hostName, [$hostName, 389]);
}
public function test_ldap_port_parsed_from_server_if_host_is_not_full_uri()
{
- $this->checkLdapReceivesCorrectDetails('ldap.bookstack.com:8080', 'ldap.bookstack.com', 8080);
+ $this->checkLdapConfigHostParsing('ldap.bookstack.com:8080', ['ldap.bookstack.com', 8080]);
}
public function test_default_ldap_port_used_if_not_in_server_string_and_not_uri()
{
- $this->checkLdapReceivesCorrectDetails('ldap.bookstack.com', 'ldap.bookstack.com', 389);
+ $this->checkLdapConfigHostParsing('ldap.bookstack.com', ['ldap.bookstack.com', 389]);
+ }
+
+ public function test_multiple_hosts_parsed_from_config_if_semicolon_seperated()
+ {
+ $this->checkLdapConfigHostParsing(
+ 'ldap.bookstack.com:8080; l.bookstackapp.com; b.bookstackapp.com:8081',
+ ['ldap.bookstack.com', 8080],
+ ['l.bookstackapp.com', 389],
+ ['b.bookstackapp.com', 8081],
+ );
}
public function test_host_fail_over_by_using_semicolon_seperated_hosts()
]);
// Standard mocks
- $this->commonLdapMocks(0, 1, 1, 2, 1);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'dn' => 'dc=test' . config('services.ldap.base_dn'),
]]);
- $this->mockLdap->shouldReceive('connect')->once()->with('ldap-tiger.example.com', 389)->andReturn(false);
+ $this->mockLdap->shouldReceive('bind')->once()->with('ldap-tiger.example.com', 389)->andReturn(false);
+ $this->commonLdapMocks(0, 1, 1);
+
$this->mockLdap->shouldReceive('connect')->once()->with('ldap-donkey.example.com', 8080)->andReturn($this->resourceId);
$this->mockUserLogin();
}
{
config()->set(['services.ldap.dump_user_details' => true, 'services.ldap.thumbnail_attribute' => 'jpegphoto']);
- $this->commonLdapMocks(1, 1, 1, 1, 1);
+ $this->commonLdapMocks(1, 1, 1);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
{
config()->set(['services.ldap.start_tls' => true]);
$this->mockLdap->shouldReceive('startTls')->once()->andReturn(false);
- $this->commonLdapMocks(1, 1, 0, 0, 0);
+ $this->commonLdapMocks(1, 0, 0);
$resp = $this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
$resp->assertStatus(500);
}
{
config()->set(['services.ldap.id_attribute' => 'BIN;uid']);
$ldapService = app()->make(LdapService::class);
- $this->commonLdapMocks(1, 1, 1, 1, 1);
+ $this->commonLdapMocks(1, 1, 1);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), ['cn', 'dn', 'uid', 'mail', 'cn'])
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), ['cn', 'dn', 'uid', 'mail', 'cn'])
->andReturn(['count' => 1, 0 => [
'uid' => [hex2bin('FFF8F7')],
'cn' => [$this->mockUser->name],
public function test_new_ldap_user_login_with_already_used_email_address_shows_error_message_to_user()
{
- $this->commonLdapMocks(1, 1, 2, 4, 2);
+ $this->commonLdapMocks(1, 2, 4);
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'uid' => [$this->mockUser->name],
'cn' => [$this->mockUser->name],
'services.ldap.remove_from_groups' => true,
]);
- $this->commonLdapMocks(1, 1, 6, 8, 6, 4);
+ $this->commonLdapMocks(1, 6, 8);
$this->mockLdap->shouldReceive('searchAndGetEntries')
->times(6)
->andReturn(['count' => 1, 0 => [
{
config()->set(['services.ldap.thumbnail_attribute' => 'jpegPhoto']);
- $this->commonLdapMocks(1, 1, 1, 2, 1);
+ $this->commonLdapMocks(1, 1, 2);
$ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
- ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->with(config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
->andReturn(['count' => 1, 0 => [
'cn' => [$this->mockUser->name],
'dn' => $ldapDn,