Skip to content

Commit

Permalink
Added LDAP provider, closes #1
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita Chernyi committed Apr 27, 2018
1 parent f7517f6 commit 98899ef
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ php:

before_script:
- composer install
- composer require phpunit/phpunit:7.0.2 satooshi/php-coveralls wtf/rest wtf/html dflydev/fig-cookies
- composer require phpunit/phpunit:7.0.2 satooshi/php-coveralls wtf/rest wtf/html dflydev/fig-cookies symfony/ldap:^4
- wget https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.10.3/php-cs-fixer.phar -O php-cs-fixer
- chmod +x ./php-cs-fixer
- mkdir -p tests/coverage
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"suggest": {
"wtf/html": "HTML bundle with twig and sessions (to implement session-based auth)",
"wtf/rest": "REST bundle with JWT tokens (to implement jwt-based auth)",
"dflydev/fig-cookies": "To implement cookie-based auth"
"dflydev/fig-cookies": "To implement cookie-based auth",
"symfony/ldap": "For LDAP auth"
},
"autoload": {
"psr-4": {
Expand Down
11 changes: 11 additions & 0 deletions src/Auth/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,16 @@ public function register(Container $container): void
$container['user'] = function ($c) {
return $c['auth']->getUser();
};

//@codeCoverageIgnoreStart
if (\class_exists('\Symfony\Component\Ldap\Ldap')) {
$container['ldap_client'] = function ($c) {
$ldap = \Symfony\Component\Ldap\Ldap::create('ext_ldap', $c['config']('auth.ldap.server'));
$ldap->bind($c['config']('auth.ldap.admin.dn'), $c['config']('auth.ldap.admin.password'));

return $ldap;
};
}
//@codeCoverageIgnoreEnd
}
}
94 changes: 94 additions & 0 deletions src/Auth/Repository/LDAP.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace Wtf\Auth\Repository;

use Psr\Container\ContainerInterface;
use Wtf\Root;

class LDAP extends Root implements RepositoryInterface
{
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
//@codeCoverageIgnoreStart
if (!\class_exists('\Symfony\Component\Ldap\Ldap')) {
throw new \Exception('symfony/ldap package required for ldap auth');
}
//@codeCoverageIgnoreEnd
}

/**
* {@inheritdoc}
*/
public function getLoginFields(): array
{
return $this->config('auth.ldap.fields.login', ['uid', 'mail']);
}

/**
* {@inheritdoc}
*/
public function login(string $login, string $password): ?Root
{
$user = $this->getByLogin($login);
if (null === $user) {
return null;
}

try {
$this->ldap_client->bind($user->get($this->config('auth.ldap.fields.loginInDb', 'email')), $password);

return $user;
} catch (\Throwable $t) {
return null;
}
}

/**
* {@inheritdoc}
*/
public function getByLogin(string $login): ?Root
{
$query = '(|';
foreach ($this->getLoginFields() as $field) {
$query .= '('.$field.'='.$login.')';
}
$query .= ')';
$collection = $this->ldap_client
->query($this->config('auth.ldap.baseDN'), $query)
->execute();

foreach ($collection->toArray() as $entry) {
$user = $this->entity($this->config('auth.entity'))->load($entry->getDn(), $this->config('auth.ldap.fields.loginInDb', 'email'));
foreach ($entry->getAttributes() as $attribute => $value) {
$field = $this->config('auth.fields.map.'.$attribute);
if ($field) {
$user->set($field, $value[0] ?? null);
}
}
$user->save();

return $user;
}

return null;
}

/**
* {@inheritdoc}
*/
public function forgot(string $login): string
{
return '';
}

/**
* {@inheritdoc}
*/
public function reset(string $code, string $new_password): bool
{
return false;
}
}
51 changes: 51 additions & 0 deletions tests/Auth/Repository/LDAPTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Wtf\Auth\Tests\Auth\Storage;

use PHPUnit\Framework\TestCase;

class LDAPTest extends TestCase
{
protected $app;

protected function setUp(): void
{
$dir = __DIR__.'/../../data/config.ldap';
$this->app = new \Wtf\App(['config_dir' => $dir]);
unset($this->app->getContainer()['entity']);
unset($this->app->getContainer()['ldap_client']);
$container = $this->app->getContainer();

$this->app->getContainer()['entity'] = $this->app->getContainer()->protect(function ($name) use ($container) {
return new \Wtf\Auth\Tests\Dummy\LDAPEntity($container);
});
$this->app->getContainer()['ldap_client'] = function ($c) {
return new \Wtf\Auth\Tests\Dummy\LDAPClient($c);
};
}

public function testGetByLogin(): void
{
$this->assertNull($this->app->getContainer()->auth_repository->getByLogin('not.exists'));
$this->assertInstanceOf('\Wtf\Root', $this->app->getContainer()->auth_repository->getByLogin('exists'));
}

public function testLoginInvalid(): void
{
$this->assertNull($this->app->getContainer()->auth_repository->login('not.exists', 'invalid'));
$this->assertNull($this->app->getContainer()->auth_repository->login('exists', 'invalid'));
}

public function testLogin(): void
{
$this->assertInstanceOf('\Wtf\Root', $this->app->getContainer()->auth_repository->login('exists', 'valid'));
}

public function testUniplemented(): void
{
$this->assertInternalType('string', $this->app->getContainer()->auth_repository->forgot('nevermind'));
$this->assertFalse($this->app->getContainer()->auth_repository->reset('nevermind', 'nevermind'));
}
}
36 changes: 36 additions & 0 deletions tests/data/config.ldap/auth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

return [
'entity' => 'user',
'storage' => \Wtf\Auth\Storage\Session::class,
'repository' => \Wtf\Auth\Repository\LDAP::class,
'ldap' => [
'server' => [
'host' => 'ldap.server',
'port' => 389,
'encryption' => 'none',
'options' => [
'protocol_version' => 3,
'referrals' => true,
],
],
'admin' => [
'dn' => 'cn=admin,dc=framework,dc=wtf',
'password' => 'supersecret',
],
'baseDN' => 'cn=Users,dc=framework,dc=wtf',
'fields' => [
'login' => ['uid', 'mail'],
'loginInDb' => 'email',
'map' => [
'cn' => 'name',
],
],
],
'rbac' => [
'defaultRole' => 'anonymous',
'errorCallback' => null,
],
];
20 changes: 20 additions & 0 deletions tests/data/config.ldap/routes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

return [
'/test' => [
'default' => [
'pattern' => '/default',
'rbac' => [
'anonymous' => ['GET', 'POST'],
],
],
'user' => [
'pattern' => '/user',
'rbac' => [
'user' => ['GET', 'POST'],
],
],
],
];
14 changes: 14 additions & 0 deletions tests/data/config.ldap/suit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

return [
'providers' => [
'\Wtf\ORM\Provider',
'\Wtf\Auth\Provider',
],
'sentry' => [
'dsn' => 'https://fa38d114872b4533834f0ffd53e59ddc:54ffe4da5b23455da1b93d4b6abc246e@sentry.io/211424', //demo project
'options' => [],
],
];
51 changes: 51 additions & 0 deletions tests/data/dummy/LDAPClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Wtf\Auth\Tests\Dummy;

class LDAPClient extends \Wtf\Root
{
public function bind($dn, $password)
{
if ('cn=exists,cn=Users,dc=framework,dc=wtf' === $dn && 'valid' === $password) {
return true;
}

throw new \Exception('cant bind');
}

public function query($baseDN, $query)
{
return new class($baseDN, $query) {
public function __construct($baseDN, $query)
{
$this->baseDN = $baseDN;
$this->query = $query;
}

public function execute()
{
return new class($this->baseDN, $this->query) {
public function __construct($baseDN, $query)
{
$this->baseDN = $baseDN;
$this->query = $query;
}

public function toArray()
{
if ('(|(uid=exists)(mail=exists))' === $this->query) {
return [new \Symfony\Component\Ldap\Entry('cn=exists,cn=Users,dc=framework,dc=wtf', [
'dn' => 'cn=exists,cn=Users,dc=framework,dc=wtf',
'cn' => 'User exists',
])];
}

return [];
}
};
}
};
}
}
56 changes: 56 additions & 0 deletions tests/data/dummy/LDAPEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Wtf\Auth\Tests\Dummy;

class LDAPEntity extends \Wtf\Root
{
protected $data = [
'id' => 1,
'email' => 'cn=exists,cn=Users,dc=framework,dc=wtf',
];

public function __construct($container)
{
parent::__construct($container);
if ($container->has('forgot')) {
$this->set('forgot', $container->get('forgot'));
}
}

public function setData($data)
{
$this->data = \array_merge($this->data, $data);

return $this;
}

public function getData()
{
return $this->data;
}

public function load($value, $field = 'id', $fields = '*')
{
return $this;
}

public function has($where)
{
if (
('email' === \array_keys($where)[0] && 'cn=Nikita Chernyi,cn=Users,dc=titanium-soft,dc=com' === $where['email'])
|| ('id' === \array_keys($where)[0] && '1' === $where['id'])
|| ('forgot' === \array_keys($where)[0] && 'notexists' !== $where['forgot'])
) {
return true;
}

return false;
}

public function save(bool $validate = true): \Wtf\Root
{
return $this;
}
}

0 comments on commit 98899ef

Please sign in to comment.