]> git.immae.eu Git - perso/Immae/Config/Nix.git/blob - virtual/modules/websites/tools/tools/wallabag_ldap.patch
Add wallabag
[perso/Immae/Config/Nix.git] / virtual / modules / websites / tools / tools / wallabag_ldap.patch
1 commit 4cd6e7f3bbcff7e2a1c5a39917176d28fa02911f
2 Author: Ismaƫl Bouya <ismael.bouya@normalesup.org>
3 Date: Sat Jun 16 11:40:00 2018 +0200
4
5 Add ldap
6
7 diff --git a/.travis.yml b/.travis.yml
8 index 57b3aa53..3b7638eb 100644
9 --- a/.travis.yml
10 +++ b/.travis.yml
11 @@ -54,6 +54,7 @@ branches:
12
13 before_script:
14 - PHP=$TRAVIS_PHP_VERSION
15 + - echo "extension=ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
16 - if [[ ! $PHP = hhvm* ]]; then echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi;
17 # xdebug isn't enable for PHP 7.1
18 - if [[ ! $PHP = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
19 diff --git a/app/AppKernel.php b/app/AppKernel.php
20 index 40726f05..c4f465dc 100644
21 --- a/app/AppKernel.php
22 +++ b/app/AppKernel.php
23 @@ -42,6 +42,10 @@ class AppKernel extends Kernel
24 new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
25 ];
26
27 + if (class_exists('FR3D\\LdapBundle\\FR3DLdapBundle')) {
28 + $bundles[] = new FR3D\LdapBundle\FR3DLdapBundle();
29 + }
30 +
31 if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
32 $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
33 $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
34 diff --git a/app/DoctrineMigrations/Version20170710113900.php b/app/DoctrineMigrations/Version20170710113900.php
35 new file mode 100644
36 index 00000000..7be83110
37 --- /dev/null
38 +++ b/app/DoctrineMigrations/Version20170710113900.php
39 @@ -0,0 +1,54 @@
40 +<?php
41 +
42 +namespace Application\Migrations;
43 +
44 +use Doctrine\DBAL\Migrations\AbstractMigration;
45 +use Doctrine\DBAL\Schema\Schema;
46 +use Symfony\Component\DependencyInjection\ContainerAwareInterface;
47 +use Symfony\Component\DependencyInjection\ContainerInterface;
48 +
49 +/**
50 + * Added dn field on wallabag_users
51 + */
52 +class Version20170710113900 extends AbstractMigration implements ContainerAwareInterface
53 +{
54 + /**
55 + * @var ContainerInterface
56 + */
57 + private $container;
58 +
59 + public function setContainer(ContainerInterface $container = null)
60 + {
61 + $this->container = $container;
62 + }
63 +
64 + private function getTable($tableName)
65 + {
66 + return $this->container->getParameter('database_table_prefix').$tableName;
67 + }
68 +
69 + /**
70 + * @param Schema $schema
71 + */
72 + public function up(Schema $schema)
73 + {
74 + $usersTable = $schema->getTable($this->getTable('user'));
75 +
76 + $this->skipIf($usersTable->hasColumn('dn'), 'It seems that you already played this migration.');
77 +
78 + $usersTable->addColumn('dn', 'text', [
79 + 'default' => null,
80 + 'notnull' => false,
81 + ]);
82 + }
83 +
84 + /**
85 + * @param Schema $schema
86 + */
87 + public function down(Schema $schema)
88 + {
89 + $usersTable = $schema->getTable($this->getTable('user'));
90 + $usersTable->dropColumn('dn');
91 + }
92 +}
93 +
94 diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist
95 index 6b0cb8e8..cfd41b69 100644
96 --- a/app/config/parameters.yml.dist
97 +++ b/app/config/parameters.yml.dist
98 @@ -62,3 +62,23 @@ parameters:
99 redis_port: 6379
100 redis_path: null
101 redis_password: null
102 +
103 + # ldap configuration
104 + # To enable, you need to require fr3d/ldap-bundle
105 + ldap_enabled: false
106 + ldap_host: localhost
107 + ldap_port: 389
108 + ldap_tls: false
109 + ldap_ssl: false
110 + ldap_bind_requires_dn: true
111 + ldap_base: dc=example,dc=com
112 + ldap_manager_dn: ou=Manager,dc=example,dc=com
113 + ldap_manager_pw: password
114 + ldap_filter: (&(ObjectClass=Person))
115 + # optional (if null: no ldap user is admin)
116 + ldap_admin_filter: (&(memberOf=ou=admins,dc=example,dc=com)(uid=%s))
117 + ldap_username_attribute: uid
118 + ldap_email_attribute: mail
119 + ldap_name_attribute: cn
120 + # optional (default sets user as enabled unconditionally)
121 + ldap_enabled_attribute: ~
122 diff --git a/app/config/security.yml b/app/config/security.yml
123 index 796dc361..59f48626 100644
124 --- a/app/config/security.yml
125 +++ b/app/config/security.yml
126 @@ -6,6 +6,7 @@ security:
127 ROLE_ADMIN: ROLE_USER
128 ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
129
130 + # /!\ This list is modified in WallabagUserBundle when LDAP is enabled
131 providers:
132 administrators:
133 entity:
134 @@ -36,6 +37,7 @@ security:
135 pattern: ^/login$
136 anonymous: ~
137
138 + # /!\ This section is modified in WallabagUserBundle when LDAP is enabled
139 secured_area:
140 pattern: ^/
141 form_login:
142 diff --git a/composer.json b/composer.json
143 index dca274ed..f115d229 100644
144 --- a/composer.json
145 +++ b/composer.json
146 @@ -87,6 +87,9 @@
147 "defuse/php-encryption": "^2.1",
148 "html2text/html2text": "^4.1"
149 },
150 + "suggest": {
151 + "fr3d/ldap-bundle": "If you want to authenticate via LDAP"
152 + },
153 "require-dev": {
154 "doctrine/doctrine-fixtures-bundle": "~2.2",
155 "doctrine/data-fixtures": "~1.1",
156 diff --git a/scripts/install.sh b/scripts/install.sh
157 index 62a46f4f..5ea3933c 100644
158 --- a/scripts/install.sh
159 +++ b/scripts/install.sh
160 @@ -12,5 +12,8 @@ ENV=$1
161 TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
162
163 git checkout $TAG
164 +if [ -n "$LDAP_ENABLED" ]; then
165 + SYMFONY_ENV=$ENV $COMPOSER_COMMAND require --no-update fr3d/ldap-bundle
166 +fi
167 SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
168 php bin/console wallabag:install --env=$ENV
169 diff --git a/scripts/update.sh b/scripts/update.sh
170 index d0598135..753ccbc3 100644
171 --- a/scripts/update.sh
172 +++ b/scripts/update.sh
173 @@ -18,6 +18,9 @@ git fetch origin
174 git fetch --tags
175 TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
176 git checkout $TAG --force
177 +if [ -n "$LDAP_ENABLED" ]; then
178 + SYMFONY_ENV=$ENV $COMPOSER_COMMAND require --no-update fr3d/ldap-bundle
179 +fi
180 SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
181 php bin/console doctrine:migrations:migrate --no-interaction --env=$ENV
182 php bin/console cache:clear --env=$ENV
183 diff --git a/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php b/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php
184 index 5ca3482e..904a6af1 100644
185 --- a/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php
186 +++ b/src/Wallabag/UserBundle/DependencyInjection/WallabagUserExtension.php
187 @@ -6,9 +6,34 @@ use Symfony\Component\Config\FileLocator;
188 use Symfony\Component\DependencyInjection\ContainerBuilder;
189 use Symfony\Component\DependencyInjection\Loader;
190 use Symfony\Component\HttpKernel\DependencyInjection\Extension;
191 +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
192
193 -class WallabagUserExtension extends Extension
194 +class WallabagUserExtension extends Extension implements PrependExtensionInterface
195 {
196 + public function prepend(ContainerBuilder $container)
197 + {
198 + $ldap = $container->getParameter('ldap_enabled');
199 +
200 + if ($ldap) {
201 + $container->prependExtensionConfig('security', array(
202 + 'providers' => array(
203 + 'chain_provider' => array(),
204 + ),
205 + ));
206 + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
207 + $loader->load('ldap.yml');
208 + } elseif ($container->hasExtension('fr3d_ldap')) {
209 + $container->prependExtensionConfig('fr3_d_ldap', array(
210 + 'driver' => array(
211 + 'host' => 'localhost',
212 + ),
213 + 'user' => array(
214 + 'baseDn' => 'dc=example,dc=com',
215 + ),
216 + ));
217 + }
218 + }
219 +
220 public function load(array $configs, ContainerBuilder $container)
221 {
222 $configuration = new Configuration();
223 @@ -16,6 +41,9 @@ class WallabagUserExtension extends Extension
224
225 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
226 $loader->load('services.yml');
227 + if ($container->getParameter('ldap_enabled')) {
228 + $loader->load('ldap_services.yml');
229 + }
230 $container->setParameter('wallabag_user.registration_enabled', $config['registration_enabled']);
231 }
232
233 diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
234 index 48446e3c..f93c59c7 100644
235 --- a/src/Wallabag/UserBundle/Entity/User.php
236 +++ b/src/Wallabag/UserBundle/Entity/User.php
237 @@ -1,5 +1,15 @@
238 <?php
239
240 +// This permits to have the LdapUserInterface even when fr3d/ldap-bundle is not
241 +// in the packages
242 +namespace FR3D\LdapBundle\Model;
243 +
244 +interface LdapUserInterface
245 +{
246 + public function setDn($dn);
247 + public function getDn();
248 +}
249 +
250 namespace Wallabag\UserBundle\Entity;
251
252 use Doctrine\Common\Collections\ArrayCollection;
253 @@ -16,6 +26,7 @@ use Wallabag\ApiBundle\Entity\Client;
254 use Wallabag\CoreBundle\Entity\Config;
255 use Wallabag\CoreBundle\Entity\Entry;
256 use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
257 +use FR3D\LdapBundle\Model\LdapUserInterface;
258
259 /**
260 * User.
261 @@ -28,7 +39,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
262 * @UniqueEntity("email")
263 * @UniqueEntity("username")
264 */
265 -class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface
266 +class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface, LdapUserInterface
267 {
268 use EntityTimestampsTrait;
269
270 @@ -67,6 +78,13 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
271 */
272 protected $email;
273
274 + /**
275 + * @var string
276 + *
277 + * @ORM\Column(name="dn", type="text", nullable=true)
278 + */
279 + protected $dn;
280 +
281 /**
282 * @var \DateTime
283 *
284 @@ -309,4 +327,33 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
285 return $this->clients->first();
286 }
287 }
288 +
289 + /**
290 + * Set dn.
291 + *
292 + * @param string $dn
293 + *
294 + * @return User
295 + */
296 + public function setDn($dn)
297 + {
298 + $this->dn = $dn;
299 +
300 + return $this;
301 + }
302 +
303 + /**
304 + * Get dn.
305 + *
306 + * @return string
307 + */
308 + public function getDn()
309 + {
310 + return $this->dn;
311 + }
312 +
313 + public function isLdapUser()
314 + {
315 + return $this->dn !== null;
316 + }
317 }
318 diff --git a/src/Wallabag/UserBundle/LdapHydrator.php b/src/Wallabag/UserBundle/LdapHydrator.php
319 new file mode 100644
320 index 00000000..cea2450f
321 --- /dev/null
322 +++ b/src/Wallabag/UserBundle/LdapHydrator.php
323 @@ -0,0 +1,103 @@
324 +<?php
325 +
326 +namespace Wallabag\UserBundle;
327 +
328 +use FR3D\LdapBundle\Hydrator\HydratorInterface;
329 +use FOS\UserBundle\FOSUserEvents;
330 +use FOS\UserBundle\Event\UserEvent;
331 +
332 +class LdapHydrator implements HydratorInterface
333 +{
334 + private $userManager;
335 + private $eventDispatcher;
336 + private $attributesMap;
337 + private $enabledAttribute;
338 + private $ldapBaseDn;
339 + private $ldapAdminFilter;
340 + private $ldapDriver;
341 +
342 + public function __construct(
343 + $user_manager,
344 + $event_dispatcher,
345 + array $attributes_map,
346 + $ldap_base_dn,
347 + $ldap_admin_filter,
348 + $ldap_driver
349 + ) {
350 + $this->userManager = $user_manager;
351 + $this->eventDispatcher = $event_dispatcher;
352 +
353 + $this->attributesMap = array(
354 + 'setUsername' => $attributes_map[0],
355 + 'setEmail' => $attributes_map[1],
356 + 'setName' => $attributes_map[2],
357 + );
358 + $this->enabledAttribute = $attributes_map[3];
359 +
360 + $this->ldapBaseDn = $ldap_base_dn;
361 + $this->ldapAdminFilter = $ldap_admin_filter;
362 + $this->ldapDriver = $ldap_driver;
363 + }
364 +
365 + public function hydrate(array $ldapEntry)
366 + {
367 + $user = $this->userManager->findUserBy(array('dn' => $ldapEntry['dn']));
368 +
369 + if (!$user) {
370 + $user = $this->userManager->createUser();
371 + $user->setDn($ldapEntry['dn']);
372 + $user->setPassword('');
373 + $user->setSalt('');
374 + $this->updateUserFields($user, $ldapEntry);
375 +
376 + $event = new UserEvent($user);
377 + $this->eventDispatcher->dispatch(FOSUserEvents::USER_CREATED, $event);
378 +
379 + $this->userManager->reloadUser($user);
380 + } else {
381 + $this->updateUserFields($user, $ldapEntry);
382 + }
383 +
384 + return $user;
385 + }
386 +
387 + private function updateUserFields($user, $ldapEntry)
388 + {
389 + foreach ($this->attributesMap as $key => $value) {
390 + if (is_array($ldapEntry[$value])) {
391 + $ldap_value = $ldapEntry[$value][0];
392 + } else {
393 + $ldap_value = $ldapEntry[$value];
394 + }
395 +
396 + call_user_func([$user, $key], $ldap_value);
397 + }
398 +
399 + if ($this->enabledAttribute !== null) {
400 + $user->setEnabled($ldapEntry[$this->enabledAttribute]);
401 + } else {
402 + $user->setEnabled(true);
403 + }
404 +
405 + if ($this->isAdmin($user)) {
406 + $user->addRole('ROLE_SUPER_ADMIN');
407 + } else {
408 + $user->removeRole('ROLE_SUPER_ADMIN');
409 + }
410 +
411 + $this->userManager->updateUser($user, true);
412 + }
413 +
414 + private function isAdmin($user)
415 + {
416 + if ($this->ldapAdminFilter === null) {
417 + return false;
418 + }
419 +
420 + $escaped_username = ldap_escape($user->getUsername(), '', LDAP_ESCAPE_FILTER);
421 + $filter = sprintf($this->ldapAdminFilter, $escaped_username);
422 + $entries = $this->ldapDriver->search($this->ldapBaseDn, $filter);
423 +
424 + return $entries['count'] == 1;
425 + }
426 +}
427 diff --git a/src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php b/src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php
428 new file mode 100644
429 index 00000000..8a851f12
430 --- /dev/null
431 +++ b/src/Wallabag/UserBundle/OAuthStorageLdapWrapper.php
432 @@ -0,0 +1,43 @@
433 +<?php
434 +
435 +namespace Wallabag\UserBundle;
436 +
437 +use FOS\OAuthServerBundle\Storage\OAuthStorage;
438 +use OAuth2\Model\IOAuth2Client;
439 +use Symfony\Component\Security\Core\Exception\AuthenticationException;
440 +
441 +class OAuthStorageLdapWrapper extends OAuthStorage
442 +{
443 + private $ldapManager;
444 +
445 + public function setLdapManager($ldap_manager)
446 + {
447 + $this->ldapManager = $ldap_manager;
448 + }
449 +
450 + public function checkUserCredentials(IOAuth2Client $client, $username, $password)
451 + {
452 + try {
453 + $user = $this->userProvider->loadUserByUsername($username);
454 + } catch (AuthenticationException $e) {
455 + return false;
456 + }
457 +
458 + if ($user->isLdapUser()) {
459 + return $this->checkLdapUserCredentials($user, $password);
460 + } else {
461 + return parent::checkUserCredentials($client, $username, $password);
462 + }
463 + }
464 +
465 + private function checkLdapUserCredentials($user, $password)
466 + {
467 + if ($this->ldapManager->bind($user, $password)) {
468 + return array(
469 + 'data' => $user,
470 + );
471 + } else {
472 + return false;
473 + }
474 + }
475 +}
476 diff --git a/src/Wallabag/UserBundle/Resources/config/ldap.yml b/src/Wallabag/UserBundle/Resources/config/ldap.yml
477 new file mode 100644
478 index 00000000..5ec16088
479 --- /dev/null
480 +++ b/src/Wallabag/UserBundle/Resources/config/ldap.yml
481 @@ -0,0 +1,28 @@
482 +fr3d_ldap:
483 + service:
484 + user_hydrator: ldap_user_hydrator
485 + driver:
486 + host: "%ldap_host%"
487 + port: "%ldap_port%"
488 + useSsl: "%ldap_ssl%"
489 + useStartTls: "%ldap_tls%"
490 + bindRequiresDn: "%ldap_bind_requires_dn%"
491 + username: "%ldap_manager_dn%"
492 + password: "%ldap_manager_pw%"
493 + user:
494 + baseDn: "%ldap_base%"
495 + filter: "%ldap_filter%"
496 + usernameAttribute: "%ldap_username_attribute%"
497 +security:
498 + providers:
499 + chain_provider:
500 + chain:
501 + providers: [ fr3d_ldapbundle, fos_userbundle ]
502 + fr3d_ldapbundle:
503 + id: fr3d_ldap.security.user.provider
504 + firewalls:
505 + secured_area:
506 + fr3d_ldap: ~
507 + form_login:
508 + provider: chain_provider
509 +
510 diff --git a/src/Wallabag/UserBundle/Resources/config/ldap_services.yml b/src/Wallabag/UserBundle/Resources/config/ldap_services.yml
511 new file mode 100644
512 index 00000000..b3e3fd8a
513 --- /dev/null
514 +++ b/src/Wallabag/UserBundle/Resources/config/ldap_services.yml
515 @@ -0,0 +1,22 @@
516 +services:
517 + fos_oauth_server.server:
518 + class: OAuth2\OAuth2
519 + arguments:
520 + - "@oauth_storage_ldap_wrapper"
521 + - "%fos_oauth_server.server.options%"
522 + oauth_storage_ldap_wrapper:
523 + class: Wallabag\UserBundle\OAuthStorageLdapWrapper
524 + parent: fos_oauth_server.storage
525 + calls:
526 + - [setLdapManager, ["@fr3d_ldap.ldap_manager"]]
527 +
528 + ldap_user_hydrator:
529 + class: Wallabag\UserBundle\LdapHydrator
530 + arguments:
531 + - "@fos_user.user_manager"
532 + - "@event_dispatcher"
533 + - [ "%ldap_username_attribute%", "%ldap_email_attribute%", "%ldap_name_attribute%", "%ldap_enabled_attribute%" ]
534 + - "%ldap_base%"
535 + - "%ldap_admin_filter%"
536 + - "@fr3d_ldap.ldap_driver"
537 +