1 diff --git a/.travis.yml b/.travis.yml
2 index 04cea258..56b1f576 100644
5 @@ -58,6 +58,7 @@ install:
8 - PHP=$TRAVIS_PHP_VERSION
9 + - echo "extension=ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
10 - if [[ ! $PHP = hhvm* ]]; then echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi;
11 # xdebug isn't enable for PHP 7.1
12 - if [[ ! $PHP = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
13 diff --git a/app/AppKernel.php b/app/AppKernel.php
14 index 40726f05..c4f465dc 100644
15 --- a/app/AppKernel.php
16 +++ b/app/AppKernel.php
17 @@ -42,6 +42,10 @@ class AppKernel extends Kernel
18 new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
21 + if (class_exists('FR3D\\LdapBundle\\FR3DLdapBundle')) {
22 + $bundles[] = new FR3D\LdapBundle\FR3DLdapBundle();
25 if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
26 $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
27 $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
28 diff --git a/app/DoctrineMigrations/Version20170710113900.php b/app/DoctrineMigrations/Version20170710113900.php
30 index 00000000..7be83110
32 +++ b/app/DoctrineMigrations/Version20170710113900.php
36 +namespace Application\Migrations;
38 +use Doctrine\DBAL\Migrations\AbstractMigration;
39 +use Doctrine\DBAL\Schema\Schema;
40 +use Symfony\Component\DependencyInjection\ContainerAwareInterface;
41 +use Symfony\Component\DependencyInjection\ContainerInterface;
44 + * Added dn field on wallabag_users
46 +class Version20170710113900 extends AbstractMigration implements ContainerAwareInterface
49 + * @var ContainerInterface
53 + public function setContainer(ContainerInterface $container = null)
55 + $this->container = $container;
58 + private function getTable($tableName)
60 + return $this->container->getParameter('database_table_prefix').$tableName;
64 + * @param Schema $schema
66 + public function up(Schema $schema)
68 + $usersTable = $schema->getTable($this->getTable('user'));
70 + $this->skipIf($usersTable->hasColumn('dn'), 'It seems that you already played this migration.');
72 + $usersTable->addColumn('dn', 'text', [
79 + * @param Schema $schema
81 + public function down(Schema $schema)
83 + $usersTable = $schema->getTable($this->getTable('user'));
84 + $usersTable->dropColumn('dn');
88 diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist
89 index 6b0cb8e8..cfd41b69 100644
90 --- a/app/config/parameters.yml.dist
91 +++ b/app/config/parameters.yml.dist
92 @@ -62,3 +62,23 @@ parameters:
97 + # ldap configuration
98 + # To enable, you need to require fr3d/ldap-bundle
100 + ldap_host: localhost
104 + ldap_bind_requires_dn: true
105 + ldap_base: dc=example,dc=com
106 + ldap_manager_dn: ou=Manager,dc=example,dc=com
107 + ldap_manager_pw: password
108 + ldap_filter: (&(ObjectClass=Person))
109 + # optional (if null: no ldap user is admin)
110 + ldap_admin_filter: (&(memberOf=ou=admins,dc=example,dc=com)(uid=%s))
111 + ldap_username_attribute: uid
112 + ldap_email_attribute: mail
113 + ldap_name_attribute: cn
114 + # optional (default sets user as enabled unconditionally)
115 + ldap_enabled_attribute: ~
116 diff --git a/app/config/security.yml b/app/config/security.yml
117 index 02afc9ea..48fbb553 100644
118 --- a/app/config/security.yml
119 +++ b/app/config/security.yml
120 @@ -6,6 +6,7 @@ security:
121 ROLE_ADMIN: ROLE_USER
122 ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
124 + # /!\ This list is modified in WallabagUserBundle when LDAP is enabled
128 @@ -36,6 +37,7 @@ security:
132 + # /!\ This section is modified in WallabagUserBundle when LDAP is enabled
136 diff --git a/composer.json b/composer.json
137 index 68cfad05..32a3d1a4 100644
141 "friendsofsymfony/jsrouting-bundle": "^1.6.3",
142 "bdunogier/guzzle-site-authenticator": "^1.0.0",
143 "defuse/php-encryption": "^2.1",
144 - "html2text/html2text": "^4.1"
145 + "html2text/html2text": "^4.1",
146 + "fr3d/ldap-bundle": "^3.0"
149 + "fr3d/ldap-bundle": "If you want to authenticate via LDAP"
152 "doctrine/doctrine-fixtures-bundle": "~2.2",
153 diff --git a/composer.lock b/composer.lock
154 index 251ee081..37795e0b 100644
158 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
159 "This file is @generated automatically"
161 - "content-hash": "d2a0bd8408dccdeb7a7455996519829b",
162 + "content-hash": "4699d166d03a8e5f70d802d0bc3e6a20",
165 "name": "bdunogier/guzzle-site-authenticator",
166 @@ -1346,6 +1346,65 @@
168 "time": "2018-12-14T19:44:53+00:00"
171 + "name": "fr3d/ldap-bundle",
172 + "version": "v3.0.0",
175 + "url": "https://github.com/Maks3w/FR3DLdapBundle.git",
176 + "reference": "5a8927c11af45fa06331b97221c6da1a4a237475"
180 + "url": "https://api.github.com/repos/Maks3w/FR3DLdapBundle/zipball/5a8927c11af45fa06331b97221c6da1a4a237475",
181 + "reference": "5a8927c11af45fa06331b97221c6da1a4a237475",
187 + "symfony/config": "2.3 - 3",
188 + "symfony/dependency-injection": "2.3 - 3",
189 + "symfony/polyfill-php56": "^1.1",
190 + "symfony/security": "2.3 - 3",
191 + "symfony/security-bundle": "2.3 - 3",
192 + "zendframework/zend-ldap": "2.5 - 3"
195 + "fabpot/php-cs-fixer": "1.11.*",
196 + "fr3d/psr3-message-assertions": "0.1.*",
197 + "friendsofsymfony/user-bundle": "~1.3",
198 + "maks3w/phpunit-methods-trait": "^4.6",
199 + "phpunit/phpunit": "^4.6",
200 + "symfony/validator": "2.3 - 3"
203 + "friendsofsymfony/user-bundle": "Integrate authentication and management for DB users, useful for unmanned LDAP servers",
204 + "symfony/validator": "Allow pre-validate for existing users before register new ones"
206 + "type": "symfony-bundle",
209 + "FR3D\\LdapBundle\\": ""
212 + "notification-url": "https://packagist.org/downloads/",
221 + "description": "This package provide users and authentication services based on LDAP directories for Symfony2 framework",
222 + "homepage": "https://github.com/Maks3w/FR3DLdapBundle",
227 + "time": "2016-02-12T17:45:14+00:00"
230 "name": "friendsofsymfony/jsrouting-bundle",
232 @@ -7027,6 +7086,59 @@
235 "time": "2018-04-25T15:33:34+00:00"
238 + "name": "zendframework/zend-ldap",
239 + "version": "2.10.0",
242 + "url": "https://github.com/zendframework/zend-ldap.git",
243 + "reference": "b63c7884a08d3a6bda60ebcf7d6238cf8ad89f49"
247 + "url": "https://api.github.com/repos/zendframework/zend-ldap/zipball/b63c7884a08d3a6bda60ebcf7d6238cf8ad89f49",
248 + "reference": "b63c7884a08d3a6bda60ebcf7d6238cf8ad89f49",
253 + "php": "^5.6 || ^7.0"
256 + "php-mock/php-mock-phpunit": "^1.1.2 || ^2.1.1",
257 + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2",
258 + "zendframework/zend-coding-standard": "~1.0.0",
259 + "zendframework/zend-config": "^2.5",
260 + "zendframework/zend-eventmanager": "^2.6.3 || ^3.0.1",
261 + "zendframework/zend-stdlib": "^2.7 || ^3.0"
264 + "zendframework/zend-eventmanager": "Zend\\EventManager component"
269 + "dev-master": "2.10.x-dev",
270 + "dev-develop": "2.11.x-dev"
275 + "Zend\\Ldap\\": "src/"
278 + "notification-url": "https://packagist.org/downloads/",
282 + "description": "Provides support for LDAP operations including but not limited to binding, searching and modifying entries in an LDAP directory",
288 + "time": "2018-07-05T05:05:12+00:00"
292 @@ -7561,12 +7673,12 @@
295 "url": "https://github.com/symfony/phpunit-bridge.git",
296 - "reference": "5dab0d4b2ac99ab22b447b615fdfdc10ec4af3d5"
297 + "reference": "d61ec438634e0f234c6bda1c6ee97016bbb0e7a1"
301 - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/5dab0d4b2ac99ab22b447b615fdfdc10ec4af3d5",
302 - "reference": "5dab0d4b2ac99ab22b447b615fdfdc10ec4af3d5",
303 + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/d61ec438634e0f234c6bda1c6ee97016bbb0e7a1",
304 + "reference": "d61ec438634e0f234c6bda1c6ee97016bbb0e7a1",
308 @@ -7619,7 +7731,7 @@
310 "description": "Symfony PHPUnit Bridge",
311 "homepage": "https://symfony.com",
312 - "time": "2019-01-01T13:45:19+00:00"
313 + "time": "2019-01-16T13:27:11+00:00"
316 "name": "symfony/polyfill-php72",
317 diff --git a/scripts/install.sh b/scripts/install.sh
318 index 8b7ea03f..3a4a33ab 100755
319 --- a/scripts/install.sh
320 +++ b/scripts/install.sh
321 @@ -26,5 +26,8 @@ ENV=$1
322 TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
325 +if [ -n "$LDAP_ENABLED" ]; then
326 + SYMFONY_ENV=$ENV $COMPOSER_COMMAND require --no-update fr3d/ldap-bundle
328 SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
329 php bin/console wallabag:install --env=$ENV
330 diff --git a/scripts/update.sh b/scripts/update.sh
331 index c62d104a..6259a431 100755
332 --- a/scripts/update.sh
333 +++ b/scripts/update.sh
334 @@ -32,6 +32,9 @@ git fetch origin
336 TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
337 git checkout $TAG --force
338 +if [ -n "$LDAP_ENABLED" ]; then
339 + SYMFONY_ENV=$ENV $COMPOSER_COMMAND require --no-update fr3d/ldap-bundle
341 SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
342 php bin/console doctrine:migrations:migrate --no-interaction --env=$ENV
343 php bin/console cache:clear --env=$ENV
344 diff --git a/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php b/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php
345 index 5ca3482e..904a6af1 100644
346 --- a/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php
347 +++ b/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php
348 @@ -6,9 +6,34 @@ use Symfony\Component\Config\FileLocator;
349 use Symfony\Component\DependencyInjection\ContainerBuilder;
350 use Symfony\Component\DependencyInjection\Loader;
351 use Symfony\Component\HttpKernel\DependencyInjection\Extension;
352 +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
354 -class WallabagUserExtension extends Extension
355 +class WallabagUserExtension extends Extension implements PrependExtensionInterface
357 + public function prepend(ContainerBuilder $container)
359 + $ldap = $container->getParameter('ldap_enabled');
362 + $container->prependExtensionConfig('security', array(
363 + 'providers' => array(
364 + 'chain_provider' => array(),
367 + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
368 + $loader->load('ldap.yml');
369 + } elseif ($container->hasExtension('fr3d_ldap')) {
370 + $container->prependExtensionConfig('fr3_d_ldap', array(
372 + 'host' => 'localhost',
375 + 'baseDn' => 'dc=example,dc=com',
381 public function load(array $configs, ContainerBuilder $container)
383 $configuration = new Configuration();
384 @@ -16,6 +41,9 @@ class WallabagUserExtension extends Extension
386 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
387 $loader->load('services.yml');
388 + if ($container->getParameter('ldap_enabled')) {
389 + $loader->load('ldap_services.yml');
391 $container->setParameter('wallabag_user.registration_enabled', $config['registration_enabled']);
394 diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
395 index 48446e3c..f93c59c7 100644
396 --- a/src/Wallabag/UserBundle/Entity/User.php
397 +++ b/src/Wallabag/UserBundle/Entity/User.php
401 +// This permits to have the LdapUserInterface even when fr3d/ldap-bundle is not
403 +namespace FR3D\LdapBundle\Model;
405 +interface LdapUserInterface
407 + public function setDn($dn);
408 + public function getDn();
411 namespace Wallabag\UserBundle\Entity;
413 use Doctrine\Common\Collections\ArrayCollection;
414 @@ -16,6 +26,7 @@ use Wallabag\ApiBundle\Entity\Client;
415 use Wallabag\CoreBundle\Entity\Config;
416 use Wallabag\CoreBundle\Entity\Entry;
417 use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
418 +use FR3D\LdapBundle\Model\LdapUserInterface;
422 @@ -28,7 +39,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
423 * @UniqueEntity("email")
424 * @UniqueEntity("username")
426 -class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface
427 +class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface, LdapUserInterface
429 use EntityTimestampsTrait;
431 @@ -67,6 +78,13 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
438 + * @ORM\Column(name="dn", type="text", nullable=true)
445 @@ -309,4 +327,33 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
446 return $this->clients->first();
453 + * @param string $dn
457 + public function setDn($dn)
469 + public function getDn()
474 + public function isLdapUser()
476 + return $this->dn !== null;
479 diff --git a/src/Wallabag/UserBundle/LdapHydrator.php b/src/Wallabag/UserBundle/LdapHydrator.php
481 index 00000000..cea2450f
483 +++ b/src/Wallabag/UserBundle/LdapHydrator.php
487 +namespace Wallabag\UserBundle;
489 +use FR3D\LdapBundle\Hydrator\HydratorInterface;
490 +use FOS\UserBundle\FOSUserEvents;
491 +use FOS\UserBundle\Event\UserEvent;
493 +class LdapHydrator implements HydratorInterface
495 + private $userManager;
496 + private $eventDispatcher;
497 + private $attributesMap;
498 + private $enabledAttribute;
499 + private $ldapBaseDn;
500 + private $ldapAdminFilter;
501 + private $ldapDriver;
503 + public function __construct(
506 + array $attributes_map,
508 + $ldap_admin_filter,
511 + $this->userManager = $user_manager;
512 + $this->eventDispatcher = $event_dispatcher;
514 + $this->attributesMap = array(
515 + 'setUsername' => $attributes_map[0],
516 + 'setEmail' => $attributes_map[1],
517 + 'setName' => $attributes_map[2],
519 + $this->enabledAttribute = $attributes_map[3];
521 + $this->ldapBaseDn = $ldap_base_dn;
522 + $this->ldapAdminFilter = $ldap_admin_filter;
523 + $this->ldapDriver = $ldap_driver;
526 + public function hydrate(array $ldapEntry)
528 + $user = $this->userManager->findUserBy(array('dn' => $ldapEntry['dn']));
531 + $user = $this->userManager->createUser();
532 + $user->setDn($ldapEntry['dn']);
533 + $user->setPassword('');
534 + $user->setSalt('');
535 + $this->updateUserFields($user, $ldapEntry);
537 + $event = new UserEvent($user);
538 + $this->eventDispatcher->dispatch(FOSUserEvents::USER_CREATED, $event);
540 + $this->userManager->reloadUser($user);
542 + $this->updateUserFields($user, $ldapEntry);
548 + private function updateUserFields($user, $ldapEntry)
550 + foreach ($this->attributesMap as $key => $value) {
551 + if (is_array($ldapEntry[$value])) {
552 + $ldap_value = $ldapEntry[$value][0];
554 + $ldap_value = $ldapEntry[$value];
557 + call_user_func([$user, $key], $ldap_value);
560 + if ($this->enabledAttribute !== null) {
561 + $user->setEnabled($ldapEntry[$this->enabledAttribute]);
563 + $user->setEnabled(true);
566 + if ($this->isAdmin($user)) {
567 + $user->addRole('ROLE_SUPER_ADMIN');
569 + $user->removeRole('ROLE_SUPER_ADMIN');
572 + $this->userManager->updateUser($user, true);
575 + private function isAdmin($user)
577 + if ($this->ldapAdminFilter === null) {
581 + $escaped_username = ldap_escape($user->getUsername(), '', LDAP_ESCAPE_FILTER);
582 + $filter = sprintf($this->ldapAdminFilter, $escaped_username);
583 + $entries = $this->ldapDriver->search($this->ldapBaseDn, $filter);
585 + return $entries['count'] == 1;
588 diff --git a/src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php b/src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php
590 index 00000000..8a851f12
592 +++ b/src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php
596 +namespace Wallabag\UserBundle;
598 +use FOS\OAuthServerBundle\Storage\OAuthStorage;
599 +use OAuth2\Model\IOAuth2Client;
600 +use Symfony\Component\Security\Core\Exception\AuthenticationException;
602 +class OAuthStorageLdapWrapper extends OAuthStorage
604 + private $ldapManager;
606 + public function setLdapManager($ldap_manager)
608 + $this->ldapManager = $ldap_manager;
611 + public function checkUserCredentials(IOAuth2Client $client, $username, $password)
614 + $user = $this->userProvider->loadUserByUsername($username);
615 + } catch (AuthenticationException $e) {
619 + if ($user->isLdapUser()) {
620 + return $this->checkLdapUserCredentials($user, $password);
622 + return parent::checkUserCredentials($client, $username, $password);
626 + private function checkLdapUserCredentials($user, $password)
628 + if ($this->ldapManager->bind($user, $password)) {
637 diff --git a/src/Wallabag/UserBundle/Resources/config/ldap.yml b/src/Wallabag/UserBundle/Resources/config/ldap.yml
639 index 00000000..5ec16088
641 +++ b/src/Wallabag/UserBundle/Resources/config/ldap.yml
645 + user_hydrator: ldap_user_hydrator
647 + host: "%ldap_host%"
648 + port: "%ldap_port%"
649 + useSsl: "%ldap_ssl%"
650 + useStartTls: "%ldap_tls%"
651 + bindRequiresDn: "%ldap_bind_requires_dn%"
652 + username: "%ldap_manager_dn%"
653 + password: "%ldap_manager_pw%"
655 + baseDn: "%ldap_base%"
656 + filter: "%ldap_filter%"
657 + usernameAttribute: "%ldap_username_attribute%"
662 + providers: [ fr3d_ldapbundle, fos_userbundle ]
664 + id: fr3d_ldap.security.user.provider
669 + provider: chain_provider
671 diff --git a/src/Wallabag/UserBundle/Resources/config/ldap_services.yml b/src/Wallabag/UserBundle/Resources/config/ldap_services.yml
673 index 00000000..b3e3fd8a
675 +++ b/src/Wallabag/UserBundle/Resources/config/ldap_services.yml
678 + fos_oauth_server.server:
679 + class: OAuth2\OAuth2
681 + - "@oauth_storage_ldap_wrapper"
682 + - "%fos_oauth_server.server.options%"
683 + oauth_storage_ldap_wrapper:
684 + class: Wallabag\UserBundle\OAuthStorageLdapWrapper
685 + parent: fos_oauth_server.storage
687 + - [setLdapManager, ["@fr3d_ldap.ldap_manager"]]
689 + ldap_user_hydrator:
690 + class: Wallabag\UserBundle\LdapHydrator
692 + - "@fos_user.user_manager"
693 + - "@event_dispatcher"
694 + - [ "%ldap_username_attribute%", "%ldap_email_attribute%", "%ldap_name_attribute%", "%ldap_enabled_attribute%" ]
696 + - "%ldap_admin_filter%"
697 + - "@fr3d_ldap.ldap_driver"