before_script:
- PHP=$TRAVIS_PHP_VERSION
+ - echo "extension=ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- if [[ ! $PHP = hhvm* ]]; then echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi;
# xdebug isn't enable for PHP 7.1
- if [[ ! $PHP = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
];
+ if (class_exists('FR3D\\LdapBundle\\FR3DLdapBundle')) {
+ $bundles[] = new FR3D\LdapBundle\FR3DLdapBundle();
+ }
+
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
$bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
--- /dev/null
+<?php
+
+namespace Application\Migrations;
+
+use Doctrine\DBAL\Migrations\AbstractMigration;
+use Doctrine\DBAL\Schema\Schema;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Added dn field on wallabag_users
+ */
+class Version20170710113900 extends AbstractMigration implements ContainerAwareInterface
+{
+ /**
+ * @var ContainerInterface
+ */
+ private $container;
+
+ public function setContainer(ContainerInterface $container = null)
+ {
+ $this->container = $container;
+ }
+
+ private function getTable($tableName)
+ {
+ return $this->container->getParameter('database_table_prefix').$tableName;
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function up(Schema $schema)
+ {
+ $usersTable = $schema->getTable($this->getTable('user'));
+
+ $this->skipIf($usersTable->hasColumn('dn'), 'It seems that you already played this migration.');
+
+ $usersTable->addColumn('dn', 'text', [
+ 'default' => null,
+ 'notnull' => false,
+ ]);
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function down(Schema $schema)
+ {
+ $usersTable = $schema->getTable($this->getTable('user'));
+ $usersTable->dropColumn('dn');
+ }
+}
+
redis_port: 6379
redis_path: null
redis_password: null
+
+ # ldap configuration
+ # To enable, you need to require fr3d/ldap-bundle
+ ldap_enabled: false
+ ldap_host: localhost
+ ldap_port: 389
+ ldap_tls: false
+ ldap_ssl: false
+ ldap_bind_requires_dn: true
+ ldap_base: dc=example,dc=com
+ ldap_manager_dn: ou=Manager,dc=example,dc=com
+ ldap_manager_pw: password
+ ldap_filter: (&(ObjectClass=Person))
+ # optional (if null: no ldap user is admin)
+ ldap_admin_filter: (&(memberOf=ou=admins,dc=example,dc=com)(uid=%s))
+ ldap_username_attribute: uid
+ ldap_email_attribute: mail
+ ldap_name_attribute: cn
+ # optional (default sets user as enabled unconditionally)
+ ldap_enabled_attribute: ~
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
+ # /!\ This list is modified in WallabagUserBundle when LDAP is enabled
providers:
administrators:
entity:
pattern: ^/login$
anonymous: ~
+ # /!\ This section is modified in WallabagUserBundle when LDAP is enabled
secured_area:
pattern: ^/
form_login:
"defuse/php-encryption": "^2.1",
"html2text/html2text": "^4.1"
},
+ "suggest": {
+ "fr3d/ldap-bundle": "If you want to authenticate via LDAP"
+ },
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "~2.2",
"doctrine/data-fixtures": "~1.1",
TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
git checkout $TAG
+if [ -n "$LDAP_ENABLED" ]; then
+ SYMFONY_ENV=$ENV $COMPOSER_COMMAND require --no-update fr3d/ldap-bundle
+fi
SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
php bin/console wallabag:install --env=$ENV
git fetch --tags
TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
git checkout $TAG --force
+if [ -n "$LDAP_ENABLED" ]; then
+ SYMFONY_ENV=$ENV $COMPOSER_COMMAND require --no-update fr3d/ldap-bundle
+fi
SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
php bin/console doctrine:migrations:migrate --no-interaction --env=$ENV
php bin/console cache:clear --env=$ENV
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
-class WallabagUserExtension extends Extension
+class WallabagUserExtension extends Extension implements PrependExtensionInterface
{
+ public function prepend(ContainerBuilder $container)
+ {
+ $ldap = $container->getParameter('ldap_enabled');
+
+ if ($ldap) {
+ $container->prependExtensionConfig('security', array(
+ 'providers' => array(
+ 'chain_provider' => array(),
+ ),
+ ));
+ $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+ $loader->load('ldap.yml');
+ } elseif ($container->hasExtension('fr3d_ldap')) {
+ $container->prependExtensionConfig('fr3_d_ldap', array(
+ 'driver' => array(
+ 'host' => 'localhost',
+ ),
+ 'user' => array(
+ 'baseDn' => 'dc=example,dc=com',
+ ),
+ ));
+ }
+ }
+
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
+ if ($container->getParameter('ldap_enabled')) {
+ $loader->load('ldap_services.yml');
+ }
$container->setParameter('wallabag_user.registration_enabled', $config['registration_enabled']);
}
<?php
+// This permits to have the LdapUserInterface even when fr3d/ldap-bundle is not
+// in the packages
+namespace FR3D\LdapBundle\Model;
+
+interface LdapUserInterface
+{
+ public function setDn($dn);
+ public function getDn();
+}
+
namespace Wallabag\UserBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Wallabag\CoreBundle\Entity\Config;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
+use FR3D\LdapBundle\Model\LdapUserInterface;
/**
* User.
* @UniqueEntity("email")
* @UniqueEntity("username")
*/
-class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface
+class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface, LdapUserInterface
{
use EntityTimestampsTrait;
*/
protected $email;
+ /**
+ * @var string
+ *
+ * @ORM\Column(name="dn", type="text", nullable=true)
+ */
+ protected $dn;
+
/**
* @var \DateTime
*
return $this->clients->first();
}
}
+
+ /**
+ * Set dn.
+ *
+ * @param string $dn
+ *
+ * @return User
+ */
+ public function setDn($dn)
+ {
+ $this->dn = $dn;
+
+ return $this;
+ }
+
+ /**
+ * Get dn.
+ *
+ * @return string
+ */
+ public function getDn()
+ {
+ return $this->dn;
+ }
+
+ public function isLdapUser()
+ {
+ return $this->dn !== null;
+ }
}
--- /dev/null
+<?php
+
+namespace Wallabag\UserBundle;
+
+use FR3D\LdapBundle\Hydrator\HydratorInterface;
+use FOS\UserBundle\FOSUserEvents;
+use FOS\UserBundle\Event\UserEvent;
+
+class LdapHydrator implements HydratorInterface
+{
+ private $userManager;
+ private $eventDispatcher;
+ private $attributesMap;
+ private $enabledAttribute;
+ private $ldapBaseDn;
+ private $ldapAdminFilter;
+ private $ldapDriver;
+
+ public function __construct(
+ $user_manager,
+ $event_dispatcher,
+ array $attributes_map,
+ $ldap_base_dn,
+ $ldap_admin_filter,
+ $ldap_driver
+ ) {
+ $this->userManager = $user_manager;
+ $this->eventDispatcher = $event_dispatcher;
+
+ $this->attributesMap = array(
+ 'setUsername' => $attributes_map[0],
+ 'setEmail' => $attributes_map[1],
+ 'setName' => $attributes_map[2],
+ );
+ $this->enabledAttribute = $attributes_map[3];
+
+ $this->ldapBaseDn = $ldap_base_dn;
+ $this->ldapAdminFilter = $ldap_admin_filter;
+ $this->ldapDriver = $ldap_driver;
+ }
+
+ public function hydrate(array $ldapEntry)
+ {
+ $user = $this->userManager->findUserBy(array('dn' => $ldapEntry['dn']));
+
+ if (!$user) {
+ $user = $this->userManager->createUser();
+ $user->setDn($ldapEntry['dn']);
+ $user->setPassword('');
+ $user->setSalt('');
+ $this->updateUserFields($user, $ldapEntry);
+
+ $event = new UserEvent($user);
+ $this->eventDispatcher->dispatch(FOSUserEvents::USER_CREATED, $event);
+
+ $this->userManager->reloadUser($user);
+ } else {
+ $this->updateUserFields($user, $ldapEntry);
+ }
+
+ return $user;
+ }
+
+ private function updateUserFields($user, $ldapEntry)
+ {
+ foreach ($this->attributesMap as $key => $value) {
+ if (is_array($ldapEntry[$value])) {
+ $ldap_value = $ldapEntry[$value][0];
+ } else {
+ $ldap_value = $ldapEntry[$value];
+ }
+
+ call_user_func([$user, $key], $ldap_value);
+ }
+
+ if ($this->enabledAttribute !== null) {
+ $user->setEnabled($ldapEntry[$this->enabledAttribute]);
+ } else {
+ $user->setEnabled(true);
+ }
+
+ if ($this->isAdmin($user)) {
+ $user->addRole('ROLE_SUPER_ADMIN');
+ } else {
+ $user->removeRole('ROLE_SUPER_ADMIN');
+ }
+
+ $this->userManager->updateUser($user, true);
+ }
+
+ private function isAdmin($user)
+ {
+ if ($this->ldapAdminFilter === null) {
+ return false;
+ }
+
+ $escaped_username = ldap_escape($user->getUsername(), '', LDAP_ESCAPE_FILTER);
+ $filter = sprintf($this->ldapAdminFilter, $escaped_username);
+ $entries = $this->ldapDriver->search($this->ldapBaseDn, $filter);
+
+ return $entries['count'] == 1;
+ }
+}
--- /dev/null
+<?php
+
+namespace Wallabag\UserBundle;
+
+use FOS\OAuthServerBundle\Storage\OAuthStorage;
+use OAuth2\Model\IOAuth2Client;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+
+class OAuthStorageLdapWrapper extends OAuthStorage
+{
+ private $ldapManager;
+
+ public function setLdapManager($ldap_manager)
+ {
+ $this->ldapManager = $ldap_manager;
+ }
+
+ public function checkUserCredentials(IOAuth2Client $client, $username, $password)
+ {
+ try {
+ $user = $this->userProvider->loadUserByUsername($username);
+ } catch (AuthenticationException $e) {
+ return false;
+ }
+
+ if ($user->isLdapUser()) {
+ return $this->checkLdapUserCredentials($user, $password);
+ } else {
+ return parent::checkUserCredentials($client, $username, $password);
+ }
+ }
+
+ private function checkLdapUserCredentials($user, $password)
+ {
+ if ($this->ldapManager->bind($user, $password)) {
+ return array(
+ 'data' => $user,
+ );
+ } else {
+ return false;
+ }
+ }
+}
--- /dev/null
+fr3d_ldap:
+ service:
+ user_hydrator: ldap_user_hydrator
+ driver:
+ host: "%ldap_host%"
+ port: "%ldap_port%"
+ useSsl: "%ldap_ssl%"
+ useStartTls: "%ldap_tls%"
+ bindRequiresDn: "%ldap_bind_requires_dn%"
+ username: "%ldap_manager_dn%"
+ password: "%ldap_manager_pw%"
+ user:
+ baseDn: "%ldap_base%"
+ filter: "%ldap_filter%"
+ usernameAttribute: "%ldap_username_attribute%"
+security:
+ providers:
+ chain_provider:
+ chain:
+ providers: [ fr3d_ldapbundle, fos_userbundle ]
+ fr3d_ldapbundle:
+ id: fr3d_ldap.security.user.provider
+ firewalls:
+ secured_area:
+ fr3d_ldap: ~
+ form_login:
+ provider: chain_provider
+
--- /dev/null
+services:
+ fos_oauth_server.server:
+ class: OAuth2\OAuth2
+ arguments:
+ - "@oauth_storage_ldap_wrapper"
+ - "%fos_oauth_server.server.options%"
+ oauth_storage_ldap_wrapper:
+ class: Wallabag\UserBundle\OAuthStorageLdapWrapper
+ parent: fos_oauth_server.storage
+ calls:
+ - [setLdapManager, ["@fr3d_ldap.ldap_manager"]]
+
+ ldap_user_hydrator:
+ class: Wallabag\UserBundle\LdapHydrator
+ arguments:
+ - "@fos_user.user_manager"
+ - "@event_dispatcher"
+ - [ "%ldap_username_attribute%", "%ldap_email_attribute%", "%ldap_name_attribute%", "%ldap_enabled_attribute%" ]
+ - "%ldap_base%"
+ - "%ldap_admin_filter%"
+ - "@fr3d_ldap.ldap_driver"
+