From 7e7435469d2674bdb4b31b89508d4affbbdc5736 Mon Sep 17 00:00:00 2001 From: jygaulier Date: Thu, 1 Dec 2022 16:27:54 +0100 Subject: [PATCH] PHRAS-2995_phr-as-auth-idp-BIS (#4180) * WIP ; allow multiple instances of some auth clients (migrate conf) ; add ps-oauth ; remove google+ * WIP ; fix ps-oauth ; fix tests * WIP ; add api-auth to extra_hosts * WIP ; fix yaml syntax error * WIP ; fix yaml syntax error ; better conf sample * WIP ; add "everyone-group" * WIP ; add the provider-id in session * WIP ; add getOptions() to providers (so phr/expose can filter providers) * WIP ; add auto-logout option so logout from phr also logouts from ps-auth * connect to expose using IDP from connected user * WIP ; auto-logout redirects to phr home * unnecessary session var * unused * fix * catch some error * fix navigation in train thumbnail * update file version * pass params in session to have constant redirect_uri ; add debug * invalidate session --> parade test ok * cleanup --- .../dist/production.js | 4 + .../dist/production.min.js | 4 + .../src/components/ui/workzone/index.js | 4 + composer.json | 1 - composer.lock | 134 +--- config/configuration.sample.yml | 36 +- docker-compose.under-phrasea.yml | 4 + .../Provider/AbstractProvider.php | 208 ++++- .../Authentication/Provider/Facebook.php | 77 +- .../Authentication/Provider/Factory.php | 65 +- .../Authentication/Provider/Github.php | 68 +- .../Authentication/Provider/GooglePlus.php | 330 -------- .../Authentication/Provider/Linkedin.php | 65 +- .../Provider/ProviderInterface.php | 12 +- .../Authentication/Provider/PsAuth.php | 732 ++++++++++++++++++ .../Authentication/Provider/Twitter.php | 73 +- .../Authentication/Provider/Viadeo.php | 67 +- .../Controller/Root/LoginController.php | 47 +- .../AuthenticationManagerServiceProvider.php | 49 +- lib/Alchemy/Phrasea/Core/Version.php | 2 +- .../Controller/PSExposeController.php | 237 ++++-- lib/classes/patch/417PHRAS2995.php | 114 +++ lib/conf.d/configuration.yml | 38 +- .../roles/app/templates/configuration.tpl | 31 +- .../config/phraseanet/configuration.yml | 39 +- resources/www/account/styles/_skin.scss | 5 - resources/www/account/styles/_variables.scss | 1 - .../www/authentication/styles/_variables.scss | 1 - templates/web/login/common/macros.html.twig | 12 +- templates/web/login/index.html.twig | 10 +- templates/web/login/register.html.twig | 9 +- templates/web/prod/WorkZone/Macros.html.twig | 15 +- .../Authentication/Provider/FacebookTest.php | 2 +- .../Authentication/Provider/FactoryTest.php | 81 +- .../Authentication/Provider/GithubTest.php | 2 +- .../Provider/GooglePlusTest.php | 289 ------- .../Authentication/Provider/LinkedinTest.php | 2 +- .../Provider/ProviderTestCase.php | 46 +- .../Authentication/Provider/ViadeoTest.php | 4 +- .../Fixtures/configuration-setup.yml | 31 +- .../Fixtures/configuration-with-hosts.yml | 31 +- .../Configuration/Fixtures/configuration.yml | 31 +- .../Fixtures/configuration-maintenance.yml | 31 +- 43 files changed, 1903 insertions(+), 1141 deletions(-) delete mode 100644 lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php create mode 100644 lib/Alchemy/Phrasea/Authentication/Provider/PsAuth.php create mode 100644 lib/classes/patch/417PHRAS2995.php delete mode 100644 tests/Alchemy/Tests/Phrasea/Authentication/Provider/GooglePlusTest.php diff --git a/Phraseanet-production-client/dist/production.js b/Phraseanet-production-client/dist/production.js index 2348f1f26c..b1ab233f61 100644 --- a/Phraseanet-production-client/dist/production.js +++ b/Phraseanet-production-client/dist/production.js @@ -11142,6 +11142,10 @@ var workzone = function workzone(services) { (0, _jquery2.default)('.expose_field_mapping').addClass('hidden'); (0, _jquery2.default)('.add_expose_block').addClass('hidden'); } + + if ('error' in data) { + (0, _jquery2.default)('.publication-list').empty().html(data.error); + } } }); } diff --git a/Phraseanet-production-client/dist/production.min.js b/Phraseanet-production-client/dist/production.min.js index 2348f1f26c..b1ab233f61 100644 --- a/Phraseanet-production-client/dist/production.min.js +++ b/Phraseanet-production-client/dist/production.min.js @@ -11142,6 +11142,10 @@ var workzone = function workzone(services) { (0, _jquery2.default)('.expose_field_mapping').addClass('hidden'); (0, _jquery2.default)('.add_expose_block').addClass('hidden'); } + + if ('error' in data) { + (0, _jquery2.default)('.publication-list').empty().html(data.error); + } } }); } diff --git a/Phraseanet-production-client/src/components/ui/workzone/index.js b/Phraseanet-production-client/src/components/ui/workzone/index.js index 92090f90d2..5e98f74e0c 100644 --- a/Phraseanet-production-client/src/components/ui/workzone/index.js +++ b/Phraseanet-production-client/src/components/ui/workzone/index.js @@ -1098,6 +1098,10 @@ const workzone = (services) => { $('.expose_field_mapping').addClass('hidden'); $('.add_expose_block').addClass('hidden'); } + + if ('error' in data) { + $('.publication-list').empty().html(data.error); + } } }); } diff --git a/composer.json b/composer.json index ce73b4dd66..59dc36ed53 100644 --- a/composer.json +++ b/composer.json @@ -109,7 +109,6 @@ "php-xpdf/php-xpdf": "~0.2.1", "exiftool/exiftool": "^11", "ramsey/uuid": "^3.0", - "roave/security-advisories": "dev-master", "silex/silex": "^1.3.0", "silex/web-profiler": "~1.0", "simple-bus/doctrine-orm-bridge": "^4.0", diff --git a/composer.lock b/composer.lock index e6dc65ec97..7e0a50b305 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4266645a17e1c8fc0ca7178ed2df0952", + "content-hash": "5c6ceff0275f1bf8ebe67d19405bc456", "packages": [ { "name": "alchemy-fr/tcpdf-clone", @@ -5975,137 +5975,6 @@ ], "time": "2016-12-22T14:09:01+00:00" }, - { - "name": "roave/security-advisories", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "0698207bf8a9bed212fdde2d8c7cdc77085660c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0698207bf8a9bed212fdde2d8c7cdc77085660c4", - "reference": "0698207bf8a9bed212fdde2d8c7cdc77085660c4", - "shasum": "" - }, - "conflict": { - "adodb/adodb-php": "<5.20.6", - "amphp/artax": ">=2,<2.0.4|>0.7.1,<1.0.4", - "aws/aws-sdk-php": ">=3,<3.2.1", - "bugsnag/bugsnag-laravel": ">=2,<2.0.2", - "cakephp/cakephp": ">=3,<3.0.15|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=1.3,<1.3.18|>=2.7,<2.7.6|>=3.1,<3.1.4", - "cartalyst/sentry": "<2.1", - "codeigniter/framework": "<=3.0.6", - "composer/composer": "<=1.0.0-alpha11", - "contao-components/mediaelement": ">=2.14.2,<2.21.1", - "contao/core": ">=2.11,<3.5.15", - "doctrine/annotations": ">=1,<1.2.7", - "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", - "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", - "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2", - "doctrine/doctrine-bundle": "<1.5.2", - "doctrine/doctrine-module": "<=0.7.1", - "doctrine/mongodb-odm": ">=1,<1.0.2", - "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", - "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", - "dompdf/dompdf": ">=0.6,<0.6.2", - "drupal/core": ">=8,<8.2.3", - "drupal/drupal": ">=8,<8.2.3", - "firebase/php-jwt": "<2", - "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", - "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", - "gregwar/rst": "<1.0.3", - "guzzlehttp/guzzle": ">=6,<6.2.1|>=4.0.0-rc2,<4.2.4|>=5,<5.3.1", - "illuminate/auth": ">=4,<4.0.99|>=4.1,<4.1.26", - "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", - "joomla/session": "<1.3.1", - "laravel/framework": ">=4,<4.0.99|>=4.1,<4.1.29", - "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", - "magento/magento2ce": ">=2,<2.2", - "monolog/monolog": ">=1.8,<1.12", - "namshi/jose": "<2.2", - "oro/crm": ">=1.7,<1.7.4", - "oro/platform": ">=1.7,<1.7.4", - "phpmailer/phpmailer": ">=5,<5.2.22", - "pusher/pusher-php-server": "<2.2.1", - "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", - "shopware/shopware": "<4.4|>=5,<5.2.15", - "silverstripe/cms": ">=3.1,<3.1.11|>=3,<=3.0.11", - "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": ">=3,<3.3", - "silverstripe/userforms": "<3", - "simplesamlphp/saml2": "<1.8.1|>=1.9,<1.9.1|>=1.10,<1.10.3|>=2,<2.3.3", - "simplesamlphp/simplesamlphp": "<1.14.11", - "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", - "socalnick/scn-social-auth": "<1.15.2", - "swiftmailer/swiftmailer": ">=4,<5.4.5", - "symfony/dependency-injection": ">=2,<2.0.17", - "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.7", - "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2", - "symfony/http-foundation": ">=2,<2.3.27|>=2.4,<2.5.11|>=2.6,<2.6.6", - "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8", - "symfony/routing": ">=2,<2.0.19", - "symfony/security": ">=2.3,<2.3.37|>=2.4,<2.6.13|>=2.7,<2.7.9|>=2,<2.0.25|>=2.1,<2.1.13|>=2.2,<2.2.9", - "symfony/security-core": ">=2.8,<2.8.6|>=3,<3.0.6|>=2.4,<2.6.13|>=2.7,<2.7.9", - "symfony/security-http": ">=2.4,<2.7.13|>=2.3,<2.3.41|>=2.8,<2.8.6|>=3,<3.0.6", - "symfony/serializer": ">=2,<2.0.11", - "symfony/symfony": ">=2,<2.3.41|>=2.4,<2.7.13|>=2.8,<2.8.6|>=3,<3.0.6", - "symfony/translation": ">=2,<2.0.17", - "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", - "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", - "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", - "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", - "twig/twig": "<1.20", - "typo3/cms": ">=6.2,<6.2.30|>=8,<8.4.1|>=7,<7.6.13", - "typo3/flow": ">=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5|>=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1", - "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", - "willdurand/js-translation-bundle": "<2.1.1", - "yiisoft/yii": ">=1.1.14,<1.1.15", - "yiisoft/yii2": "<2.0.5", - "yiisoft/yii2-bootstrap": "<2.0.4", - "yiisoft/yii2-dev": "<2.0.4", - "yiisoft/yii2-gii": "<2.0.4", - "yiisoft/yii2-jui": "<2.0.4", - "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", - "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", - "zendframework/zend-diactoros": ">=1,<1.0.4", - "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-http": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.3,<2.3.8|>=2.4,<2.4.1", - "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", - "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", - "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", - "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", - "zendframework/zend-validator": ">=2.3,<2.3.6", - "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", - "zendframework/zendframework": ">=2,<2.4.11|>=2.5,<2.5.1", - "zendframework/zendframework1": "<1.12.20", - "zendframework/zendopenid": ">=2,<2.0.2", - "zendframework/zendxml": ">=1,<1.0.1", - "zf-commons/zfc-user": "<1.2.2", - "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", - "zfr/zfr-oauth2-server-module": "<0.1.2" - }, - "type": "metapackage", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "role": "maintainer" - } - ], - "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2017-01-24T18:32:04+00:00" - }, { "name": "seld/jsonlint", "version": "1.5.0", @@ -8899,7 +8768,6 @@ "jms/translation-bundle": 20, "league/fractal": 20, "neutron/process-manager": 20, - "roave/security-advisories": 20, "willdurand/negotiation": 15 }, "prefer-stable": false, diff --git a/config/configuration.sample.yml b/config/configuration.sample.yml index 70980b98ef..6814807f06 100644 --- a/config/configuration.sample.yml +++ b/config/configuration.sample.yml @@ -167,35 +167,63 @@ authentication: providers: facebook: enabled: false + display: false + title: Facebook + type: facebook options: app-id: '' secret: '' default-graph-version: 'v2.10' twitter: enabled: false + display: false + title: Twitter + type: twitter options: consumer-key: '' consumer-secret: '' - google-plus: + github: enabled: false + display: false + title: Github + type: github options: client-id: '' client-secret: '' - github: + viadeo: enabled: false + display: false + title: Viadeo + type: viadeo options: client-id: '' client-secret: '' - viadeo: + linkedin: enabled: false + display: false + title: LinkedIN + type: linkedin options: client-id: '' client-secret: '' - linkedin: + ps_auth_1: enabled: false + display: false + title: 'PS Auth 1' + type: 'ps-auth' options: client-id: '' client-secret: '' + base-url: 'https://api-auth.phrasea.local' + provider-type: 'oauth' + provider-name: 'v2' + icon-uri: null + birth-group: '_firstlog' + everyone-group: '_everyone' + metamodel: '_metamodel' + model-gpfx: '_M_' + model-upfx: '_U_' + auto-logout: false registration-fields: - name: company diff --git a/docker-compose.under-phrasea.yml b/docker-compose.under-phrasea.yml index 363e22e87b..e48be6dd1f 100644 --- a/docker-compose.under-phrasea.yml +++ b/docker-compose.under-phrasea.yml @@ -18,7 +18,9 @@ services: phraseanet: extra_hosts: + - api-auth.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} - api-databox.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} + - api-expose.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} - api-uploader.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} - databox-indexer.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} - minio.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} @@ -26,7 +28,9 @@ services: worker: extra_hosts: + - api-auth.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} - api-databox.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} + - api-expose.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} - api-uploader.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} - databox-indexer.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} - minio.${PHRASEA_DOMAIN}:${PHRASEA_GATEWAY_IP} diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/AbstractProvider.php b/lib/Alchemy/Phrasea/Authentication/Provider/AbstractProvider.php index 53b6a15fa7..8f6ad67c04 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/AbstractProvider.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/AbstractProvider.php @@ -11,15 +11,125 @@ namespace Alchemy\Phrasea\Authentication\Provider; +use Alchemy\Phrasea\Authentication\ACLProvider; use Alchemy\Phrasea\Authentication\Provider\Token\Identity; -use Symfony\Component\Routing\Generator\UrlGenerator; +use Alchemy\Phrasea\Model\Manipulator\UserManipulator; +use Alchemy\Phrasea\Model\Repositories\UserRepository; +use appbox; +use RandomLib\Generator as RandomGenerator; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; abstract class AbstractProvider implements ProviderInterface { protected $generator; protected $session; + /** + * @var bool + */ + private $display = true; + + /** + * @var string + */ + private $title; + + /** + * @var UserManipulator + */ + private $userManipulator; + + /** + * @var UserRepository + */ + private $userRepository; + + /** + * @var ACLProvider + */ + private $ACLProvider; + + /** + * @var appbox + */ + private $appbox; + + /** + * @var RandomGenerator + */ + private $randomGenerator; + + /** + * @var array + */ + private $options = null; + + private $id; + + + protected function __construct(UrlGenerator $generator, SessionInterface $session) + { + $this->generator = $generator; + $this->session = $session; + } + + public function getId() + { + return $this->id ?: $this->getType(); + } + + public function setId($newId) + { + $this->id = $newId; + return $this; + } + + public function getType() + { + $u = explode('\\', static::class); + return array_pop($u); + } + + public function getDisplay(): bool + { + return $this->display; + } + + /** + * @param bool $display + */ + public function setDisplay(bool $display) + { + $this->display = $display; + } + + /** + * @deprecated replaced by getTitle() + * {@inheritdoc} + */ + public function getName() + { + return $this->getTitle(); + } + + /** + * more clear that getName because the key in conf is "title" + * @return string + */ + public function getTitle() + { + return $this->title ?: $this->getId(); + } + + /** + * @param string $title + */ + public function setTitle(string $title) + { + $this->title = $title; + } + /** * @param UrlGenerator $generator * @@ -68,6 +178,102 @@ public function getSession() return $this->session; } + /** + * @return UserManipulator + */ + public function getUserManipulator(): UserManipulator + { + return $this->userManipulator; + } + + /** + * @param UserManipulator $userManipulator + */ + public function setUserManipulator(UserManipulator $userManipulator) + { + $this->userManipulator = $userManipulator; + } + + /** + * @return UserRepository + */ + public function getUserRepository(): UserRepository + { + return $this->userRepository; + } + + /** + * @param UserRepository $userRepository + */ + public function setUserRepository(UserRepository $userRepository) + { + $this->userRepository = $userRepository; + } + + /** + * @return ACLProvider + */ + public function getACLProvider(): ACLProvider + { + return $this->ACLProvider; + } + + /** + * @param ACLProvider $ACLProvider + */ + public function setACLProvider(ACLProvider $ACLProvider) + { + $this->ACLProvider = $ACLProvider; + } + + /** + * @return appbox + */ + public function getAppbox(): appbox + { + return $this->appbox; + } + + /** + * @param appbox $appbox + */ + public function setAppbox(appbox $appbox) + { + $this->appbox = $appbox; + } + + /** + * @return RandomGenerator + */ + public function getRandomGenerator(): RandomGenerator + { + return $this->randomGenerator; + } + + /** + * @param RandomGenerator $randomGenerator + */ + public function setRandomGenerator(RandomGenerator $randomGenerator) + { + $this->randomGenerator = $randomGenerator; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = $options; + } + protected function createState() { return md5(uniqid(microtime(true), true)); diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php b/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php index ed938e5352..c316ea96b3 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Facebook.php @@ -11,46 +11,31 @@ namespace Alchemy\Phrasea\Authentication\Provider; -use Alchemy\Phrasea\Authentication\Provider\Token\Token; -use Alchemy\Phrasea\Authentication\Provider\Token\Identity; use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; -use Symfony\Component\HttpFoundation\Request; +use Alchemy\Phrasea\Authentication\Provider\Token\Identity; +use Alchemy\Phrasea\Authentication\Provider\Token\Token; +use Alchemy\Phrasea\Exception\InvalidArgumentException; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; class Facebook extends AbstractProvider { /** @var \Facebook\Facebook */ private $facebook; - public function __construct(\Facebook\Facebook $facebook, UrlGenerator $generator, SessionInterface $session) - { - $this->facebook = $facebook; - $this->generator = $generator; - $this->session = $session; - } - - /** - * {@inheritdoc} - */ - public function getId() - { - return 'facebook'; - } - /** - * {@inheritdoc} - */ - public function getName() + public function __construct(UrlGenerator $generator, SessionInterface $session, \Facebook\Facebook $facebook) { - return 'Facebook'; + parent::__construct($generator, $session); + $this->facebook = $facebook; } /** * {@inheritdoc} */ - public function authenticate(array $params = array()) + public function authenticate(array $params = array()): RedirectResponse { $params = array_merge(['providerId' => $this->getId()], $params); @@ -89,7 +74,7 @@ public function setFacebook(\Facebook\Facebook $facebook) /** * @return \Facebook\Facebook */ - public function getFacebook() + public function getFacebook(): \Facebook\Facebook { return $this->facebook; } @@ -97,6 +82,7 @@ public function getFacebook() /** * @param $dataToRetrieve * @return \Facebook\GraphNodes\GraphUser + * @throws \Facebook\Exceptions\FacebookSDKException */ protected function getGraphUser($dataToRetrieve) { @@ -107,14 +93,11 @@ protected function getGraphUser($dataToRetrieve) ); } catch(\Facebook\Exceptions\FacebookResponseException $e) { throw new NotAuthenticatedException('Graph returned an error: ' . $e->getMessage()); - exit; } catch(\Facebook\Exceptions\FacebookSDKException $e) { throw new NotAuthenticatedException('Facebook SDK returned an error: ' . $e->getMessage()); - exit; } - if (!$response) - { + if (!$response) { throw new NotAuthenticatedException('Not authenticated'); } @@ -124,7 +107,7 @@ protected function getGraphUser($dataToRetrieve) /** * {@inheritdoc} */ - public function getIdentity() + public function getIdentity(): Identity { $user = $this->getGraphUser(['id', 'name', 'email', 'picture', 'last_name', 'first_name']); @@ -151,10 +134,8 @@ public function onCallback(Request $request) $accessToken = $helper->getAccessToken(); } catch(\Facebook\Exceptions\FacebookResponseException $e) { throw new NotAuthenticatedException('Graph returned an error: ' . $e->getMessage()); - exit; } catch(\Facebook\Exceptions\FacebookSDKException $e) { throw new NotAuthenticatedException('Facebook SDK returned an error: ' . $e->getMessage()); - exit; } if (! isset($accessToken)) { @@ -169,7 +150,6 @@ public function onCallback(Request $request) } else { throw new NotAuthenticatedException('Facebook authentication failed'); } - exit; } $oAuth2Client = $this->facebook->getOAuth2Client(); @@ -179,7 +159,6 @@ public function onCallback(Request $request) $accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken); } catch (\Facebook\Exceptions\FacebookSDKException $e) { throw new NotAuthenticatedException('Error getting long-lived access token: ' . $e->getMessage() ); - exit; } } $this->session->set('fb_access_token', (string) $accessToken); @@ -187,8 +166,9 @@ public function onCallback(Request $request) /** * {@inheritdoc} + * @throws \Facebook\Exceptions\FacebookSDKException */ - public function getToken() + public function getToken(): Token { $user = $this->getGraphUser(['id']); @@ -198,7 +178,7 @@ public function getToken() /** * {@inheritdoc} */ - public function getIconURI() + public function getIconURI(): string { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAwCAYAAAC4w' . 'JK5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0W' @@ -248,16 +228,25 @@ public function getIconURI() /** * {@inheritdoc} * - * @return Facebook + * @return self + * @throws \Facebook\Exceptions\FacebookSDKException */ - public static function create(UrlGenerator $generator, SessionInterface $session, array $options) + public static function create(UrlGenerator $generator, SessionInterface $session, array $options): self { - $config['app_id'] = $options['app-id']; - $config['app_secret'] = $options['secret']; - $config['default_graph_version'] = $options['default-graph-version']; - - $facebook = new \Facebook\Facebook($config); + foreach (['app-id', 'secret', 'default-graph-version'] as $parm) { + if (!isset($options[$parm])) { + throw new InvalidArgumentException(sprintf('Missing Facebook "%s" parameter in conf/authentication/providers', $parm)); + } + } - return new static($facebook, $generator, $session); + return new static( + $generator, + $session, + new \Facebook\Facebook([ + 'app_id' => $options['app-id'], + 'app_secret' => $options['secret'], + 'default_graph_version' => $options['default-graph-version'] + ]) + ); } } diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Factory.php b/lib/Alchemy/Phrasea/Authentication/Provider/Factory.php index 8cff079ffa..ce1fa7ea4a 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Factory.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Factory.php @@ -11,33 +11,82 @@ namespace Alchemy\Phrasea\Authentication\Provider; +use Alchemy\Phrasea\Authentication\ACLProvider; use Alchemy\Phrasea\Exception\InvalidArgumentException; -use Symfony\Component\Routing\Generator\UrlGenerator; +use Alchemy\Phrasea\Model\Manipulator\UserManipulator; +use Alchemy\Phrasea\Model\Repositories\UserRepository; +use appbox; +use RandomLib\Generator as RandomGenerator; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; + class Factory { private $generator; private $session; + /** + * @var UserManipulator + */ + private $userManipulator; + /** + * @var UserRepository + */ + private $userRepository; + /** + * @var ACLProvider + */ + private $ACLProvider; + /** + * @var appbox + */ + private $appbox; + /** + * @var RandomGenerator + */ + private $randomGenerator; + - public function __construct(UrlGenerator $generator, SessionInterface $session) + public function __construct(UrlGenerator $generator, SessionInterface $session, + UserManipulator $userManipulator, UserRepository $userRepository, + ACLProvider $ACLProvider, appbox $appbox, RandomGenerator $randomGenerator + ) { $this->generator = $generator; $this->session = $session; + $this->userManipulator = $userManipulator; + $this->userRepository = $userRepository; + $this->ACLProvider = $ACLProvider; + $this->appbox = $appbox; + $this->randomGenerator = $randomGenerator; } - public function build($name, array $options = []) + public function build(string $id, string $type, bool $display, string $title, array $options = []) { - $name = implode('', array_map(function ($chunk) { + $type = implode('', array_map(function ($chunk) { return ucfirst(strtolower($chunk)); - }, explode('-', $name))); + }, explode('-', $type))); - $class_name = sprintf('%s\\%s', __NAMESPACE__, $name); + $class_name = sprintf('%s\\%s', __NAMESPACE__, $type); if (!class_exists($class_name)) { - throw new InvalidArgumentException(sprintf('Invalid provider %s', $name)); + throw new InvalidArgumentException(sprintf('Invalid provider %s', $type)); } - return $class_name::create($this->generator, $this->session, $options); + /** @var AbstractProvider $o */ + $o = $class_name::create($this->generator, $this->session, $options); // v1 bc compat : can't change + + $o->setId($id); + $o->setDisplay($display); + $o->setTitle($title); + $o->setOptions($options); + + $o->setUserManipulator($this->userManipulator); + $o->setUserRepository($this->userRepository); + $o->setACLProvider($this->ACLProvider); + $o->setAppbox($this->appbox); + $o->setRandomGenerator($this->randomGenerator); + + return $o; } } diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Github.php b/lib/Alchemy/Phrasea/Authentication/Provider/Github.php index 64a8907bb5..9760c7f196 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Github.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Github.php @@ -11,16 +11,17 @@ namespace Alchemy\Phrasea\Authentication\Provider; -use Alchemy\Phrasea\Authentication\Provider\Token\Token; -use Alchemy\Phrasea\Authentication\Provider\Token\Identity; use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; +use Alchemy\Phrasea\Authentication\Provider\Token\Identity; +use Alchemy\Phrasea\Authentication\Provider\Token\Token; +use Alchemy\Phrasea\Exception\InvalidArgumentException; +use Guzzle\Common\Exception\GuzzleException; use Guzzle\Http\Client as Guzzle; use Guzzle\Http\ClientInterface; -use Guzzle\Common\Exception\GuzzleException; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; class Github extends AbstractProvider { @@ -29,14 +30,14 @@ class Github extends AbstractProvider private $key; private $secret; - public function __construct(UrlGenerator $generator, SessionInterface $session, ClientInterface $client, $key, $secret) + + public function __construct(UrlGenerator $generator, SessionInterface $session, array $options, ClientInterface $client) { - $this->generator = $generator; - $this->session = $session; - $this->client = $client; + parent::__construct($generator, $session); - $this->key = $key; - $this->secret = $secret; + $this->client = $client; + $this->key = $options['client-id']; + $this->secret = $options['client-secret']; } /** @@ -44,7 +45,7 @@ public function __construct(UrlGenerator $generator, SessionInterface $session, * * @return Github */ - public function setGuzzleClient(ClientInterface $client) + public function setGuzzleClient(ClientInterface $client): self { $this->client = $client; @@ -62,23 +63,7 @@ public function getGuzzleClient() /** * {@inheritdoc} */ - public function getId() - { - return 'github'; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'Github'; - } - - /** - * {@inheritdoc} - */ - public function authenticate(array $params = array()) + public function authenticate(array $params = array()): RedirectResponse { $params = array_merge(['providerId' => $this->getId()], $params); @@ -177,7 +162,7 @@ public function onCallback(Request $request) /** * {@inheritdoc} */ - public function getToken() + public function getToken(): Token { if ('' === trim($this->session->get('github.provider.id'))) { throw new NotAuthenticatedException('Github has not authenticated'); @@ -189,7 +174,7 @@ public function getToken() /** * {@inheritdoc} */ - public function getIdentity() + public function getIdentity(): Identity { $identity = new Identity(); @@ -227,7 +212,7 @@ public function getIdentity() /** * {@inheritdoc} */ - public function getIconURI() + public function getIconURI(): string { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAwCAYAAAC4w' . 'JK5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MO' @@ -289,16 +274,19 @@ public function getIconURI() /** * {@inheritdoc} */ - public static function create(UrlGenerator $generator, SessionInterface $session, array $options) + public static function create(UrlGenerator $generator, SessionInterface $session, array $options): self { - if (!isset($options['client-id'])) { - throw new InvalidArgumentException('Missing GitHub client-id parameter'); - } - - if (!isset($options['client-secret'])) { - throw new InvalidArgumentException('Missing GitHub client-secret parameter'); + foreach (['client-id', 'client-secret'] as $parm) { + if (!isset($options[$parm])) { + throw new InvalidArgumentException(sprintf('Missing Github "%s" parameter in conf/authentication/providers', $parm)); + } } - return new Github($generator, $session, new Guzzle('https://github.com/login/oauth'), $options['client-id'], $options['client-secret']); + return new static( + $generator, + $session, + $options, + new Guzzle('https://github.com/login/oauth') + ); } } diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php b/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php deleted file mode 100644 index 074cbcfb04..0000000000 --- a/lib/Alchemy/Phrasea/Authentication/Provider/GooglePlus.php +++ /dev/null @@ -1,330 +0,0 @@ -generator = $generator; - $this->session = $session; - $this->client = $google; - $this->guzzle = $guzzle; - - $this->client->setScopes([ - 'https://www.googleapis.com/auth/plus.me', - 'https://www.googleapis.com/auth/userinfo.email', - 'https://www.googleapis.com/auth/userinfo.profile', - ]); - - $this->client->setApprovalPrompt("auto"); - - if ($this->session->has('google-plus.provider.token')) { - $this->client->setAccessToken($this->session->get('google-plus.provider.token')); - } - } - - /** - * @param ClientInterface $client - * - * @return Github - */ - public function setGuzzleClient(ClientInterface $client) - { - $this->guzzle = $client; - - return $this; - } - - /** - * @return ClientInterface - */ - public function getGuzzleClient() - { - return $this->guzzle; - } - - /** - * @param \Google_Client $client - * - * @return GooglePlus - */ - public function setGoogleClient(\Google_Client $client) - { - $this->client = $client; - - return $this; - } - - /** - * @return \Google_Client - */ - public function getGoogleClient() - { - return $this->client; - } - - /** - * {@inheritdoc} - */ - public function getId() - { - return 'google-plus'; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'Google +'; - } - - /** - * {@inheritdoc} - */ - public function authenticate(array $params = array()) - { - $params = array_merge(['providerId' => $this->getId()], $params); - - $this->client->setRedirectUri( - $this->generator->generate( - 'login_authentication_provider_callback', - $params, - UrlGenerator::ABSOLUTE_URL - ) - ); - - $state = $this->createState(); - - $this->session->set('google-plus.provider.state', $state); - $this->client->setState($state); - - return new RedirectResponse($this->client->createAuthUrl()); - } - - /** - * {@inheritdoc} - */ - public function logout() - { - try { - $this->client->revokeToken(); - } catch (\Google_Exception $e) { - throw new RuntimeException('Unable to logout from Google+', $e->getCode(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function onCallback(Request $request) - { - if (!$this->session->has('google-plus.provider.state')) { - throw new NotAuthenticatedException('No state value in session ; CSRF try ?'); - } - - if ($request->query->get('state') !== $this->session->remove('google-plus.provider.state')) { - throw new NotAuthenticatedException('Invalid state value ; CSRF try ?'); - } - - try { - $this->client->fetchAccessTokenWithAuthCode($request->query->get('code')); - - $token = @json_decode($this->client->getAccessToken(), true); - - if (JSON_ERROR_NONE !== json_last_error()) { - throw new NotAuthenticatedException('Unable to parse Google+ JSON', json_last_error()); - } - - $ticket = $this->client->verifyIdToken($token['id_token']); - } - catch (\Google_Exception $e) { - throw new NotAuthenticatedException('Unable to authenticate through Google+', $e->getCode(), $e); - } - - $this->session->set('google-plus.provider.token', $this->client->getAccessToken()); - $this->session->set('google-plus.provider.id', $ticket['sub']); - } - - /** - * {@inheritdoc} - */ - public function getToken() - { - if (!ctype_digit($this->session->get('google-plus.provider.id'))) { - throw new NotAuthenticatedException('Google + has not authenticated'); - } - - return new Token($this, $this->session->get('google-plus.provider.id')); - } - - /** - * {@inheritdoc} - */ - public function getIdentity() - { - $identity = new Identity(); - - $accessToken = $this->client->getAccessToken(); - $token = @json_decode($accessToken, true); - - if (JSON_ERROR_NONE !== json_last_error()) { - throw new NotAuthenticatedException('Unable to parse Google+ JSON'); - } - - try { - if(is_array($ticket = $this->client->verifyIdToken($token['id_token']))){ - $mapping = [ - 'email' => Identity::PROPERTY_EMAIL, - 'given_name' => Identity::PROPERTY_FIRSTNAME, - 'family_name' => Identity::PROPERTY_LASTNAME, - 'picture' => Identity::PROPERTY_IMAGEURL, - 'sub' => Identity::PROPERTY_ID - ]; - foreach ($mapping as $src => $dest) { - if (array_key_exists($src, $ticket)) { - $identity->set($dest, $ticket[$src]); - } - } - } - else { - throw new NotAuthenticatedException('Google + has not authenticated'); - } - } - catch (\Google_Exception $e) { - throw new NotAuthenticatedException('Google + has not authenticated'); - } - - if(!$identity->has(Identity::PROPERTY_ID)) { - throw new NotAuthenticatedException('Google + has not authenticated'); - } - - return $identity; - } - - /** - * {@inheritdoc} - */ - public function getIconURI() - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAwCAYAAAC4w' - . 'JK5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MO' - . 'mNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ' - . '2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6b' - . 'WV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgM' - . 'jAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJod' - . 'HRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZ' - . 'XNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZ' - . 'S5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL' - . '3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZ' - . 'G9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZ' - . 'DowMjgwMTE3NDA3MjA2ODExOEMxNEY3NEJCM0UzNjU4QyIgeG1wTU06RG9jdW1lbnRJR' - . 'D0ieG1wLmRpZDo2N0M5MkQ3OTcxRUUxMUUyQjc5NzlGRUJDNjcwRkVDMyIgeG1wTU06S' - . 'W5zdGFuY2VJRD0ieG1wLmlpZDo2N0M5MkQ3ODcxRUUxMUUyQjc5NzlGRUJDNjcwRkVDM' - . 'yIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpI' - . 'j4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDI4M' - . 'DExNzQwNzIwNjgxMThDMTRGNzRCQjNFMzY1OEMiIHN0UmVmOmRvY3VtZW50SUQ9Inhtc' - . 'C5kaWQ6MDI4MDExNzQwNzIwNjgxMThDMTRGNzRCQjNFMzY1OEMiLz4gPC9yZGY6RGVzY' - . '3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiP' - . 'z6uF8PaAAAL1UlEQVR42txae4wdVRn/zdzXPu/d1227W5aWPizQFrolXZAFS3hUpICAC' - . 'oLVNlU0NcVoiTEm/mEqGMVUVBqEolFEBUQbiokIAo1Q6COlrZTutrRLu9vd7pbtvu7uf' - . 'c7j+J2ZM/eemZ177xITX5N8e86dM3PO9/ve58wqjDH8r18q/g+uIP+jKIrVCOLAAlL/v' - . '+ligkwiQ7Q2CIfhvr6+9ubm5l+rqvqRk0MZzHvkPaA5RJAEvjxWC3nhzWMZbLq2AVtun' - . 'O27cm73biSuvNJ61OHCkLgxpX6pe9TqOvBKG/A5+pkh0oh0hfsEaYJLPKzr+ruBQGA+X' - . '/jp3YO4Z9txYG4Vve1hPN9XCvLpz+DAd5ag7fxqXyDDK1Ygt3+/1TeLkOHz2wua00eBh' - . 'dSMEyWJsqrkG2EHAL8GEprdsWamV3kAyIuImDeVgpg4IHruyX3DRe0gfPPNtu597MNpF' - . 'YkcJTPpGQcYXc1EMaIKbvoOCO4DEZcJGMyzimIDsfpMIjF7Uxg/PTSK4UndH0RHBxTPP' - . 'UViVJGW8j7j048T1fJpuQJUaTwkvxwJ0pAhMeoiRawoWv5MBclhMIsDvZP+EWTBAktSx' - . 'aTPPGNKEY8W96NCC5xnRTangPxSvCZUmN10TMrbd0ysAOaNbn8QTPInVWLSy7RXI8yjN' - . 'WGSEQHAmipYLGe0NtBzXBucUc6A4kifSStJrPDfoQD29qZ9QSiG4csYfJiWmPXVkldRw' - . 'WKOePF5FJXqyeRSNF2l4jFOaXlFmjekYChlWNaleOzBOHXKYkzxMQ+HaW+fTcPMSmbse' - . 'E0Yay6mAPBBpqAFeUbmMSnB0UjOgGZMddHczp0uiSvCfsPCQ+tEuIlKHhsoYV7T0gS/1' - . 'qyYgd++NGA7eF63ir9MFGY9F6EmGJg6l/6n7agRTDpXlmiU6GxdBNnKAGUtkikzEUgai' - . 'CayaJAY5EY6JhLDhwLx8aX1WHppPQ53Ul5pqRKGKlSiSGCcTE6md15tCKrXlh77Heq6O' - . 'q3s9GrHbHTOacTp+gqciVWgl+h4LIw0BUqdXlNJw1W6ifmJHFrGMoiPZzFrNI25p0dw0' - . 'e5+tJgfEgS/fnBLK1bvOQc0UPwPBwvJzXFsp88Nc1zDJ5fE3BOcHMSrP1qD7fctx74FM' - . '7B/NumjKmRrl5uiRlxlDSkHARM019nGSvKxeppXeEpaw8V3TGB53wTw8G6PEdhlRxV3A' - . '+qf8gOy6dmTePhX3cAiMoaQ6o5MHATPmafTqG6pxMADS1FbYdtTwsjiU4/eiVf0E/TuU' - . 'mCSqoCUBlVjNm8M7ujmm86Z7Xb8heqQLYA7nttAA51EPURDLk0kciai4am+/uO7LsBkU' - . 'scTf+gljVDojYftFMMBpEhDAxmoTRV4ZePCPICzmQRu3b4B+3KHgRmXINifcE/qmIV3P' - . '8PcAKzoQ43Knx+jtci09FLR6YatnTh5LuNrVtvWL8TW+y9C+zwq8IZyVPCRq51Joy6i4' - . 't5PtODoQ5fiirl28bd36Dgu+81nsK9nFwKxxQiks+SvrECG0zfd9wx53PQ8S0SAmY/KX' - . 'OaEL+w5VRnK4edrFmDtNc1F/eTt7gm8N5hGtCKIyxdG0RQtKPT1gSNY+dQdloQDDQvI9' - . 'nWfbMWKx01W3ryMr79W3JwwrxLp99JYt+Vd/OL1Qdy6rBHXUoRqm1NDVVYh4lw2v9Yi7' - . '9U91oeVz6yzTCVQTwWxrk0FUM58GMqaV+noxCNFjByH6qZd/xjBrt0UlRojmN9ajZnRM' - . 'KojASR6Urhr9Wx8Y9VUTf3k4LMkly4EWjtormxp6X4o7bDimW4KCB72dPFkIxWJTSR9i' - . 'iTdJyfRnaP7WQKZMbH5ywt9J+ubHCIvq6Q6SaN1zfIMytKd7jgrV3aYcmUqQPGtKSUwr' - . 'hHe1rU1YNXimC+I2+ZdTfE8AT0zKUoSmoQXfoYp+qa7bxpSXyImxpjhuSfasiDy9ZBPb' - . 'USA0mM5DDm7Ps+1dvFqbLjp+7Sv6IKRHBYllmmTKbVW35D60n1OhhgzzEIr+haQaWvCt' - . 'V8QoMgnsqeS+GvXeNHI9eh19+OhOx+ltHsO5nCPvcQU6Rtu6bs0ZfhozITCyRozyoEwB' - . 'cNe9TqaoX6Fggef7ytZqnyz/fN44YvPobKiAeaZLnpdEXMZPkybBbMrAk7J91mhGC2tC' - . 'anU5k4+odvleD/Vjz1EGR3HXujFmm0nUOrs8Jb5V+HI117G4jntYKffscwh72cOOUzJD' - . 'JqF+4ppk1crZQpAxk8IKLVnrSjE9821rTWYNyOG1sYwmusqMCteiUUNlQhWRnw3KPJ1Q' - . 'W0c79z3F1zzxD144+CfgeZF9I7iCZnFQ6vriLVEdHKDOEqSDmloa2vEygvrcfmFdVh1S' - . 'RwN1f/aOenr9/4eVzz+Wew99CIUAmJHnhKhtRjTjJUHse6uZtoIxXEdZWn3NQ7t9IvIj' - . 'XVBT/XDTA+CZSn6ZMl5NR3h+ApEr3ycduz1RcHs+cozaHnwagz0H4PS1GqbBjy73FJ5o' - . 'YQmSpbiyRNPItn9NLSh/dASw9bJlXVmELAPe1QhApYiBTbFUfexbQjPvq0okH2Dx3D5A' - . 'yupnK4jwBF/AL7acJuVueVdV+3ku8c2Uu9jYEcHzuxYh0TnS9AmhxGoiiAQrUKglqimC' - . 'kpVFViY2gj9bqyBNjaEoRduR/Lgt4qCaJ+1CDd13A2cOyOqAx42Tau1I5Xk9DrLPzPlX' - . 'rmDAm3sEE4+tQxjXW8hGA0hGCMlhSrJjFUiZhHTbYITNanmUmuqoVBiH975EMZ2FtfG2' - . 'ktWgz/IqLpVGCtkYeaTmS3fMadm7XIgep+/AZnhCYSbiHEWsBkXqYI5KcNJJwKUST8ML' - . 'rFgFYL1YSTe3oGRl6/3BdHGHTsap/IkXZCyLktehFR5zPCMlQKR6NqKyd5zCBMjBkmXM' - . '2eKlG84zFKri9YUcZ0Zjlb4xiVIZXgE4wdexcTb901ZcG6sGfXVFACyual1Uz4fMN+8k' - . 'R8rBWKi5wmQ8Eniql17SfWXxaCoyeBolhVM2QLsEE0bqFUwtncrjMQx14KhQBBV3Kn5l' - . 'wZTOpg2i7RyteDcKwVCT49YJ5x5E5KrBMEoj2b5AtMoVAt5wGKbiXAFcrStTh5/zLVgM' - . 'pfG2GTCDnGmT/b2trIGipQdQXfpZFhhVKcHVee00rTPBJh0/Ep6sk8r7PwLptpnyvlwq' - . 'YiTFi6YRK9rwSND7yM5SvuOQMjNEJvGZmk6myIlGIOWGYAaEeehrHC4p0qHf873FQVWj' - . 'rFOIphaOMFRrWeYrSXTvfKbPQeprCGNNzXbkvVjshSAcpui6pabkEvZNYtj305flwtay' - . 'Sf4KYTpmJgYswOKAoN8N1A907XgL3f90VpWtfg3i/uCWWKsFIjGZd9FOEYbnxG+vVQLF' - . 'bJpM+vYP4RPyM7NgciOb6ZylCBJMIu+lJ9/+9G/48ibf6N9fKOwc8/HOkPaUZp+YwJIK' - . 'RBqqBYLP70VGoXwNFWy3DMcodjRT4RZ4V+Oc+cjmXVGRC2pLfmBiWjbagrXl+Wr/PU/2' - . '2DVKgo5jGtH52oN/90gK5xRlUt2rO7Cr2Lx+i3Wl51Ef5b2y1req/NadkKpZHa8b5A/5' - . 'NJZJAc01C1ZhFnX78hPvOx7N2L8/RNQGhqFBLzfd6UtcdGx8powR0dHD/NOw5JNWL5pD' - . '+KXtlGkUjAxqCF1TkNuQoOW1SgRGiRtg8aIcjr5kYYsmWBqIEfaUBDvWIUL1h6l2e0jz' - . 'at+eDsO73oNSnwW1TWGzYju2RQVC7WGO7xS4h3xfC3OV7H8I17Dxo0b2zdv3vxIfX39e' - . 'Q6y1NABDB/cionT+6CND0JPUlme0vNfg0IUKcOxKMK0Aao9fzma2r+NcEOb9e6u3oO4e' - . '8t69HUeAWbOpNSgFiRZpEItta8gsxrBofFncCL5Ft05zk+JiEYcEGHx/YOHkjlEHEQjD' - . '1jeD5L/wYtLP8W/6/NP/0R823CWV0tBaXed45UHr8/FPf4ZtGo63zD+zSD4UcsHgld+z' - . 'GjIILJiAOLhIfGp9b/ln1QcQacEnxPit/lPAQYAZxcBzaHB/oIAAAAASUVORK5CYII='; - } - - /** - * {@inheritdoc} - */ - public static function create(UrlGenerator $generator, SessionInterface $session, array $options) - { - $client = new \Google_Client(); - - $client->setApplicationName('Phraseanet'); - $client->setClientId($options['client-id']); - $client->setClientSecret($options['client-secret']); - - return new GooglePlus($generator, $session, $client, new Guzzle()); - } -} - diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php b/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php index 0b3c5eb60c..4057797c74 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Linkedin.php @@ -11,16 +11,17 @@ namespace Alchemy\Phrasea\Authentication\Provider; -use Alchemy\Phrasea\Authentication\Provider\Token\Token; -use Alchemy\Phrasea\Authentication\Provider\Token\Identity; use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; +use Alchemy\Phrasea\Authentication\Provider\Token\Identity; +use Alchemy\Phrasea\Authentication\Provider\Token\Token; +use Alchemy\Phrasea\Exception\InvalidArgumentException; +use Guzzle\Common\Exception\GuzzleException; use Guzzle\Http\Client as Guzzle; use Guzzle\Http\ClientInterface; -use Guzzle\Common\Exception\GuzzleException; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; class Linkedin extends AbstractProvider { @@ -29,14 +30,14 @@ class Linkedin extends AbstractProvider private $key; private $secret; - public function __construct(UrlGenerator $generator, SessionInterface $session, ClientInterface $client, $key, $secret) + + public function __construct(UrlGenerator $generator, SessionInterface $session, array $options, ClientInterface $client) { - $this->generator = $generator; - $this->session = $session; - $this->client = $client; + parent::__construct($generator, $session); - $this->key = $key; - $this->secret = $secret; + $this->client = $client; + $this->key = $options['client-id']; + $this->secret = $options['client-secret']; } /** @@ -44,7 +45,7 @@ public function __construct(UrlGenerator $generator, SessionInterface $session, * * @return Linkedin */ - public function setGuzzleClient(ClientInterface $client) + public function setGuzzleClient(ClientInterface $client): self { $this->client = $client; @@ -54,7 +55,7 @@ public function setGuzzleClient(ClientInterface $client) /** * @return ClientInterface */ - public function getGuzzleClient() + public function getGuzzleClient(): ClientInterface { return $this->client; } @@ -62,23 +63,7 @@ public function getGuzzleClient() /** * {@inheritdoc} */ - public function getId() - { - return 'linkedin'; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'LinkedIN'; - } - - /** - * {@inheritdoc} - */ - public function authenticate(array $params = array()) + public function authenticate(array $params = array()): RedirectResponse { $params = array_merge(['providerId' => $this->getId()], $params); @@ -177,7 +162,7 @@ public function onCallback(Request $request) /** * {@inheritdoc} */ - public function getToken() + public function getToken(): Token { if ('' === trim($this->session->get('linkedin.provider.id'))) { throw new NotAuthenticatedException('Linkedin has not authenticated'); @@ -189,7 +174,7 @@ public function getToken() /** * {@inheritdoc} */ - public function getIdentity() + public function getIdentity(): Identity { $identity = new Identity(); @@ -231,7 +216,7 @@ public function getIdentity() /** * {@inheritdoc} */ - public function getIconURI() + public function getIconURI(): string { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAwCAYAAAC4w' . 'JK5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MO' @@ -278,16 +263,14 @@ public function getIconURI() /** * {@inheritdoc} */ - public static function create(UrlGenerator $generator, SessionInterface $session, array $options) + public static function create(UrlGenerator $generator, SessionInterface $session, array $options): self { - if (!isset($options['client-id'])) { - throw new InvalidArgumentException('Missing LinkedIn client-id parameter'); - } - - if (!isset($options['client-secret'])) { - throw new InvalidArgumentException('Missing LinkedIn client-secret parameter'); + foreach (['client-id', 'client-secret'] as $parm) { + if (!isset($options[$parm])) { + throw new InvalidArgumentException(sprintf('Missing Linkedin "%s" parameter in conf/authentication/providers', $parm)); + } } - return new Linkedin($generator, $session, new Guzzle(), $options['client-id'], $options['client-secret']); + return new static($generator, $session, $options, new Guzzle()); } } diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php b/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php index 9178fe0aa6..e86b71e1ef 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/ProviderInterface.php @@ -11,23 +11,21 @@ namespace Alchemy\Phrasea\Authentication\Provider; +use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; use Alchemy\Phrasea\Authentication\Provider\Token\Identity; use Alchemy\Phrasea\Authentication\Provider\Token\Token; -use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; use Alchemy\Phrasea\Exception\RuntimeException; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; interface ProviderInterface { /** - * Returns a unique identifier for the provider. + * Returns the unique identifier for the provider (first-level key in conf) * * Allowed characters are a-z and - (minus). - * Examples : twitter => Twitter - * google-plus => GooglePlus * * @return string */ @@ -103,9 +101,9 @@ public function getTemplates(Identity $identity); /** * Creates a provider * - * @param UrlGenerator $generator + * @param UrlGenerator $generator * @param SessionInterface $session - * @param array $options + * @param array $options */ public static function create(UrlGenerator $generator, SessionInterface $session, array $options); } diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/PsAuth.php b/lib/Alchemy/Phrasea/Authentication/Provider/PsAuth.php new file mode 100644 index 0000000000..bcdf38b03f --- /dev/null +++ b/lib/Alchemy/Phrasea/Authentication/Provider/PsAuth.php @@ -0,0 +1,732 @@ +config) && $this->config['debug'] === true) { + $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + if ($bt[0]['file'] != $lastfile) { + file_put_contents('/var/alchemy/Phraseanet/logs/psauth.log', sprintf("FILE %s \n", ($lastfile = $bt[0]['file'])), FILE_APPEND); + } + $s = sprintf("LINE (%d) : %s\n", $bt[0]['line'], $s); + file_put_contents('/var/alchemy/Phraseanet/logs/psauth.log', $s, FILE_APPEND); + } + } + + public function __construct(UrlGenerator $urlGenerator, SessionInterface $session, array $config, Guzzle $client) + { + parent::__construct($urlGenerator, $session); + + $this->config = $config; + if(!array_key_exists('model-gpfx', $this->config)) { + $this->config['model-gpfx'] = '_G_'; + } + if(!array_key_exists('model-upfx', $this->config)) { + $this->config['model-upfx'] = '_U_'; + } + if(!array_key_exists('metamodel', $this->config)) { + $this->config['metamodel'] = '_metamodel'; + } + if(!array_key_exists('auto-logout', $this->config)) { + $this->config['auto-logout'] = false; + } + + $this->client = $client; + $this->iconUri = array_key_exists('icon-uri', $config) ? $config['icon-uri'] : null; // if not set, will fallback on default icon + } + + /** + * {@inheritdoc} + */ + public static function create(UrlGenerator $generator, SessionInterface $session, array $options): AbstractProvider + { + foreach (['client-id', 'client-secret', 'base-url', 'provider-type', 'provider-name'] as $parm) { + if (!isset($options[$parm]) || (trim($options[$parm]) == '')) { + throw new InvalidArgumentException(sprintf('Missing Phraseanet "%s" parameter in conf/authentification/providers', $parm)); + } + } + + $guzzle = new Guzzle($options['base-url']); + $guzzle->setSslVerification(false, false, 0); + + return new self($generator, $session, $options, $guzzle); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'PS Auth'; + } + + /** + * @param ClientInterface $client + * + * @return self + */ + public function setGuzzleClient(ClientInterface $client): self + { + $this->client = $client; + + return $this; + } + + /** + * @return ClientInterface + */ + public function getGuzzleClient() + { + return $this->client; + } + + /** + * {@inheritdoc} + */ + public function authenticate(array $params = array()): RedirectResponse + { + $this->debug(); + $this->session->invalidate(0); + + /* + * for oauth2 the callback url(s) MUST be fully static. One CAN register multiple possible urls, like + * - one for phraseanet home : already static + * - one for phraseanet oauth api + * - ... ? + * api client may want to include static/variable params to be used for final redirect (eg. parade), + * we pass those in session + * lib/Alchemy/Phrasea/Controller/Api/OAuth2Controller::authorizeCallbackAction(...) will restore params + */ + $this->session->set($this->getId() . ".parms", array_merge(['providerId' => $this->getId()], $params)); + $this->debug(sprintf("authenticate params saved : session[%s] = %s", + $this->getId() . ".parms", + var_export($params, true) + )); + + $params = ['providerId' => $this->getId()]; // the only required parm (constant) + $this->debug(sprintf("redirect_uri params (cleaned) = %s", var_export($params, true))); + + $redirect_uri = $this->generator->generate( + 'login_authentication_provider_callback', + $params, + $this->getUrlGenerator()::ABSOLUTE_URL + ); + $this->debug(sprintf("redirect_uri = %s", $redirect_uri)); + + $state = $this->createState(); + + $this->session->set($this->getId() . '.provider.state', $state); + + $url = sprintf("%s/%s/%s/auth?%s", + $this->config['base-url'], + urlencode($this->config['provider-type']), + urlencode($this->config['provider-name']), + http_build_query([ + 'client_id' => $this->config['client-id'], + 'state' => $state, + 'redirect_uri' => $redirect_uri, + 'response_type' => "code" + ], '', '&') + ); + + $this->debug(sprintf("go to url = %s", $url)); + + return new RedirectResponse($url); + } + + /** + * {@inheritdoc} + */ + public function logout() + { + $this->debug("logout ?"); + if($this->config['auto-logout']) { + + // too bad: getting the logout page is not enough... +// $url = "security/logout"; +// $guzzleRequest = $this->client->get($url); +// $response = $guzzleRequest->send(); +// $this->debug($response->getBody()); +// return null; + + // ... we really need to redirect to it, which will prevent phr to redirect to his home + $url = sprintf("%s/security/logout", $this->config['base-url']); + + return new RedirectResponse($url); + } + + return null; + } + + public function logoutAndRedirect($redirect_uri) + { + $this->debug("logoutAndRedirect ?"); + if($this->config['auto-logout']) { + $url = sprintf("%s/security/logout?r=%s", $this->config['base-url'], urlencode($redirect_uri)); + + return new RedirectResponse($url); + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function onCallback(Request $request) + { + $this->debug(); + if (!$this->session->has($this->getId() . '.provider.state')) { + throw new NotAuthenticatedException('No state value in session ; CSRF try ?'); + } + $this->debug(); + if ($request->query->get('state') !== $this->session->remove($this->getId() . '.provider.state')) { + throw new NotAuthenticatedException('Invalid state value ; CSRF try ?'); + } + $this->debug(); + try { + $url = sprintf("%s/%s/token", + urlencode($this->config['provider-type']), + urlencode($this->config['provider-name']) + ); + $guzzleRequest = $this->client->post($url); + + $guzzleRequest->addPostFields([ + 'grant_type' => "authorization_code", + 'code' => $request->query->get('code'), + 'redirect_uri' => $this->generator->generate( + 'login_authentication_provider_callback', + ['providerId' => $this->getId()], + $this->getUrlGenerator()::ABSOLUTE_URL + ), + 'client_id' => $this->config['client-id'], + 'client_secret' => $this->config['client-secret'], + ]); + $guzzleRequest->setHeader('Accept', 'application/json'); + $this->debug(); + $response = $guzzleRequest->send(); + $this->debug(); + } + catch (GuzzleException $e) { + $this->debug(); + throw new NotAuthenticatedException('Guzzle error while authentication', $e->getCode(), $e); + } + + if (200 !== $response->getStatusCode()) { + $this->debug(); + throw new NotAuthenticatedException('Error while getting access_token'); + } + + $this->debug(); + $data = @json_decode($response->getBody(true), true); + $this->debug(); + + if (JSON_ERROR_NONE !== json_last_error()) { + $this->debug(); + throw new NotAuthenticatedException('Error while decoding token response, unable to parse JSON.'); + } + + $this->debug(var_export($data, true)); + $this->session->remove($this->getId() . '.provider.state'); + $this->session->set($this->getId() . '.provider.access_token', $data['access_token']); + + try { + $this->debug(); + // $request = $this->client->get($this->getId() . 'userinfo'); + $request = $this->client->get('me'); + $request->getQuery()->add('access_token', $data['access_token']); + $request->setHeader('Accept', 'application/json'); + $this->debug(); + + $response = $request->send(); + $this->debug(); + } + catch (GuzzleException $e) { + $this->debug(); + throw new NotAuthenticatedException('Guzzle error while authentication', $e->getCode(), $e); + } + + $this->debug(); + $data = @json_decode($response->getBody(true), true); + $this->debug(var_export($data, true)); + + if (200 !== $response->getStatusCode()) { + $this->debug(); + throw new NotAuthenticatedException('Error while retrieving user info, invalid status code.'); + } + + if (JSON_ERROR_NONE !== json_last_error()) { + $this->debug(); + throw new NotAuthenticatedException('Error while retrieving user info, unable to parse JSON.'); + } + + $this->debug(); + + $this->CreateUser([ + 'id' => $distantUserId = $data['user_id'], + 'login' => $data['username'], + 'firstname' => null, + 'lastname' => null, + 'email' => $data['email'], + '_groups' => $data['groups'] + ]); + + $this->session->set($this->getId() . ".provider.id", $distantUserId); + $this->session->set($this->getId() . ".provider.username", $data['username']); + } + + /** + * {@inheritdoc} + */ + public function getToken(): Token + { + $this->debug(); + if ('' === trim($this->session->get($this->getId() . '.provider.id'))) { + $this->debug(); + throw new NotAuthenticatedException($this->getId() . ' has not authenticated'); + } + + $this->debug(); + return new Token($this, $this->session->get($this->getId() . '.provider.id')); + } + + /** + * {@inheritdoc} + */ + public function getIdentity(): Identity + { + $this->debug(); + $identity = new Identity(); + + try { + $request = $this->client->get('me'); + $request->getQuery()->add('access_token', $this->session->get($this->getId() . '.provider.access_token')); + $request->setHeader('Accept', 'application/json'); + + $response = $request->send(); + } + catch (GuzzleException $e) { + $this->debug(); + throw new NotAuthenticatedException('Error while retrieving user info', $e->getCode(), $e); + } + + if (200 !== $response->getStatusCode()) { + $this->debug(); + throw new NotAuthenticatedException('Error while retrieving user info'); + } + + $data = @json_decode($response->getBody(true), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + $this->debug(); + throw new NotAuthenticatedException('Error while parsing json'); + } + + $this->debug(); + $identity->set(Identity::PROPERTY_EMAIL, $data['email']); + $identity->set(Identity::PROPERTY_ID, $data['user_id']); + $identity->set(Identity::PROPERTY_USERNAME, $data['username']); + + $this->debug(); + return $identity; + } + + /** + * @param array $data + * @return User|null + * @throws Exception + */ + private function CreateUser(Array $data) + { + $userManipulator = $this->getUserManipulator(); + $userRepository = $this->getUserRepository(); + $ACLProvider = $this->getACLProvider(); + + $ret = null; + + $login = trim($data['login']); + + $this->debug(sprintf("login=%s \n", var_export($login, true))); + + if ($login == "") { + $this->debug("login is empty, user not created \n"); + } + + /** @var User $userUA */ + $userUA = $userRepository->findByLogin($login); + + if (!$userUA) { + // need to create the user + $this->debug(sprintf("creating user \"%s\" \n", $login)); + $tmp_email = str_replace(['.', '@'], ['_', '_'], $login) . "@nomail.eu"; + $userUA = $userManipulator->createUser($login, 'user_tmp_pwd', $tmp_email, false); + + if ($userUA) { + $this->debug(sprintf("found user \"%s\" with id=%s \n", $login, $userUA->getId())); + + // if the id provider does NOT return groups, the new user will get "birth" privileges + if (!is_array($data['_groups']) && array_key_exists('birth-group', $this->config)) { + $data['_groups'] = [$this->config['birth-group']]; + } + } + else { + $this->debug(sprintf("failed to create user \"%s\" \n", $login)); + } + } + else { + // the user already exists + $this->debug(sprintf("found user \"%s\" with id=%s \n", $login, $userUA->getId())); + + // if the id provider does return groups, then revoke privileges + if (is_array($data['_groups'])) { + $appbox = $this->getAppbox(); + $all_base_ids = []; + foreach ($appbox->get_databoxes() as $databox) { + foreach ($databox->get_collections() as $collection) { + $all_base_ids[] = $collection->get_base_id(); + } + } + + $userACL = $ACLProvider->get($userUA); + $userACL->revoke_access_from_bases($all_base_ids)->revoke_unused_sbas_rights(); + $this->debug(sprintf("revoked from=%s \n", var_export($all_base_ids, true))); + } + } + + // here we should have a user + + if ($userUA) { + $this->debug(sprintf("User id=%s \n", $userUA->getId())); + + // apply groups + if (is_array($data['_groups'])) { + + $userACL = $ACLProvider->get($userUA); + + $models = []; + + // change groups to models + foreach ($data['_groups'] as $grp) { + $models[] = ['name' => $this->config['model-gpfx'] . $grp, 'autocreate' => true]; + } + + // add "everyone-group" + if(array_key_exists('everyone-group', $this->config)) { + $models[] = ['name' => $this->config['everyone-group'], 'autocreate' => true]; + } + + // add a specific model for the user + $models[] = ['name' => $this->config['model-upfx'] . $login, 'autocreate' => false]; + + $this->debug(sprintf("models=%s \n", var_export($models, true))); + + // if we need those (in case of creation of a model), they will be set only once + $metaModelUA = $metaModelBASES = $metaModelOwnerUA = null; + + foreach ($models as $model) { + + $this->debug(sprintf("searching model '%s' \n", $model['name'])); + + // we check if the model exits + $modelUA = $userRepository->findByLogin($model['name']); + + if (!$modelUA) { + if ($model['autocreate'] == true) { + $this->debug(sprintf("model '%s' not found \n", $model['name'])); + + // the model does not exist, so create it + // + // if not already known, get the metamodel + if ($metaModelUA === null) { + + $this->debug(sprintf("searching metamodel '%s'... \n", $this->config['metamodel'])); + + $metaModelUA = $userRepository->findByLogin($this->config['metamodel']); + + if ($metaModelUA) { + + $this->debug(sprintf("metaModelID=%s \n", print_r($metaModelUA->getId(), true))); + + // metamodel found, get some infos... + // ... get acl + $metaModelACL = $ACLProvider->get($metaModelUA); + // ... then list of bases + $metaModelBASES = $metaModelACL->get_granted_base(); + // ... in fact we simply need an array of base_ids, and base_id is the keys of the array, so switch + $metaModelBASES = array_keys($metaModelBASES); + + if ($metaModelUA->isTemplate()) { + $metaModelOwnerUA = $metaModelUA->getTemplateOwner(); + + $this->debug(sprintf("metamodel is a model, owner_id=%s \n", print_r($metaModelOwnerUA->getId(), true))); + } + + $this->debug(sprintf("metamodel granted on bases '%s' \n", print_r($metaModelBASES, true))); + } + else { + $this->debug("metamodel not found \n"); + + $metaModelUA = false; // don't search again + } + } + + // now we can create the model only if we found the metamodel + if ($metaModelUA) { + + $this->debug(sprintf("creating model '%s'... \n", $model['name'])); + + // create the model user... + $modelUA = $userManipulator->createUser($model['name'], 'model_pwd', null, false); + + $this->debug(sprintf("model '%s' created with modelID=%s... \n", $model['name'], print_r($modelUA->getId(), true))); + + if ($metaModelOwnerUA) { + $modelUA->setTemplateOwner($metaModelOwnerUA); + + $this->debug(sprintf("model '%s' set as model, owner_id=%s... \n", $model['name'], print_r($metaModelOwnerUA->getId(), true))); + } + + // ... then copy acl of every sbas + $modelACL = $ACLProvider->get($modelUA); + $modelACL->apply_model($metaModelUA, $metaModelBASES); + + $this->debug(sprintf(" ... and granted on bases %s \n", print_r($metaModelBASES, true))); + } + } + } + else { + // the model already exists + $this->debug(sprintf("model '%s' already exists, id=%s \n", $model['name'], print_r($modelUA->getId(), true))); + } + + // here we should have the model, except "user" models which are not automatically created + + if ($modelUA) { + $this->debug(sprintf(" ... modelID=%s \n", print_r($modelUA->getId(), true))); + + // here we have the model so get some infos about it + $modelACL = $ACLProvider->get($modelUA); + $modelBASES = $modelACL->get_granted_base(); + // ... in fact we simply need an array of base_ids, and base_id is the keys of the array, so switch + $modelBASES = array_keys($modelBASES); + + $this->debug(sprintf("model granted on bases '%s' \n", print_r($modelBASES, true))); + + // ... then copy acl of every sbas + $userACL->apply_model($modelUA, $modelBASES); + + $this->debug(sprintf("user '%s' granted on bases %s \n", $login, print_r($modelBASES, true))); + } + else { + $this->debug(sprintf("no model '%s' \n", $model['name'])); + } + } + + $userACL->inject_rights(); + } + + // now update infos of the user + if (!is_null($data['firstname']) && ($v = trim($data['firstname'])) != '') { + $userUA->setFirstName($v); + } + if (!is_null($data['firstname']) && ($v = trim($data['lastname'])) != '') { + $userUA->setLastName($v); + } + + $mail = ""; // mail is a special case + try { + if (($v = trim($data['email'])) != '') { + $mail = $v; + } + } + catch (Exception $e) { + // no-op + } + + if ($mail != $userUA->getEmail()) { + try { + $this->debug("unsetting former email of user"); + $userManipulator->setEmail($userUA, null); + if ($mail != "") { + $this->debug(sprintf("setting email '%s' to user", $mail)); + $dupUserUA = $userRepository->findByEmail($mail); + if ($dupUserUA == null) { + // ok we can set the mail + $userManipulator->setEmail($userUA, $mail); + $this->debug(sprintf("email '%s' set to user", $mail)); + } + else { + $this->debug(sprintf("warning : another user (id=%s) already has email '%s', email not set", $dupUserUA->getId(), $mail)); + } + } + } + catch (Exception $e) { + // no-op + $this->debug(var_export($e->getMessage(), true)); + } + } + else { + $this->debug(sprintf("email '%s' does not change\n", $mail)); + } + + // yes we are logged ! + /** @var RandomGenerator $randomGenerator */ + $randomGenerator = $this->getRandomGenerator(); + $password = $randomGenerator->generateString(16); + $userUA->setPassword($password); + + $this->debug(sprintf("returning user id=%s", $userUA->getId())); + + $ret = $userUA; // ->getId(); + } + + return $ret; + } + + + + /** + * {@inheritdoc} + */ + public function getIconURI() + { + return $this->iconUri ?: 'data:image/png;base64,' + . 'iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAA' + . 'AJZlWElmTU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAAB' + . 'AAAAUgExAAIAAAARAAAAWodpAAQAAAABAAAAbAAAAAAAAABIAAAAAQAAAEgAAAAB' + . 'QWRvYmUgSW1hZ2VSZWFkeQAAAAOgAQADAAAAAQABAACgAgAEAAAAAQAAADCgAwAE' + . 'AAAAAQAAADAAAAAAXukGzAAAAAlwSFlzAAALEwAACxMBAJqcGAAAActpVFh0WE1M' + . 'OmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6' + . 'bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1s' + . 'bnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgt' + . 'bnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAg' + . 'ICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgog' + . 'ICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYv' + . 'MS4wLyI+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+QWRvYmUgSW1hZ2VSZWFk' + . 'eTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4x' + . 'PC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAg' + . 'PC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KKS7NPQAADE5JREFUaAXtWQuMVcUZ/uY8' + . '7vvui+WNAYHa2oqKiw/SWnaJ0ipqH4ZHmjTVGMFao00LVm2Dl9YXtVETpVYSX6m2' + . 'tYhajU210V0KPqi7CALKrggUWeS9uOzufZzH9PvPvXdZ1t27rJCUpM5y7pyZM2fm' + . '+/7/m3/ODMAX6QsLHJcF1HG9XeJlraEaFsOUJrW3w1MKukTzk+tR4zzYvRHpeli9' + . '605E+YR2Wp+CVbC2o+9DtF1bd6EyodsTiV+qup1p8QpBG/SGdyLAn7A+dAqGgC92' + . '2H4vrubVqpcaWi+v0u1/qWjteC7+o+Jz8QbJGMXy8eTHNQfEok3zYU1ZBkdAtN2L' + . 'WkNhSVkE52VyQE4rB9XlCCe1HU76SDvu24hgQWx6+g1prxsptRq4xzM/uq0mHQ4m' + . 'ic45sAB3Dt2P8crF3VEbs0kA7Wm4nLIG/2yxs+Nq3+30/Xg5LoDlrc6tMv/o2t5t' + . 'agp2ypjiEVXHdz5HGrQH6mup8wZGFY67ewHisSrrNm25N5fFYBG4L3h4BdEHZIjh' + . 'ZTCiZBPyoGzPM8O+ClV7hpfx00rpO40OLBHwBUmpwc6PY9ZhoHOCr2ugywmy7ebk' + . '1eForCUZMm5TviHgxYLSXx58MWiKiVS+oAw+U9pwO+CYpo4aFbjDT2KzXosrCdwX' + . '8CIrGYtvHVOS7ksmDq2a5tVQ5015nV87YpoXcu+tLPfOFcyO4Toq7FnKJi9THFBI' + . 'gln0NFI84AceMEKFPOzBsHyJSa4RosxCgHcYr5kWFqjJWCc9HOv8KElAp1KGSqUC' + . 'VG1zx41zTf+eaNybY1IOncpxKQlDhVyDOZiLRGj/AokCATUq2S2hQEYkYZKAsrrJ' + . 'SkjVRpxRLAP4Hh6m+RdxfuwPiGjNsKu6G0tdz9Svq4rg3wSiu6446zdZL95cZlhz' + . 'utKm39FleBSBxcuAY0E7ZvdFIRzpX26DS/woF/8FubDrTiI5y++E63vUVxl+rFW8' + . 'Jdd4xo3SQsBrneoXZ4/RujuELrD+aO3aKZWrX3mx7MVbR+5PUxFjznJgdNjKygK0' + . 'JGVDy8vkpPwLeXfZIEhKSI1OHOWBQEbiAXneOwWVY1wDO2wk+NBYuGlH1Q2zxw4Z' + . '+34RU+9X+mS2uK4uqP/9jQtOX521R7Ze9XgmcuF8rbeut732PQwzFYyNR6wuHvhM' + . 'ueAJCURi9fzF4QsTujcQqJHwfV8ZLsFH6vw269HM3/wZX1u88tUp0nZxQ0OfWPus' + . 'REND0H+6eUv65YUL8MhjK+y1Y85U6rqlCI/+JvytG+HniEyXUTr8SiuQERI9iUCz' + . 'jVha/BxcQiRPKBgg+BkCX1cD7iew7CgyQ36Hf2RvMua8q+3vNjbhvcO7u6RZw8qV' + . 'R17pcdc3gdraoIkqLzOGTr4A5oZm/eoNP8ELqzag9fzvIzT3bsbDELzWZmivnCEk' + . 'XiCSnw9wC/OCYsh3JCR6EAkqE/TkGE7aA7CM/TAqb8Fa+ync1DwCl6zbhFzmE315' + . 'PI6kbfeNMd/zQPHWh9vRBb8qqSomfx2fPvES/nnNXXhrZxpdM3+FyPSFUAdb4B3c' + . 'zvBRDe2GSMQIPFL0Rn7iitXF9Jw3BqWnBHgHTL0TVvlV2Jb4E+7cWYOaxq1o3P8R' + . 'ZnJJt1RYpX0GnwE++wb8lJBgTXGSSBrhSWMRN018suRhtJ9xNk676jKMnf0oQh+8' + . 'DmfD01BVxJicSAIHKC9a3OYk5ut56wv4EZRLK2yPXxCJ6Thgz8WLe4bgF9tbEXYP' + . '4tJwhN8mUXQGSwRDk7w7QBqQgLwvWCSiaMeF52pEzpkEK5PBfxb8Fu3f+RZOmVmH' + . 'IRPPBdY9A6f1LRjDRwgTzgcSURJxyikXmtIl+MRQZBIL8fqB8bi7ZR9Wd6zDxYkw' + . '14Y40lrCfWAyGfaYUkl9BStMj24CIiz7/NTUlEL4vDPhrWvBtnkPYPPbu9BxzrUI' + . 'X7QYhrsb/r4dbDiMchkOn+WItQ/msFvwjv8g5q2pxMxVa5HrbMXFsRhysoYF4HsM' + . 'doy3x+YBdiZOkNQz97uylE0C9sgKpP/wGpoffxtDbrkEo+qeQGRXI5yWh2DTqKFh' + . '1+BDcxqe3ODhzub3MDySRV1ZElnKsYvytAyDKpM5kh9jML+lCdAF2hLI0rM+CnxQ' + . 'psA1AeiuHIwpwxmZNA4sfhIHp07C6DnTMGr6Q2gbpfD8riSuf+dDTsj9mFpVDicU' + . 'x6eeRogh1mT3RaMMBnixbUkJFRvJAMVBjs5ZEl3JTHU8rg0Edf4ERLv2YuNPF+Hl' + . 'lgTu2jgS1z/9Ciabh3B2sgxtXOAckj7K2MVOiwMOIi/tgV4d5X1wtCdk7Py0I3iq' + . 'OZMJ4f3E6Wg44zyM35eFjnTwa2oiDnQoSqULUUtmFu1W4F7sTfo5ilSvsfsrDkgg' + . 'b5ziMEc8EQAQCXFYWzM6cfStoTFYExqJXaEo/M4OxLkGHabOpW0mV4bOQ2Wo1mmU' + . 'l3XRaR6r5Tvu88A+QmdAAtI0T6KQsyDzTURgMjRavNtjVaMpPAbvW0lEKakK7eEQ' + . '32I4p7EJMhSGHbbY3kRbV4IS8lBV0U6CJCL9ayHy+dKABIrgi93LtknsblMuh1U5' + . '1tLq79pVjPUWyn1uhbnt4naFLfKLmBIP2CFuYLhz4TPLkpU4gkOHy+H5WZSV7UfY' + . '4qcue8xv5nqPWBy577wkAVGr2F3A5JcYDkO5uASw0RyB9+yh6DAiqCJwiUCOMrne' + . '5YVR8BcUtw0IhWBaNmyGS4tlk5cQcbwY2g9XgSdHiMV3wzA4Z7QQCfwSjD7QT0kC' + . '8nJgD5rcDERj4GN+L2w2hmK3iqOS0YTRnNtKERLtJzGNN6TBm7y2xTPiAYtX3gN5' + . 'AiY9Y/ESMo4zGumuEYhEdpPoTr4rpgt398GbflNJAlxeGPR4oqB91YY4tmEItusk' + . 'EqxNqiyxUkjEabIsH2vSWj45fFlVC8t24IGAgM3N2hHQliVE8gQsMjfMMNeUr9AA' + . 'p1COW9nHTpbZTYntpLAqSYCvR3IMjs1uzNmCpEGLqXKdpX19ZBUlUbAyTAHP3sTw' + . 'dJnPWc7NifRPYDKJ6QHOAd0tISFCAkeREDIMtWY164dzA7bXUZGPTTe7MxJ0NC34' + . '/cyPDPnZ1NAQjH5I2f+u74x+sM6NRGzfUSE/62R9VzvUvOdxLvgOL94Hl1fIpezz' + . 'YpikCXt6wBYZ2SQe5LyXeUFiIcmDujClplzbtlTFsHMiMVywNxadtFEA1tbenrdI' + . 'L7R9EkjRiLyMZ7aub9m4682vluvMz3Ke86lLzbiegHbcAHxAgkSCOgEuJLgiS+5K' + . 'nO9BIABbBNqTSKHOCnkk4cUrhlkk5bpdB5fErAnj//69metSPB1J9SOlfiVUJCH5' + . 'ptY37p844sKnckjfYZuheaZvWYTNnbySI4+8EeRzopA8TnlXCNADBqUik1gsbHJN' + . 'OKJ7kRClxAMiw7K8SDxpyzOd63rWsIxbl3/7lC3SXYoHDP2Bl+d9ekAeSEoRPDPF' + . 'vZK9ZfeqfZtb/zXfcbwpWS9Tz7MOHk15BiXk0BtaJNVTSq7j5CVEnUcI3gqkc8QD' + . 'VsjWlmU7oWjMSFQOpXCcd7WXu+jZS8fOEvDzHmm02YEqBV4w9usBeVhIugnBqZxR' + . 'U1NjNjXVN7F++lnjZszxffOeiG2NE9nwkE1O7oL/2DB9A27GDaKReKBCPCCTOIg6' + . 'gRdcy7StaEW17XV17PHSnYtWXH7qMhmvtr7eGrpvn142e4qD+VJTOpX0QK9X/aam' + . 'JmcWZjGspIz12199Jhfb8eWcm77d9XI8KOL88Hk25bvctPHIMSsEuCaTQDyQEK1P' + . 'qeR1PlR07ut0572VVYkvrbhsLMFrJeAb6urc5bNn59fQXgD6Kg6GQPD+cixn5ym/' + . 'trbW2rRpU65p68u/9nx9WtZN/5maN/j9Y9IjdIdLleXnQNQO+ZSQE0lWmNFEuamz' + . 'mecYhU9fcfm4mx/7xtDDgVy4pRHwfYEsVTdoAsXOGhoaZDBFWdlNW1/asebDF37g' + . 'ec60nJt5h9X8aHYNHz4PIC0nFosZcerc8Jz12vdmPH/FqVc+P3NCS01B58vmUy7/' + . '25QyxCNFDDOm/vCaCbh096Kf36d/u+aAnrq8ee/cl7ZdV3w+66/alOhSLJ80eYFE' + . 'MZ7G70gtffCBlduXYslmHlHkk+i8eH/S5jU184Jo1BNgIBdqq2fdSX6fMmbNmmWK' + . 'XMCV9CQH+wW8/18L/BeSV1YkHS6B9wAAAABJRU5ErkJggg=='; + } + + public function getAccessToken() + { + return $this->session->get($this->getId() . '.provider.access_token'); + } + + public function getUserName() + { + return $this->session->get($this->getId() . ".provider.username"); + } + +} diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php b/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php index ff3efe744c..049e47fa6b 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Twitter.php @@ -11,31 +11,34 @@ namespace Alchemy\Phrasea\Authentication\Provider; -use Alchemy\Phrasea\Authentication\Provider\Token\Token; -use Alchemy\Phrasea\Authentication\Provider\Token\Identity; use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; -use Symfony\Component\HttpFoundation\Request; +use Alchemy\Phrasea\Authentication\Provider\Token\Identity; +use Alchemy\Phrasea\Authentication\Provider\Token\Token; +use Alchemy\Phrasea\Exception\InvalidArgumentException; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; +use tmhOAuth; class Twitter extends AbstractProvider { private $twitter; - public function __construct(UrlGenerator $generator, SessionInterface $session, \tmhOAuth $twitter) + + public function __construct(UrlGenerator $generator, SessionInterface $session, tmhOAuth $twitter) { - $this->generator = $generator; + parent::__construct($generator, $session); + $this->twitter = $twitter; - $this->session = $session; } /** - * @param \tmhOAuth $twitter + * @param tmhOAuth $twitter * - * @return Twitter + * @return self */ - public function setTwitterClient(\tmhOAuth $twitter) + public function setTwitterClient(tmhOAuth $twitter): self { $this->twitter = $twitter; @@ -43,9 +46,9 @@ public function setTwitterClient(\tmhOAuth $twitter) } /** - * @return \tmhOAuth + * @return tmhOAuth */ - public function getTwitterClient() + public function getTwitterClient(): tmhOAuth { return $this->twitter; } @@ -53,23 +56,7 @@ public function getTwitterClient() /** * {@inheritdoc} */ - public function getId() - { - return 'twitter'; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'Twitter'; - } - - /** - * {@inheritdoc} - */ - public function authenticate(array $params = array()) + public function authenticate(array $params = array()): RedirectResponse { $params = array_merge(['providerId' => $this->getId()], $params); @@ -153,7 +140,7 @@ public function onCallback(Request $request) /** * {@inheritdoc} */ - public function getToken() + public function getToken(): Token { if (0 >= $this->session->get('twitter.provider.id')) { throw new NotAuthenticatedException('Provider has not authenticated'); @@ -165,7 +152,7 @@ public function getToken() /** * {@inheritdoc} */ - public function getIdentity() + public function getIdentity(): Identity { $access_token = $this->session->get('twitter.provider.access_token'); @@ -198,7 +185,7 @@ public function getIdentity() /** * {@inheritdoc} */ - public function getIconURI() + public function getIconURI(): string { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAwCAYAAAC4w' . 'JK5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MO' @@ -248,14 +235,22 @@ public function getIconURI() /** * {@inheritdoc} */ - public static function create(UrlGenerator $generator, SessionInterface $session, array $options) + public static function create(UrlGenerator $generator, SessionInterface $session, array $options): self { - $twitter = new \tmhOAuth([ - 'consumer_key' => $options['consumer-key'], - 'consumer_secret' => $options['consumer-secret'], - 'timezone' => date_default_timezone_get(), - ]); + foreach (['consumer-key', 'consumer-secret'] as $parm) { + if (!isset($options[$parm])) { + throw new InvalidArgumentException(sprintf('Missing Twitter "%s" parameter in conf/authentification/providers', $parm)); + } + } - return new Twitter($generator, $session, $twitter); + return new static( + $generator, + $session, + new tmhOAuth([ + 'consumer_key' => $options['consumer-key'], + 'consumer_secret' => $options['consumer-secret'], + 'timezone' => date_default_timezone_get() + ]) + ); } } diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php b/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php index 93af8800ad..8e40a2a45f 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Viadeo.php @@ -11,17 +11,18 @@ namespace Alchemy\Phrasea\Authentication\Provider; -use Alchemy\Phrasea\Authentication\Provider\Token\Token; +use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; use Alchemy\Phrasea\Authentication\Provider\Token\Identity; +use Alchemy\Phrasea\Authentication\Provider\Token\Token; +use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Exception\RuntimeException; -use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException; +use Guzzle\Common\Exception\GuzzleException; use Guzzle\Http\Client as Guzzle; use Guzzle\Http\ClientInterface; -use Guzzle\Common\Exception\GuzzleException; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; class Viadeo extends AbstractProvider { @@ -30,22 +31,22 @@ class Viadeo extends AbstractProvider private $key; private $secret; - public function __construct(UrlGenerator $generator, SessionInterface $session, ClientInterface $client, $key, $secret) + + public function __construct(UrlGenerator $generator, SessionInterface $session, array $options, ClientInterface $client) { - $this->generator = $generator; - $this->session = $session; - $this->client = $client; + parent::__construct($generator, $session); - $this->key = $key; - $this->secret = $secret; + $this->client = $client; + $this->key = $options['client-id']; + $this->secret = $options['client-secret']; } /** * @param ClientInterface $client * - * @return Github + * @return self */ - public function setGuzzleClient(ClientInterface $client) + public function setGuzzleClient(ClientInterface $client): self { $this->client = $client; @@ -55,7 +56,7 @@ public function setGuzzleClient(ClientInterface $client) /** * @return ClientInterface */ - public function getGuzzleClient() + public function getGuzzleClient(): ClientInterface { return $this->client; } @@ -63,23 +64,7 @@ public function getGuzzleClient() /** * {@inheritdoc} */ - public function getId() - { - return 'viadeo'; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'Viadeo'; - } - - /** - * {@inheritdoc} - */ - public function authenticate(array $params = array()) + public function authenticate(array $params = array()): RedirectResponse { $params = array_merge(['providerId' => $this->getId()], $params); @@ -192,7 +177,7 @@ public function onCallback(Request $request) /** * {@inheritdoc} */ - public function getToken() + public function getToken(): Token { if ('' === trim($this->session->get('viadeo.provider.id'))) { throw new NotAuthenticatedException('Viadeo has not authenticated'); @@ -204,7 +189,7 @@ public function getToken() /** * {@inheritdoc} */ - public function getIdentity() + public function getIdentity(): Identity { $identity = new Identity(); @@ -266,7 +251,7 @@ public function getIdentity() /** * {@inheritdoc} */ - public function getIconURI() + public function getIconURI(): string { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAwCAYAAAC4w' . 'JK5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MO' @@ -324,16 +309,14 @@ public function getIconURI() /** * {@inheritdoc} */ - public static function create(UrlGenerator $generator, SessionInterface $session, array $options) + public static function create(UrlGenerator $generator, SessionInterface $session, array $options): self { - if (!isset($options['client-id'])) { - throw new InvalidArgumentException('Missing Viadeo client-id parameter'); - } - - if (!isset($options['client-secret'])) { - throw new InvalidArgumentException('Missing Viadeo client-secret parameter'); + foreach (['client-id', 'client-secret'] as $parm) { + if (!isset($options[$parm])) { + throw new InvalidArgumentException(sprintf('Missing Viadeo "%s" parameter in conf/authentication/providers', $parm)); + } } - return new Viadeo($generator, $session, new Guzzle(), $options['client-id'], $options['client-secret']); + return new static($generator, $session, $options, new Guzzle()); } } diff --git a/lib/Alchemy/Phrasea/Controller/Root/LoginController.php b/lib/Alchemy/Phrasea/Controller/Root/LoginController.php index a0e977c931..c0d70465c9 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/LoginController.php +++ b/lib/Alchemy/Phrasea/Controller/Root/LoginController.php @@ -482,17 +482,43 @@ public function displayRegisterForm(Request $request) */ public function logout(Request $request) { - $this->dispatch(PhraseaEvents::LOGOUT, new LogoutEvent($this->app)); - $this->getAuthenticator()->closeAccount(); - - $this->app->addFlash('info', $this->app->trans('Vous etes maintenant deconnecte. A bientot.')); + $providerId = $this->getSession()->get('auth_provider.id', null); + + /** @var RedirectResponse $response */ + $response = null; + + // does the provider provides a logout redirection ? + if($providerId && ($provider = $this->findProvider($providerId))) { + if(method_exists($provider, 'logoutAndRedirect')) { + $redirectToPhr = $this->app->url('logout', [ + 'redirect' => $request->query->get("redirect") + ]); + $response = $provider->logoutAndRedirect($redirectToPhr); + } + else { + $this->dispatch(PhraseaEvents::LOGOUT, new LogoutEvent($this->app)); + $this->getAuthenticator()->closeAccount(); - $response = $this->app->redirectPath('homepage', [ - 'redirect' => $request->query->get("redirect") - ]); + $response = $provider->logout(); + } + } - $response->headers->clearCookie('persistent'); - $response->headers->clearCookie('last_act'); + if($response) { + $this->getSession()->set('auth_provider.id', null); + } + else { + // no provider logout : use ours + $this->dispatch(PhraseaEvents::LOGOUT, new LogoutEvent($this->app)); + $this->getAuthenticator()->closeAccount(); + + $this->app->addFlash('info', $this->app->trans('Vous etes maintenant deconnecte. A bientot.')); + + $response = $this->app->redirectPath('homepage', [ + 'redirect' => $request->query->get("redirect") + ]); + $response->headers->clearCookie('persistent'); + $response->headers->clearCookie('last_act'); + } return $response; } @@ -669,12 +695,15 @@ public function authenticateWithProvider(Request $request, $providerId) public function authenticationCallback(Request $request, $providerId) { + $this->getSession()->set('auth_provider.id', null); + $provider = $this->findProvider($providerId); // triggers what's necessary try { $provider->onCallback($request); $token = $provider->getToken(); + $this->getSession()->set('auth_provider.id', $providerId); } catch (NotAuthenticatedException $e) { $this->getSession()->getFlashBag()->add('error', $this->app->trans('Unable to authenticate with %provider_name%', ['%provider_name%' => $provider->getName()])); diff --git a/lib/Alchemy/Phrasea/Core/Provider/AuthenticationManagerServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/AuthenticationManagerServiceProvider.php index 31dca6d564..ac0123fda0 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/AuthenticationManagerServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/AuthenticationManagerServiceProvider.php @@ -11,23 +11,23 @@ namespace Alchemy\Phrasea\Core\Provider; -use Alchemy\Phrasea\Authentication\Authenticator; use Alchemy\Phrasea\Authentication\AccountCreator; +use Alchemy\Phrasea\Authentication\Authenticator; use Alchemy\Phrasea\Authentication\Manager; -use Alchemy\Phrasea\Authentication\ProvidersCollection; -use Alchemy\Phrasea\Authentication\Phrasea\FailureManager; -use Alchemy\Phrasea\Authentication\Provider\Factory as ProviderFactory; use Alchemy\Phrasea\Authentication\PersistentCookie\Manager as CookieManager; use Alchemy\Phrasea\Authentication\Phrasea\FailureHandledNativeAuthentication; +use Alchemy\Phrasea\Authentication\Phrasea\FailureManager; use Alchemy\Phrasea\Authentication\Phrasea\NativeAuthentication; use Alchemy\Phrasea\Authentication\Phrasea\OldPasswordEncoder; use Alchemy\Phrasea\Authentication\Phrasea\PasswordEncoder; +use Alchemy\Phrasea\Authentication\Provider\Factory as ProviderFactory; +use Alchemy\Phrasea\Authentication\ProvidersCollection; use Alchemy\Phrasea\Authentication\RecoveryService; use Alchemy\Phrasea\Authentication\RegistrationService; use Alchemy\Phrasea\Authentication\SuggestionFinder; +use Alchemy\Phrasea\Core\Event\Subscriber\PersistentCookieSubscriber; use Silex\Application; use Silex\ServiceProviderInterface; -use Alchemy\Phrasea\Core\Event\Subscriber\PersistentCookieSubscriber; class AuthenticationManagerServiceProvider implements ServiceProviderInterface { @@ -46,7 +46,15 @@ public function register(Application $app) }); $app['authentication.providers.factory'] = $app->share(function (Application $app) { - return new ProviderFactory($app['url_generator'], $app['session']); + return new ProviderFactory( + $app['url_generator'], + $app['session'], + $app['manipulator.user'], + $app['repo.users'], + $app['acl'], + $app['phraseanet.appbox'], + $app['random.medium'] + ); }); $app['authentication.providers.account-creator'] = $app->share(function (Application $app) { @@ -75,11 +83,34 @@ public function register(Application $app) $providers = new ProvidersCollection(); $authConf = $app['conf']->get('authentication'); - foreach ($authConf['providers'] as $providerId => $data) { - if (isset($data['enabled']) && false === $data['enabled']) { + foreach ($authConf['providers'] as $providerId => $conf) { + if (isset($conf['enabled']) && false === $conf['enabled']) { continue; } - $providers->register($app['authentication.providers.factory']->build($providerId, $data['options'])); + /** @var ProviderFactory $factory */ + $factory = $app['authentication.providers.factory']; + /* + * avoid bc brk + * - conf v1 : the top-level id declares the (unique) type/instance + * github: + * enabled: false + * options: + * client-id: '' + * client-secret: '' + * - conf v2 : the top-level key has no meaning, id, name, type etc. are in data + * github_foo: + * enabled: false + * display: false + * title: Github + * type: github + * options: + * client-id: '' + * client-secret: '' + */ + $type = array_key_exists('type', $conf) ? $conf['type'] : $providerId; + $display = array_key_exists('display', $conf) ? $conf['display'] : true; + $title = array_key_exists('title', $conf) ? $conf['title'] : $providerId; + $providers->register($factory->build($providerId, $type, $display, $title, $conf['options'])); } return $providers; diff --git a/lib/Alchemy/Phrasea/Core/Version.php b/lib/Alchemy/Phrasea/Core/Version.php index 803a5f6ded..6983d30ba0 100644 --- a/lib/Alchemy/Phrasea/Core/Version.php +++ b/lib/Alchemy/Phrasea/Core/Version.php @@ -17,7 +17,7 @@ class Version * @var string */ - private $number = '4.1.6-rc6'; + private $number = '4.1.7-rc1'; /** * @var string diff --git a/lib/Alchemy/Phrasea/PhraseanetService/Controller/PSExposeController.php b/lib/Alchemy/Phrasea/PhraseanetService/Controller/PSExposeController.php index 1734c9b202..76ff424df6 100644 --- a/lib/Alchemy/Phrasea/PhraseanetService/Controller/PSExposeController.php +++ b/lib/Alchemy/Phrasea/PhraseanetService/Controller/PSExposeController.php @@ -3,6 +3,7 @@ namespace Alchemy\Phrasea\PhraseanetService\Controller; use Alchemy\Phrasea\Application as PhraseaApplication; +use Alchemy\Phrasea\Authentication\ProvidersCollection; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Utilities\NetworkProxiesConfiguration; use Alchemy\Phrasea\WorkerManager\Event\ExposeUploadEvent; @@ -30,7 +31,7 @@ public function authenticateAction(PhraseaApplication $app, Request $request) if ($exposeConfiguration == null) { return $this->app->json([ 'success' => false, - 'message' => 'Please, set configuration in admin!' + 'error_description' => 'Please, set configuration in admin!' ]); } @@ -55,19 +56,14 @@ public function authenticateAction(PhraseaApplication $app, Request $request) } catch(\Exception $e) { return $this->app->json([ 'success' => false, - 'message' => $e->getMessage() + 'error_description' => $e->getMessage() ]); } if ($response->getStatusCode() !== 200) { - $body = $response->getBody()->getContents(); - - $body = json_decode($body,true); return $this->app->json([ 'success' => false, - 'message' => 'Status code: '. $response->getStatusCode(), - 'error' => $body['error'], - 'error_description' => $body['error_description'] + 'error_description' => 'Error with status code: ' . $response->getStatusCode(), ]); } @@ -183,6 +179,18 @@ public function listPublicationAction(PhraseaApplication $app, Request $request) $session = $this->getSession(); $passSessionName = $this->getPassSessionName($request->get('exposeName')); + $providerId = $session->get('auth_provider.id'); + + if (!$session->has($passSessionName) && $providerId != null) { + try { + $provider = $this->getAuthenticationProviders()->get($providerId); + if ($provider->getType() == 'PsAuth') { + $session->set($passSessionName, $provider->getAccessToken()); + $session->set($this->getLoginSessionName($request->get('exposeName')), $provider->getUserName()); + } + } catch(\Exception $e) { + } + } if (!$session->has($passSessionName) && $exposeConfiguration['connection_kind'] == 'password' && $request->get('format') != 'json') { return $app->json([ @@ -211,23 +219,37 @@ public function listPublicationAction(PhraseaApplication $app, Request $request) $exposeClient = $proxyConfig->getClientWithOptions($clientOptions); - $response = $exposeClient->get('/publications?flatten=true&order[createdAt]=desc', [ - 'headers' => [ - 'Authorization' => 'Bearer '. $accessToken, - 'Content-Type' => 'application/json' - ] - ]); + try { + $response = $exposeClient->get('/publications?flatten=true&order[createdAt]=desc', [ + 'headers' => [ + 'Authorization' => 'Bearer '. $accessToken, + 'Content-Type' => 'application/json' + ] + ]); - $exposeFrontBasePath = \p4string::addEndSlash($exposeConfiguration['expose_front_uri']); - $publications = []; - $basePath = []; + if ($response->getStatusCode() == 200) { + $body = @json_decode($response->getBody()->getContents(),true); - if ($response->getStatusCode() == 200) { - $body = json_decode($response->getBody()->getContents(),true); - $publications = $body['hydra:member']; - $basePath = $body['@id']; + if (!isset($body['hydra:member']) || !isset($body['@id'])) { + throw new \Exception("index undefined on response body!"); + } + $publications = $body['hydra:member']; + $basePath = $body['@id']; + } else { + throw new \Exception("Error with status code : " . $response->getStatusCode()); + } + + } catch(\Exception $e) { + return $app->json([ + 'success' => false, + 'publications' => [], + 'basePath' => [], + 'error' => $e->getMessage() + ]); } + $exposeFrontBasePath = \p4string::addEndSlash($exposeConfiguration['expose_front_uri']); + if ($request->get('format') == 'pub-list') { return $app->json([ 'publications' => $publications, @@ -279,12 +301,19 @@ public function getPublicationAction(PhraseaApplication $app, Request $request) $publication = []; - $resPublication = $exposeClient->get('/publications/' . $request->get('publicationId') , [ - 'headers' => [ - 'Authorization' => 'Bearer '. $accessToken, - 'Content-Type' => 'application/json' - ] - ]); + try { + $resPublication = $exposeClient->get('/publications/' . $request->get('publicationId') , [ + 'headers' => [ + 'Authorization' => 'Bearer '. $accessToken, + 'Content-Type' => 'application/json' + ] + ]); + } catch(\Exception $e) { + return $app->json([ + 'success' => false, + 'message' => $e->getMessage() + ]); + } if ($resPublication->getStatusCode() != 200) { return $app->json([ @@ -336,11 +365,18 @@ public function checkPublicationSlugAction(PhraseaApplication $app, Request $req $accessToken = $this->getAndSaveToken($request->get('exposeName')); - $resAvailability = $exposeClient->get('/publications/slug-availability/' . $request->get('slug') , [ - 'headers' => [ - 'Authorization' => 'Bearer '. $accessToken, - ] - ]); + try { + $resAvailability = $exposeClient->get('/publications/slug-availability/' . $request->get('slug') , [ + 'headers' => [ + 'Authorization' => 'Bearer '. $accessToken, + ] + ]); + } catch (\Exception $e) { + return $app->json([ + 'success' => false, + 'message' => $e->getMessage() + ]); + } if ($resAvailability->getStatusCode() != 200) { return $app->json([ @@ -410,12 +446,19 @@ public function getPublicationAssetsAction(PhraseaApplication $app, Request $req $accessToken = $this->getAndSaveToken($request->get('exposeName')); - $resPublication = $exposeClient->get('/publications/' . $request->get('publicationId') . '/assets?page=' . $page , [ - 'headers' => [ - 'Authorization' => 'Bearer '. $accessToken, - 'Content-Type' => 'application/json' - ] - ]); + try { + $resPublication = $exposeClient->get('/publications/' . $request->get('publicationId') . '/assets?page=' . $page , [ + 'headers' => [ + 'Authorization' => 'Bearer '. $accessToken, + 'Content-Type' => 'application/json' + ] + ]); + } catch(\Exception $e) { + return $app->json([ + 'success' => false, + 'message' => $e->getMessage() + ]); + } if ($resPublication->getStatusCode() != 200) { return $app->json([ @@ -469,12 +512,20 @@ public function listProfileAction(PhraseaApplication $app, Request $request) $profiles = []; $basePath = ''; - $resProfile = $exposeClient->get('/publication-profiles' , [ - 'headers' => [ - 'Authorization' => 'Bearer '. $accessToken, - 'Content-Type' => 'application/json' - ] - ]); + try { + $resProfile = $exposeClient->get('/publication-profiles' , [ + 'headers' => [ + 'Authorization' => 'Bearer '. $accessToken, + 'Content-Type' => 'application/json' + ] + ]); + } catch (\Exception $e) { + return $app->json([ + 'success' => false, + 'message' => $e->getMessage() + ]); + } + if ($resProfile->getStatusCode() != 200) { return $app->json([ @@ -802,12 +853,16 @@ public function getDataboxesFieldAction(PhraseaApplication $app, Request $reques } $exposeMappingName = $this->getExposeMappingName('field'); - $clientAnnotationProfile = $this->getClientAnnotationProfile($exposeClient, $exposeName, $profile); + try { + $clientAnnotationProfile = $this->getClientAnnotationProfile($exposeClient, $exposeName, $profile); - $fieldMapping = !empty($clientAnnotationProfile[$exposeMappingName]) ? $clientAnnotationProfile[$exposeMappingName] : []; + $fieldMapping = !empty($clientAnnotationProfile[$exposeMappingName]) ? $clientAnnotationProfile[$exposeMappingName] : []; - $actualFieldsList = !empty($fieldMapping['fields']) ? $fieldMapping['fields'] : []; - $fields = ($profile != null) ? $this->getFields($actualFieldsList) : []; + $actualFieldsList = !empty($fieldMapping['fields']) ? $fieldMapping['fields'] : []; + $fields = ($profile != null) ? $this->getFields($actualFieldsList) : []; + } catch (\Exception $e) { + + } // send geoloc and send vtt checked by default if not setting return $this->render('prod/WorkZone/ExposeFieldList.html.twig', [ @@ -830,9 +885,12 @@ public function getSubdefsListAction(PhraseaApplication $app, Request $request) ]); } - $exposeMappingName = $this->getExposeMappingName('subdef'); - $clientAnnotationProfile = $this->getClientAnnotationProfile($exposeClient, $exposeName, $profile); + try { + $clientAnnotationProfile = $this->getClientAnnotationProfile($exposeClient, $exposeName, $profile); + } catch(\Exception $e) { + } + $exposeMappingName = $this->getExposeMappingName('subdef'); $actualSubdefMapping = !empty($clientAnnotationProfile[$exposeMappingName]) ? $clientAnnotationProfile[$exposeMappingName] : []; $databoxes = empty($profile)? [] : $this->getApplicationBox()->get_databoxes(); @@ -890,22 +948,34 @@ public function saveFieldMappingAction(PhraseaApplication $app, Request $request ]); } - // get the actual value and merge it with the new one before save - $clientAnnotationProfile = $this->getClientAnnotationProfile($exposeClient, $exposeName, $profile); + $clientAnnotationProfile = []; + + try { + // get the actual value and merge it with the new one before save + $clientAnnotationProfile = $this->getClientAnnotationProfile($exposeClient, $exposeName, $profile); + } catch (\Exception $e) { + } $annotationValues = array_merge($clientAnnotationProfile, $fieldMapping); $accessToken = $this->getAndSaveToken($exposeName); - // save field mapping in the selected profile - $resProfile = $exposeClient->put($profile , [ - 'headers' => [ - 'Authorization' => 'Bearer '. $accessToken, - 'Content-Type' => 'application/json' - ], - 'json' => [ - 'clientAnnotations' => json_encode($annotationValues) - ] - ]); + try { + // save field mapping in the selected profile + $resProfile = $exposeClient->put($profile , [ + 'headers' => [ + 'Authorization' => 'Bearer '. $accessToken, + 'Content-Type' => 'application/json' + ], + 'json' => [ + 'clientAnnotations' => json_encode($annotationValues) + ] + ]); + } catch(\Exception $e) { + return $app->json([ + 'success' => false, + 'message' => $e->getMessage() + ]); + } if ($resProfile->getStatusCode() !== 200) { return $app->json([ @@ -938,23 +1008,30 @@ public function saveSubdefMappingAction(PhraseaApplication $app, Request $reques $this->getExposeMappingName('subdef') => $subdefs ]; - // get the actual value and merge it with the new one before save - $clientAnnotationProfile = $this->getClientAnnotationProfile($exposeClient, $exposeName, $profile); + try { + // get the actual value and merge it with the new one before save + $clientAnnotationProfile = $this->getClientAnnotationProfile($exposeClient, $exposeName, $profile); - $annotationValues = array_merge($clientAnnotationProfile, $subdefs); + $annotationValues = array_merge($clientAnnotationProfile, $subdefs); - $accessToken = $this->getAndSaveToken($exposeName); + $accessToken = $this->getAndSaveToken($exposeName); - // save subdef mapping in the selected profile - $resProfile = $exposeClient->put($profile , [ - 'headers' => [ - 'Authorization' => 'Bearer '. $accessToken, - 'Content-Type' => 'application/json' - ], - 'json' => [ - 'clientAnnotations' => json_encode($annotationValues) - ] - ]); + // save subdef mapping in the selected profile + $resProfile = $exposeClient->put($profile , [ + 'headers' => [ + 'Authorization' => 'Bearer '. $accessToken, + 'Content-Type' => 'application/json' + ], + 'json' => [ + 'clientAnnotations' => json_encode($annotationValues) + ] + ]); + } catch (\Exception $e) { + return $app->json([ + 'success' => false, + 'message' => $e->getMessage() + ]); + } if ($resProfile->getStatusCode() !== 200) { return $app->json([ @@ -1291,4 +1368,12 @@ private function getSession() { return $this->app['session']; } + + /** + * @return ProvidersCollection + */ + private function getAuthenticationProviders() + { + return $this->app['authentication.providers']; + } } diff --git a/lib/classes/patch/417PHRAS2995.php b/lib/classes/patch/417PHRAS2995.php new file mode 100644 index 0000000000..0f9cf0c0dd --- /dev/null +++ b/lib/classes/patch/417PHRAS2995.php @@ -0,0 +1,114 @@ +release; + } + /** + * {@inheritdoc} + */ + public function concern() + { + return $this->concern; + } + /** + * {@inheritdoc} + */ + public function require_all_upgrades() + { + return false; + } + /** + * {@inheritdoc} + */ + public function getDoctrineMigrations() + { + return []; + } + /** + * {@inheritdoc} + */ + public function apply(base $appbox, Application $app) + { + $id2title = [ + 'facebook' => 'Facebook', + 'github' => 'Github', + 'linkedin' => 'LinkedIn', + 'phraseanet' => 'Phraseanet', + 'twitter' => 'Twitter', + 'viadeo' => 'Viadeo' + ]; + + /** @var PropertyAccess $conf */ + $conf = $app['conf']; + $newProviders = []; + $psFound = false; + foreach ($app['conf']->get(['authentication', 'providers'], []) as $providerId => $data) { + if($providerId === 'google-plus') { // rip + continue; + } + if(array_key_exists('type', $data)) { + // already good format + $newProviders[$providerId] = $data; + if($data['type'] === "ps-auth") { + $psFound = true; + } + } + else { + // bump format + $newProviders[$providerId] = [ + 'enabled' => $data['enabled'], + 'display' => $data['enabled'], + 'title' => array_key_exists($providerId, $id2title) ? $id2title[$providerId] : $providerId, + 'type' => $providerId, + 'options' => $data['options'] + ]; + } + } + + // add ps + if(!$psFound && !array_key_exists('ps-auth-1', $newProviders)) { + $newProviders['ps-auth-1'] = [ + 'enabled' => false, + 'display' => false, + 'title' => 'PS Auth', + 'type' => 'ps-auth', + 'options' => [ + 'client-id' => 'client_id', + 'client-secret' => 'client_secret', + 'base-url' => 'https://api-auth.phrasea.local', + 'provider-type' => 'oauth', + 'provider-name' => 'v2', + 'icon-uri' => null, + 'birth-group' => '_firstlog', + 'everyone-group' => '_everyone', + 'metamodel' => '_metamodel', + 'model-gpfx' => '_M_', + 'model-upfx' => '_U_', + 'auto-logout' => false + ] + ]; + } + + $conf->set(['authentication', 'providers'], $newProviders); + + return true; + } +} diff --git a/lib/conf.d/configuration.yml b/lib/conf.d/configuration.yml index 3356a8d0a9..af84b9d4ef 100644 --- a/lib/conf.d/configuration.yml +++ b/lib/conf.d/configuration.yml @@ -189,34 +189,64 @@ authentication: providers: facebook: enabled: false + display: false + title: Facebook + type: facebook options: app-id: '' secret: '' + default-graph-version: 'v2.10' twitter: enabled: false + display: false + title: Twitter + type: twitter options: consumer-key: '' consumer-secret: '' - google-plus: + github: enabled: false + display: false + title: Github + type: github options: client-id: '' client-secret: '' - github: + viadeo: enabled: false + display: false + title: Viadeo + type: viadeo options: client-id: '' client-secret: '' - viadeo: + linkedin: enabled: false + display: false + title: LinkedIN + type: linkedin options: client-id: '' client-secret: '' - linkedin: + ps_auth_1: enabled: false + display: false + title: 'PS Auth 1' + type: 'ps-auth' options: client-id: '' client-secret: '' + base-url: 'https://api-auth.phrasea.local' + provider-type: 'oauth' + provider-name: 'v2' + icon-uri: null, + debug: false, + birth-group: '_firstlog' + everyone-group: '_everyone' + metamodel: '_metamodel' + model-gpfx: '_M_' + model-upfx: '_U_' + auto-logout: false registration-fields: - name: company diff --git a/resources/ansible/roles/app/templates/configuration.tpl b/resources/ansible/roles/app/templates/configuration.tpl index 905c19b731..abff263720 100644 --- a/resources/ansible/roles/app/templates/configuration.tpl +++ b/resources/ansible/roles/app/templates/configuration.tpl @@ -135,34 +135,57 @@ authentication: providers: facebook: enabled: false + display: false + title: Facebook + type: facebook options: app-id: '' secret: '' + default-graph-version: 'v2.10' twitter: enabled: false + display: false + title: Twitter + type: twitter options: consumer-key: '' consumer-secret: '' - google-plus: + github: enabled: false + display: false + title: Github + type: github options: client-id: '' client-secret: '' - github: + viadeo: enabled: false + display: false + title: Viadeo + type: viadeo options: client-id: '' client-secret: '' - viadeo: + linkedin: enabled: false + display: false + title: LinkedIN + type: linkedin options: client-id: '' client-secret: '' - linkedin: + ps_auth_1: enabled: false + display: false + title: 'PS Auth 1' + type: 'ps-auth' options: client-id: '' client-secret: '' + base-url: 'https://api-auth.phrasea.local' + provider-type: 'oauth' + provider-name: 'v2' + icon-uri: null registration-fields: - name: company diff --git a/resources/vagrant/config/phraseanet/configuration.yml b/resources/vagrant/config/phraseanet/configuration.yml index f897cfe0ce..79190a6f56 100644 --- a/resources/vagrant/config/phraseanet/configuration.yml +++ b/resources/vagrant/config/phraseanet/configuration.yml @@ -134,35 +134,58 @@ authentication: trials-before-display: 9 providers: facebook: - enabled: false - options: - app-id: '' - secret: '' + enabled: false + display: false + title: Facebook + type: facebook + options: + app-id: '' + secret: '' + default-graph-version: 'v2.10' twitter: enabled: false + display: false + title: Twitter + type: twitter options: consumer-key: '' consumer-secret: '' - google-plus: + github: enabled: false + display: false + title: Github + type: github options: client-id: '' client-secret: '' - github: + viadeo: enabled: false + display: false + title: Viadeo + type: viadeo options: client-id: '' client-secret: '' - viadeo: + linkedin: enabled: false + display: false + title: LinkedIN + type: linkedin options: client-id: '' client-secret: '' - linkedin: + ps_auth_1: enabled: false + display: false + title: 'PS Auth 1' + type: 'ps-auth' options: client-id: '' client-secret: '' + base-url: 'https://api-auth.phrasea.local' + provider-type: 'oauth' + provider-name: 'v2' + icon-uri: null registration-fields: - name: company diff --git a/resources/www/account/styles/_skin.scss b/resources/www/account/styles/_skin.scss index 0bc8961dc7..8a09e332ae 100644 --- a/resources/www/account/styles/_skin.scss +++ b/resources/www/account/styles/_skin.scss @@ -343,11 +343,6 @@ blockquote p { @include gradient($colorFacebook, 0.8); } -.google-plus-badge { - background-color: $colorGooglePlus; - @include gradient($colorGooglePlus, 0.8); -} - .viadeo-badge { background-color: $colorViadeo; @include gradient($colorViadeo, 0.8); diff --git a/resources/www/account/styles/_variables.scss b/resources/www/account/styles/_variables.scss index 388ae4ee2a..2612a66ec9 100644 --- a/resources/www/account/styles/_variables.scss +++ b/resources/www/account/styles/_variables.scss @@ -53,7 +53,6 @@ $inputOutsideBorder: #4c4c4c; $colorError: #af3030; $colorSuccess: #108946; $colorFacebook: #3b5a97; -$colorGooglePlus: #ba2828; $colorViadeo: #242424; $colorTwitter: #2fa3dc; $colorLinkedin: #025b8e; diff --git a/resources/www/authentication/styles/_variables.scss b/resources/www/authentication/styles/_variables.scss index 81e944f582..9b435c4979 100644 --- a/resources/www/authentication/styles/_variables.scss +++ b/resources/www/authentication/styles/_variables.scss @@ -53,7 +53,6 @@ $inputOutsideBorder: #4c4c4c; $colorError: #af3030; $colorSuccess: #108946; $colorFacebook: #3b5a97; -$colorGooglePlus: #ba2828; $colorViadeo: #242424; $colorTwitter: #2fa3dc; $colorLinkedin: #025b8e; diff --git a/templates/web/login/common/macros.html.twig b/templates/web/login/common/macros.html.twig index 3bc405c4ae..0f3a776268 100644 --- a/templates/web/login/common/macros.html.twig +++ b/templates/web/login/common/macros.html.twig @@ -55,11 +55,13 @@ {% macro providerList() %} {% endmacro %} diff --git a/templates/web/login/index.html.twig b/templates/web/login/index.html.twig index 6cca5039fe..ed5303c0a5 100644 --- a/templates/web/login/index.html.twig +++ b/templates/web/login/index.html.twig @@ -96,7 +96,15 @@ - {% if authentication_providers | count > 0 %} + + {% set display_providers = false %} + {% for provider in app['authentication.providers'] %} + {% if provider.display() %} + {% set display_providers = true %} + {% endif %} + {% endfor %} + + {% if display_providers %}