aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--app/AppKernel.php4
-rw-r--r--app/DoctrineMigrations/Version20170710113900.php54
-rw-r--r--app/config/parameters.yml.dist20
-rw-r--r--app/config/security.yml2
-rw-r--r--composer.json3
-rwxr-xr-xscripts/install.sh3
-rwxr-xr-xscripts/update.sh3
-rw-r--r--src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php30
-rw-r--r--src/Wallabag/UserBundle/Entity/User.php49
-rw-r--r--src/Wallabag/UserBundle/LdapHydrator.php103
-rw-r--r--src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php43
-rw-r--r--src/Wallabag/UserBundle/Resources/config/ldap.yml28
-rw-r--r--src/Wallabag/UserBundle/Resources/config/ldap_services.yml22
14 files changed, 363 insertions, 2 deletions
diff --git a/.travis.yml b/.travis.yml
index 04cea258..56b1f576 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -58,6 +58,7 @@ install:
58 58
59before_script: 59before_script:
60 - PHP=$TRAVIS_PHP_VERSION 60 - PHP=$TRAVIS_PHP_VERSION
61 - echo "extension=ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
61 - if [[ ! $PHP = hhvm* ]]; then echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi; 62 - if [[ ! $PHP = hhvm* ]]; then echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi;
62 # xdebug isn't enable for PHP 7.1 63 # xdebug isn't enable for PHP 7.1
63 - if [[ ! $PHP = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi 64 - if [[ ! $PHP = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
diff --git a/app/AppKernel.php b/app/AppKernel.php
index 40726f05..c4f465dc 100644
--- a/app/AppKernel.php
+++ b/app/AppKernel.php
@@ -42,6 +42,10 @@ class AppKernel extends Kernel
42 new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(), 42 new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
43 ]; 43 ];
44 44
45 if (class_exists('FR3D\\LdapBundle\\FR3DLdapBundle')) {
46 $bundles[] = new FR3D\LdapBundle\FR3DLdapBundle();
47 }
48
45 if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { 49 if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
46 $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle(); 50 $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
47 $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); 51 $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
diff --git a/app/DoctrineMigrations/Version20170710113900.php b/app/DoctrineMigrations/Version20170710113900.php
new file mode 100644
index 00000000..7be83110
--- /dev/null
+++ b/app/DoctrineMigrations/Version20170710113900.php
@@ -0,0 +1,54 @@
1<?php
2
3namespace Application\Migrations;
4
5use Doctrine\DBAL\Migrations\AbstractMigration;
6use Doctrine\DBAL\Schema\Schema;
7use Symfony\Component\DependencyInjection\ContainerAwareInterface;
8use Symfony\Component\DependencyInjection\ContainerInterface;
9
10/**
11 * Added dn field on wallabag_users
12 */
13class Version20170710113900 extends AbstractMigration implements ContainerAwareInterface
14{
15 /**
16 * @var ContainerInterface
17 */
18 private $container;
19
20 public function setContainer(ContainerInterface $container = null)
21 {
22 $this->container = $container;
23 }
24
25 private function getTable($tableName)
26 {
27 return $this->container->getParameter('database_table_prefix').$tableName;
28 }
29
30 /**
31 * @param Schema $schema
32 */
33 public function up(Schema $schema)
34 {
35 $usersTable = $schema->getTable($this->getTable('user'));
36
37 $this->skipIf($usersTable->hasColumn('dn'), 'It seems that you already played this migration.');
38
39 $usersTable->addColumn('dn', 'text', [
40 'default' => null,
41 'notnull' => false,
42 ]);
43 }
44
45 /**
46 * @param Schema $schema
47 */
48 public function down(Schema $schema)
49 {
50 $usersTable = $schema->getTable($this->getTable('user'));
51 $usersTable->dropColumn('dn');
52 }
53}
54
diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist
index 6b0cb8e8..cfd41b69 100644
--- a/app/config/parameters.yml.dist
+++ b/app/config/parameters.yml.dist
@@ -62,3 +62,23 @@ parameters:
62 redis_port: 6379 62 redis_port: 6379
63 redis_path: null 63 redis_path: null
64 redis_password: null 64 redis_password: null
65
66 # ldap configuration
67 # To enable, you need to require fr3d/ldap-bundle
68 ldap_enabled: false
69 ldap_host: localhost
70 ldap_port: 389
71 ldap_tls: false
72 ldap_ssl: false
73 ldap_bind_requires_dn: true
74 ldap_base: dc=example,dc=com
75 ldap_manager_dn: ou=Manager,dc=example,dc=com
76 ldap_manager_pw: password
77 ldap_filter: (&(ObjectClass=Person))
78 # optional (if null: no ldap user is admin)
79 ldap_admin_filter: (&(memberOf=ou=admins,dc=example,dc=com)(uid=%s))
80 ldap_username_attribute: uid
81 ldap_email_attribute: mail
82 ldap_name_attribute: cn
83 # optional (default sets user as enabled unconditionally)
84 ldap_enabled_attribute: ~
diff --git a/app/config/security.yml b/app/config/security.yml
index 02afc9ea..48fbb553 100644
--- a/app/config/security.yml
+++ b/app/config/security.yml
@@ -6,6 +6,7 @@ security:
6 ROLE_ADMIN: ROLE_USER 6 ROLE_ADMIN: ROLE_USER
7 ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ] 7 ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
8 8
9 # /!\ This list is modified in WallabagUserBundle when LDAP is enabled
9 providers: 10 providers:
10 administrators: 11 administrators:
11 entity: 12 entity:
@@ -36,6 +37,7 @@ security:
36 pattern: ^/login$ 37 pattern: ^/login$
37 anonymous: ~ 38 anonymous: ~
38 39
40 # /!\ This section is modified in WallabagUserBundle when LDAP is enabled
39 secured_area: 41 secured_area:
40 pattern: ^/ 42 pattern: ^/
41 form_login: 43 form_login:
diff --git a/composer.json b/composer.json
index 68cfad05..775a1ebd 100644
--- a/composer.json
+++ b/composer.json
@@ -87,6 +87,9 @@
87 "defuse/php-encryption": "^2.1", 87 "defuse/php-encryption": "^2.1",
88 "html2text/html2text": "^4.1" 88 "html2text/html2text": "^4.1"
89 }, 89 },
90 "suggest": {
91 "fr3d/ldap-bundle": "If you want to authenticate via LDAP"
92 },
90 "require-dev": { 93 "require-dev": {
91 "doctrine/doctrine-fixtures-bundle": "~2.2", 94 "doctrine/doctrine-fixtures-bundle": "~2.2",
92 "doctrine/data-fixtures": "~1.1", 95 "doctrine/data-fixtures": "~1.1",
diff --git a/scripts/install.sh b/scripts/install.sh
index 8b7ea03f..3a4a33ab 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -26,5 +26,8 @@ ENV=$1
26TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) 26TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
27 27
28git checkout $TAG 28git checkout $TAG
29if [ -n "$LDAP_ENABLED" ]; then
30 SYMFONY_ENV=$ENV $COMPOSER_COMMAND require --no-update fr3d/ldap-bundle
31fi
29SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist 32SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
30php bin/console wallabag:install --env=$ENV 33php bin/console wallabag:install --env=$ENV
diff --git a/scripts/update.sh b/scripts/update.sh
index c62d104a..6259a431 100755
--- a/scripts/update.sh
+++ b/scripts/update.sh
@@ -32,6 +32,9 @@ git fetch origin
32git fetch --tags 32git fetch --tags
33TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) 33TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
34git checkout $TAG --force 34git checkout $TAG --force
35if [ -n "$LDAP_ENABLED" ]; then
36 SYMFONY_ENV=$ENV $COMPOSER_COMMAND require --no-update fr3d/ldap-bundle
37fi
35SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist 38SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
36php bin/console doctrine:migrations:migrate --no-interaction --env=$ENV 39php bin/console doctrine:migrations:migrate --no-interaction --env=$ENV
37php bin/console cache:clear --env=$ENV 40php bin/console cache:clear --env=$ENV
diff --git a/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php b/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php
index 5ca3482e..904a6af1 100644
--- a/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php
+++ b/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php
@@ -6,9 +6,34 @@ use Symfony\Component\Config\FileLocator;
6use Symfony\Component\DependencyInjection\ContainerBuilder; 6use Symfony\Component\DependencyInjection\ContainerBuilder;
7use Symfony\Component\DependencyInjection\Loader; 7use Symfony\Component\DependencyInjection\Loader;
8use Symfony\Component\HttpKernel\DependencyInjection\Extension; 8use Symfony\Component\HttpKernel\DependencyInjection\Extension;
9use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
9 10
10class WallabagUserExtension extends Extension 11class WallabagUserExtension extends Extension implements PrependExtensionInterface
11{ 12{
13 public function prepend(ContainerBuilder $container)
14 {
15 $ldap = $container->getParameter('ldap_enabled');
16
17 if ($ldap) {
18 $container->prependExtensionConfig('security', array(
19 'providers' => array(
20 'chain_provider' => array(),
21 ),
22 ));
23 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
24 $loader->load('ldap.yml');
25 } elseif ($container->hasExtension('fr3d_ldap')) {
26 $container->prependExtensionConfig('fr3_d_ldap', array(
27 'driver' => array(
28 'host' => 'localhost',
29 ),
30 'user' => array(
31 'baseDn' => 'dc=example,dc=com',
32 ),
33 ));
34 }
35 }
36
12 public function load(array $configs, ContainerBuilder $container) 37 public function load(array $configs, ContainerBuilder $container)
13 { 38 {
14 $configuration = new Configuration(); 39 $configuration = new Configuration();
@@ -16,6 +41,9 @@ class WallabagUserExtension extends Extension
16 41
17 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 42 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
18 $loader->load('services.yml'); 43 $loader->load('services.yml');
44 if ($container->getParameter('ldap_enabled')) {
45 $loader->load('ldap_services.yml');
46 }
19 $container->setParameter('wallabag_user.registration_enabled', $config['registration_enabled']); 47 $container->setParameter('wallabag_user.registration_enabled', $config['registration_enabled']);
20 } 48 }
21 49
diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
index 48446e3c..f93c59c7 100644
--- a/src/Wallabag/UserBundle/Entity/User.php
+++ b/src/Wallabag/UserBundle/Entity/User.php
@@ -1,5 +1,15 @@
1<?php 1<?php
2 2
3// This permits to have the LdapUserInterface even when fr3d/ldap-bundle is not
4// in the packages
5namespace FR3D\LdapBundle\Model;
6
7interface LdapUserInterface
8{
9 public function setDn($dn);
10 public function getDn();
11}
12
3namespace Wallabag\UserBundle\Entity; 13namespace Wallabag\UserBundle\Entity;
4 14
5use Doctrine\Common\Collections\ArrayCollection; 15use Doctrine\Common\Collections\ArrayCollection;
@@ -16,6 +26,7 @@ use Wallabag\ApiBundle\Entity\Client;
16use Wallabag\CoreBundle\Entity\Config; 26use Wallabag\CoreBundle\Entity\Config;
17use Wallabag\CoreBundle\Entity\Entry; 27use Wallabag\CoreBundle\Entity\Entry;
18use Wallabag\CoreBundle\Helper\EntityTimestampsTrait; 28use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
29use FR3D\LdapBundle\Model\LdapUserInterface;
19 30
20/** 31/**
21 * User. 32 * User.
@@ -28,7 +39,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
28 * @UniqueEntity("email") 39 * @UniqueEntity("email")
29 * @UniqueEntity("username") 40 * @UniqueEntity("username")
30 */ 41 */
31class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface 42class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface, LdapUserInterface
32{ 43{
33 use EntityTimestampsTrait; 44 use EntityTimestampsTrait;
34 45
@@ -68,6 +79,13 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
68 protected $email; 79 protected $email;
69 80
70 /** 81 /**
82 * @var string
83 *
84 * @ORM\Column(name="dn", type="text", nullable=true)
85 */
86 protected $dn;
87
88 /**
71 * @var \DateTime 89 * @var \DateTime
72 * 90 *
73 * @ORM\Column(name="created_at", type="datetime") 91 * @ORM\Column(name="created_at", type="datetime")
@@ -309,4 +327,33 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
309 return $this->clients->first(); 327 return $this->clients->first();
310 } 328 }
311 } 329 }
330
331 /**
332 * Set dn.
333 *
334 * @param string $dn
335 *
336 * @return User
337 */
338 public function setDn($dn)
339 {
340 $this->dn = $dn;
341
342 return $this;
343 }
344
345 /**
346 * Get dn.
347 *
348 * @return string
349 */
350 public function getDn()
351 {
352 return $this->dn;
353 }
354
355 public function isLdapUser()
356 {
357 return $this->dn !== null;
358 }
312} 359}
diff --git a/src/Wallabag/UserBundle/LdapHydrator.php b/src/Wallabag/UserBundle/LdapHydrator.php
new file mode 100644
index 00000000..cea2450f
--- /dev/null
+++ b/src/Wallabag/UserBundle/LdapHydrator.php
@@ -0,0 +1,103 @@
1<?php
2
3namespace Wallabag\UserBundle;
4
5use FR3D\LdapBundle\Hydrator\HydratorInterface;
6use FOS\UserBundle\FOSUserEvents;
7use FOS\UserBundle\Event\UserEvent;
8
9class LdapHydrator implements HydratorInterface
10{
11 private $userManager;
12 private $eventDispatcher;
13 private $attributesMap;
14 private $enabledAttribute;
15 private $ldapBaseDn;
16 private $ldapAdminFilter;
17 private $ldapDriver;
18
19 public function __construct(
20 $user_manager,
21 $event_dispatcher,
22 array $attributes_map,
23 $ldap_base_dn,
24 $ldap_admin_filter,
25 $ldap_driver
26 ) {
27 $this->userManager = $user_manager;
28 $this->eventDispatcher = $event_dispatcher;
29
30 $this->attributesMap = array(
31 'setUsername' => $attributes_map[0],
32 'setEmail' => $attributes_map[1],
33 'setName' => $attributes_map[2],
34 );
35 $this->enabledAttribute = $attributes_map[3];
36
37 $this->ldapBaseDn = $ldap_base_dn;
38 $this->ldapAdminFilter = $ldap_admin_filter;
39 $this->ldapDriver = $ldap_driver;
40 }
41
42 public function hydrate(array $ldapEntry)
43 {
44 $user = $this->userManager->findUserBy(array('dn' => $ldapEntry['dn']));
45
46 if (!$user) {
47 $user = $this->userManager->createUser();
48 $user->setDn($ldapEntry['dn']);
49 $user->setPassword('');
50 $user->setSalt('');
51 $this->updateUserFields($user, $ldapEntry);
52
53 $event = new UserEvent($user);
54 $this->eventDispatcher->dispatch(FOSUserEvents::USER_CREATED, $event);
55
56 $this->userManager->reloadUser($user);
57 } else {
58 $this->updateUserFields($user, $ldapEntry);
59 }
60
61 return $user;
62 }
63
64 private function updateUserFields($user, $ldapEntry)
65 {
66 foreach ($this->attributesMap as $key => $value) {
67 if (is_array($ldapEntry[$value])) {
68 $ldap_value = $ldapEntry[$value][0];
69 } else {
70 $ldap_value = $ldapEntry[$value];
71 }
72
73 call_user_func([$user, $key], $ldap_value);
74 }
75
76 if ($this->enabledAttribute !== null) {
77 $user->setEnabled($ldapEntry[$this->enabledAttribute]);
78 } else {
79 $user->setEnabled(true);
80 }
81
82 if ($this->isAdmin($user)) {
83 $user->addRole('ROLE_SUPER_ADMIN');
84 } else {
85 $user->removeRole('ROLE_SUPER_ADMIN');
86 }
87
88 $this->userManager->updateUser($user, true);
89 }
90
91 private function isAdmin($user)
92 {
93 if ($this->ldapAdminFilter === null) {
94 return false;
95 }
96
97 $escaped_username = ldap_escape($user->getUsername(), '', LDAP_ESCAPE_FILTER);
98 $filter = sprintf($this->ldapAdminFilter, $escaped_username);
99 $entries = $this->ldapDriver->search($this->ldapBaseDn, $filter);
100
101 return $entries['count'] == 1;
102 }
103}
diff --git a/src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php b/src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php
new file mode 100644
index 00000000..8a851f12
--- /dev/null
+++ b/src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php
@@ -0,0 +1,43 @@
1<?php
2
3namespace Wallabag\UserBundle;
4
5use FOS\OAuthServerBundle\Storage\OAuthStorage;
6use OAuth2\Model\IOAuth2Client;
7use Symfony\Component\Security\Core\Exception\AuthenticationException;
8
9class OAuthStorageLdapWrapper extends OAuthStorage
10{
11 private $ldapManager;
12
13 public function setLdapManager($ldap_manager)
14 {
15 $this->ldapManager = $ldap_manager;
16 }
17
18 public function checkUserCredentials(IOAuth2Client $client, $username, $password)
19 {
20 try {
21 $user = $this->userProvider->loadUserByUsername($username);
22 } catch (AuthenticationException $e) {
23 return false;
24 }
25
26 if ($user->isLdapUser()) {
27 return $this->checkLdapUserCredentials($user, $password);
28 } else {
29 return parent::checkUserCredentials($client, $username, $password);
30 }
31 }
32
33 private function checkLdapUserCredentials($user, $password)
34 {
35 if ($this->ldapManager->bind($user, $password)) {
36 return array(
37 'data' => $user,
38 );
39 } else {
40 return false;
41 }
42 }
43}
diff --git a/src/Wallabag/UserBundle/Resources/config/ldap.yml b/src/Wallabag/UserBundle/Resources/config/ldap.yml
new file mode 100644
index 00000000..5ec16088
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/config/ldap.yml
@@ -0,0 +1,28 @@
1fr3d_ldap:
2 service:
3 user_hydrator: ldap_user_hydrator
4 driver:
5 host: "%ldap_host%"
6 port: "%ldap_port%"
7 useSsl: "%ldap_ssl%"
8 useStartTls: "%ldap_tls%"
9 bindRequiresDn: "%ldap_bind_requires_dn%"
10 username: "%ldap_manager_dn%"
11 password: "%ldap_manager_pw%"
12 user:
13 baseDn: "%ldap_base%"
14 filter: "%ldap_filter%"
15 usernameAttribute: "%ldap_username_attribute%"
16security:
17 providers:
18 chain_provider:
19 chain:
20 providers: [ fr3d_ldapbundle, fos_userbundle ]
21 fr3d_ldapbundle:
22 id: fr3d_ldap.security.user.provider
23 firewalls:
24 secured_area:
25 fr3d_ldap: ~
26 form_login:
27 provider: chain_provider
28
diff --git a/src/Wallabag/UserBundle/Resources/config/ldap_services.yml b/src/Wallabag/UserBundle/Resources/config/ldap_services.yml
new file mode 100644
index 00000000..b3e3fd8a
--- /dev/null
+++ b/src/Wallabag/UserBundle/Resources/config/ldap_services.yml
@@ -0,0 +1,22 @@
1services:
2 fos_oauth_server.server:
3 class: OAuth2\OAuth2
4 arguments:
5 - "@oauth_storage_ldap_wrapper"
6 - "%fos_oauth_server.server.options%"
7 oauth_storage_ldap_wrapper:
8 class: Wallabag\UserBundle\OAuthStorageLdapWrapper
9 parent: fos_oauth_server.storage
10 calls:
11 - [setLdapManager, ["@fr3d_ldap.ldap_manager"]]
12
13 ldap_user_hydrator:
14 class: Wallabag\UserBundle\LdapHydrator
15 arguments:
16 - "@fos_user.user_manager"
17 - "@event_dispatcher"
18 - [ "%ldap_username_attribute%", "%ldap_email_attribute%", "%ldap_name_attribute%", "%ldap_enabled_attribute%" ]
19 - "%ldap_base%"
20 - "%ldap_admin_filter%"
21 - "@fr3d_ldap.ldap_driver"
22