diff --git a/app/config/config.yml b/app/config/config.yml index 410000164..870f78391 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -71,31 +71,24 @@ framework: - mustache - twig -hwi_oauth: - firewall_names: - - main - resource_owners: - elife: - type: oauth2 - class: eLife\Journal\Security\OAuth2\ElifeResourceOwner - client_id: '%oauth2_client_id%' - client_secret: '%oauth2_client_secret%' - access_token_url: '%api_url%/oauth2/token' - authorization_url: '%api_url_public%/oauth2/authorize' - paths: - identifier: id - nickname: id - realname: name - options: - csrf: true - use_referer: true - isometriks_spam: honeypot: field: '%honeypot_field%' global: true message: Please try submitting the form again. +knpu_oauth2_client: + clients: + elife: + type: generic + provider_class: eLife\Journal\Security\OAuth2\ElifeProvider + client_id: '%oauth2_client_id%' + client_secret: '%oauth2_client_secret%' + redirect_route: log-in-check + provider_options: + api_url: '%api_url%' + api_url_public: '%api_url_public%' + monolog: channels: - api diff --git a/app/config/security.yml b/app/config/security.yml index cf3c61b30..560995c1b 100644 --- a/app/config/security.yml +++ b/app/config/security.yml @@ -1,23 +1,19 @@ security: providers: oauth: - id: hwi_oauth.user.provider + id: knpu.oauth2.user_provider firewalls: dev: pattern: ^/(_(profiler|wdt))/ security: false main: anonymous: ~ + guard: + authenticators: + - elife.journal.security.authenticator.elife logout: path: /log-out target: / - oauth: - login_path: /log-in - failure_path: / - oauth_user_provider: - service: hwi_oauth.user.provider - resource_owners: - elife: /log-in/check access_control: - path: ^/log-in roles: IS_AUTHENTICATED_ANONYMOUSLY diff --git a/app/config/services.yml b/app/config/services.yml index a799f4c1c..3b77d1fb7 100644 --- a/app/config/services.yml +++ b/app/config/services.yml @@ -241,6 +241,14 @@ services: - '%hypothesis_client_id%' - '%hypothesis_client_secret%' + elife.journal.security.authenticator.elife: + class: eLife\Journal\Security\Authenticator\ElifeAuthenticator + public: false + arguments: + - '@oauth2.registry' + - '@router' + - '@security.http_utils' + elife.journal.security.voter.feature.can_authenticate: class: eLife\Journal\Security\Voter\SessionAttributeVoter public: false diff --git a/app/config/services_test.yml b/app/config/services_test.yml index a2a724c6d..718419db6 100644 --- a/app/config/services_test.yml +++ b/app/config/services_test.yml @@ -27,7 +27,7 @@ services: class: Csa\Bundle\GuzzleBundle\Cache\MockStorageAdapter arguments: - '%api_mock%' - - ['Authorization', 'Content-Length', 'Host', 'host', 'Referer', 'referer', 'User-Agent', 'user-agent', 'X-Guzzle-Cache'] + - ['authorization', 'content-length', 'host', 'referer', 'user-agent', 'x-guzzle-cache'] elife.guzzle.middleware.mock.storage.validating: class: test\eLife\Journal\ValidatingStorageAdapter @@ -36,6 +36,12 @@ services: - '@elife.guzzle.middleware.mock.storage.validating.inner' - '@elife.api_validator.validator' + elife.guzzle.middleware.mock.storage.normalizing: + class: test\eLife\Journal\NormalizingStorageAdapter + decorates: elife.guzzle.middleware.mock.storage + arguments: + - '@elife.guzzle.middleware.mock.storage.normalizing.inner' + elife.guzzle.middleware.mock: class: Csa\Bundle\GuzzleBundle\GuzzleHttp\Middleware\MockMiddleware arguments: diff --git a/composer.json b/composer.json index ff4669dff..e0a519ee9 100644 --- a/composer.json +++ b/composer.json @@ -25,9 +25,9 @@ "fabpot/goutte": "^3.2", "firebase/php-jwt": "^5.0", "guzzlehttp/promises": "^1.3", - "hwi/oauth-bundle": "^0.6@dev", "isometriks/spam-bundle": "^1.0", "kevinrob/guzzle-cache-middleware": "^3.2", + "knpuniversity/oauth2-client-bundle": "^1.16", "mindplay/composer-locator": "^2.1", "mustache/mustache": "^2.12", "nelmio/security-bundle": "^2.4", diff --git a/composer.lock b/composer.lock index 31bb8e77d..1081b1a37 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "7ffe9f3934bc3c2f94c1b4fce72280c0", + "content-hash": "09617f78bd1f523042f95c42c90c1772", "packages": [ { "name": "beberlei/assert", @@ -113,58 +113,6 @@ ], "time": "2014-07-10T12:09:42+00:00" }, - { - "name": "clue/stream-filter", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/clue/php-stream-filter.git", - "reference": "d80fdee9b3a7e0d16fc330a22f41f3ad0eeb09d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/clue/php-stream-filter/zipball/d80fdee9b3a7e0d16fc330a22f41f3ad0eeb09d0", - "reference": "d80fdee9b3a7e0d16fc330a22f41f3ad0eeb09d0", - "shasum": "" - }, - "require": { - "php": ">=5.3" - }, - "require-dev": { - "phpunit/phpunit": "^5.0 || ^4.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Clue\\StreamFilter\\": "src/" - }, - "files": [ - "src/functions.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@lueck.tv" - } - ], - "description": "A simple and modern approach to stream filtering in PHP", - "homepage": "https://github.com/clue/php-stream-filter", - "keywords": [ - "bucket brigade", - "callback", - "filter", - "php_user_filter", - "stream", - "stream_filter_append", - "stream_filter_register" - ], - "time": "2017-08-18T09:54:01+00:00" - }, { "name": "cocur/slugify", "version": "v3.0.1", @@ -1389,162 +1337,6 @@ ], "time": "2017-03-20T17:10:46+00:00" }, - { - "name": "hwi/oauth-bundle", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/hwi/HWIOAuthBundle.git", - "reference": "2a6f3d983f35bf5b8273d015ec7b3eb5d64d9316" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hwi/HWIOAuthBundle/zipball/2a6f3d983f35bf5b8273d015ec7b3eb5d64d9316", - "reference": "2a6f3d983f35bf5b8273d015ec7b3eb5d64d9316", - "shasum": "" - }, - "require": { - "php": "^5.6|^7.0", - "php-http/client-common": "^1.3", - "php-http/client-implementation": "^1.0", - "php-http/discovery": "^1.0", - "php-http/guzzle6-adapter": "^1.1", - "php-http/httplug": "^1.0", - "php-http/message-factory": "^1.0", - "psr/http-message": "^1.0", - "symfony/form": "^2.7|^3.0|^4.0", - "symfony/framework-bundle": "^2.7|^3.0|^4.0", - "symfony/options-resolver": "^2.7|^3.0|^4.0", - "symfony/security-bundle": "^2.7|^3.0|^4.0", - "symfony/templating": "^2.7|^3.0|^4.0", - "symfony/yaml": "^2.7|^3.0|^4.0" - }, - "conflict": { - "twig/twig": "<1.12" - }, - "require-dev": { - "doctrine/orm": "^2.3", - "friendsofphp/php-cs-fixer": "^2.0", - "friendsofsymfony/user-bundle": "^1.3|^2.0", - "phpunit/phpunit": "^5.7", - "symfony/phpunit-bridge": "^2.7|^3.0|^4.0", - "symfony/property-access": "^2.7|^3.0|^4.0", - "symfony/stopwatch": "^2.7|^3.0|^4.0", - "symfony/twig-bundle": "^2.7|^3.0|^4.0", - "symfony/validator": "^2.7|^3.0|^4.0" - }, - "suggest": { - "doctrine/doctrine-bundle": "to use Doctrine user provider", - "friendsofsymfony/user-bundle": "to connect FOSUB with this bundle", - "symfony/property-access": "to use FOSUB integration with this bundle", - "symfony/twig-bundle": "to use the Twig hwi_oauth_* functions" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "0.6-dev" - } - }, - "autoload": { - "psr-4": { - "HWI\\Bundle\\OAuthBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Contributors", - "homepage": "https://github.com/hwi/HWIOAuthBundle/contributors" - }, - { - "name": "Joseph Bielawski", - "email": "stloyd@gmail.com" - }, - { - "name": "Alexander", - "email": "iam.asm89@gmail.com" - }, - { - "name": "Geoffrey Bachelet", - "email": "geoffrey.bachelet@gmail.com" - } - ], - "description": "Support for authenticating users using both OAuth1.0a and OAuth2 in Symfony2.", - "homepage": "http://github.com/hwi/HWIOAuthBundle", - "keywords": [ - "37signals", - "Authentication", - "Deezer", - "EVE Online", - "amazon", - "asana", - "auth0", - "azure", - "bitbucket", - "bitly", - "box", - "bufferapp", - "clever", - "dailymotion", - "deviantart", - "discogs", - "disqus", - "dropbox", - "eventbrite", - "facebook", - "firewall", - "fiware", - "flickr", - "foursquare", - "github", - "gitlab", - "google", - "hubic", - "instagram", - "jawbone", - "jira", - "linkedin", - "mail.ru", - "oauth", - "oauth1", - "oauth2", - "odnoklassniki", - "paypal", - "qq", - "reddit", - "runkeeper", - "salesforce", - "security", - "sensio connect", - "sina weibo", - "slack", - "sound cloud", - "spotify", - "stack exchange", - "stereomood", - "strava", - "toshl", - "trakt", - "trello", - "twitch", - "twitter", - "vkontakte", - "windows live", - "wordpress", - "wunderlist", - "xing", - "yahoo", - "yandex", - "youtube" - ], - "time": "2017-08-27T08:33:01+00:00" - }, { "name": "isometriks/spam-bundle", "version": "v1.0.0", @@ -1667,6 +1459,127 @@ ], "time": "2017-11-21T12:35:22+00:00" }, + { + "name": "knpuniversity/oauth2-client-bundle", + "version": "1.16.1", + "source": { + "type": "git", + "url": "https://github.com/knpuniversity/oauth2-client-bundle.git", + "reference": "86084234deb173c10d2258777037dddf958f8875" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/86084234deb173c10d2258777037dddf958f8875", + "reference": "86084234deb173c10d2258777037dddf958f8875", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^1.0|^2.0", + "php": " >=5.5.9", + "symfony/dependency-injection": "^2.8|^3.0|^4.0", + "symfony/framework-bundle": "^2.7|^3.0|^4.0", + "symfony/http-foundation": "^2.7|^3.0|^4.0", + "symfony/routing": "^2.7|^3.0|^4.0" + }, + "require-dev": { + "league/oauth2-facebook": "^1.1|^2.0", + "phpunit/phpunit": "^4.8", + "symfony/security-guard": "^2.8|^3.0" + }, + "suggest": { + "symfony/security-guard": "For integration with Symfony's Guard Security layer" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "KnpU\\OAuth2ClientBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ryan Weaver", + "email": "ryan@knpuniversity.com" + } + ], + "description": "Integration with league/oauth2-client to provide services", + "homepage": "http://knpuniversity.com", + "keywords": [ + "oauth", + "oauth2" + ], + "time": "2018-01-22T16:50:52+00:00" + }, + { + "name": "league/oauth2-client", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "313250eab923e673a5c0c8f463f443ee79f4383f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/313250eab923e673a5c0c8f463f443ee79f4383f", + "reference": "313250eab923e673a5c0c8f463f443ee79f4383f", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "paragonie/random_compat": "^1|^2", + "php": ">=5.6.0" + }, + "require-dev": { + "eloquent/liberator": "^2.0", + "eloquent/phony": "^0.14.1", + "jakub-onderka/php-parallel-lint": "~0.9", + "phpunit/phpunit": "^5.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "time": "2017-04-25T14:43:14+00:00" + }, { "name": "mindplay/composer-locator", "version": "2.1.3", @@ -2057,417 +1970,6 @@ ], "time": "2017-03-17T22:46:53+00:00" }, - { - "name": "php-http/client-common", - "version": "1.7.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/client-common.git", - "reference": "9accb4a082eb06403747c0ffd444112eda41a0fd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/client-common/zipball/9accb4a082eb06403747c0ffd444112eda41a0fd", - "reference": "9accb4a082eb06403747c0ffd444112eda41a0fd", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0", - "php-http/httplug": "^1.1", - "php-http/message": "^1.6", - "php-http/message-factory": "^1.0", - "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0" - }, - "require-dev": { - "guzzlehttp/psr7": "^1.4", - "phpspec/phpspec": "^2.5 || ^3.4 || ^4.2" - }, - "suggest": { - "php-http/cache-plugin": "PSR-6 Cache plugin", - "php-http/logger-plugin": "PSR-3 Logger plugin", - "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Client\\Common\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Common HTTP Client implementations and tools for HTTPlug", - "homepage": "http://httplug.io", - "keywords": [ - "client", - "common", - "http", - "httplug" - ], - "time": "2017-11-30T11:06:59+00:00" - }, - { - "name": "php-http/discovery", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/discovery.git", - "reference": "7b50ab4d6c9fdaa1ed53ae310c955900af6e3372" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/7b50ab4d6c9fdaa1ed53ae310c955900af6e3372", - "reference": "7b50ab4d6c9fdaa1ed53ae310c955900af6e3372", - "shasum": "" - }, - "require": { - "php": "^5.5 || ^7.0" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "^2.0.2", - "php-http/httplug": "^1.0", - "php-http/message-factory": "^1.0", - "phpspec/phpspec": "^2.4", - "puli/composer-plugin": "1.0.0-beta10" - }, - "suggest": { - "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories", - "puli/composer-plugin": "Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Discovery\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Finds installed HTTPlug implementations and PSR-7 message factories", - "homepage": "http://php-http.org", - "keywords": [ - "adapter", - "client", - "discovery", - "factory", - "http", - "message", - "psr7" - ], - "time": "2017-08-03T10:12:53+00:00" - }, - { - "name": "php-http/guzzle6-adapter", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/php-http/guzzle6-adapter.git", - "reference": "a56941f9dc6110409cfcddc91546ee97039277ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/a56941f9dc6110409cfcddc91546ee97039277ab", - "reference": "a56941f9dc6110409cfcddc91546ee97039277ab", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "^6.0", - "php": ">=5.5.0", - "php-http/httplug": "^1.0" - }, - "provide": { - "php-http/async-client-implementation": "1.0", - "php-http/client-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "php-http/adapter-integration-tests": "^0.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Adapter\\Guzzle6\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - }, - { - "name": "David de Boer", - "email": "david@ddeboer.nl" - } - ], - "description": "Guzzle 6 HTTP Adapter", - "homepage": "http://httplug.io", - "keywords": [ - "Guzzle", - "http" - ], - "time": "2016-05-10T06:13:32+00:00" - }, - { - "name": "php-http/httplug", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/httplug.git", - "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", - "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", - "shasum": "" - }, - "require": { - "php": ">=5.4", - "php-http/promise": "^1.0", - "psr/http-message": "^1.0" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "^1.0", - "phpspec/phpspec": "^2.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eric GELOEN", - "email": "geloen.eric@gmail.com" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "HTTPlug, the HTTP client abstraction for PHP", - "homepage": "http://httplug.io", - "keywords": [ - "client", - "http" - ], - "time": "2016-08-31T08:30:17+00:00" - }, - { - "name": "php-http/message", - "version": "1.6.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/message.git", - "reference": "2edd63bae5f52f79363c5f18904b05ce3a4b7253" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/message/zipball/2edd63bae5f52f79363c5f18904b05ce3a4b7253", - "reference": "2edd63bae5f52f79363c5f18904b05ce3a4b7253", - "shasum": "" - }, - "require": { - "clue/stream-filter": "^1.3", - "php": ">=5.4", - "php-http/message-factory": "^1.0.2", - "psr/http-message": "^1.0" - }, - "provide": { - "php-http/message-factory-implementation": "1.0" - }, - "require-dev": { - "akeneo/phpspec-skip-example-extension": "^1.0", - "coduo/phpspec-data-provider-extension": "^1.0", - "ext-zlib": "*", - "guzzlehttp/psr7": "^1.0", - "henrikbjorn/phpspec-code-coverage": "^1.0", - "phpspec/phpspec": "^2.4", - "slim/slim": "^3.0", - "zendframework/zend-diactoros": "^1.0" - }, - "suggest": { - "ext-zlib": "Used with compressor/decompressor streams", - "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", - "slim/slim": "Used with Slim Framework PSR-7 implementation", - "zendframework/zend-diactoros": "Used with Diactoros Factories" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Message\\": "src/" - }, - "files": [ - "src/filters.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "HTTP Message related tools", - "homepage": "http://php-http.org", - "keywords": [ - "http", - "message", - "psr-7" - ], - "time": "2017-07-05T06:40:53+00:00" - }, - { - "name": "php-http/message-factory", - "version": "v1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-http/message-factory.git", - "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", - "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", - "shasum": "" - }, - "require": { - "php": ">=5.4", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Factory interfaces for PSR-7 HTTP Message", - "homepage": "http://php-http.org", - "keywords": [ - "factory", - "http", - "message", - "stream", - "uri" - ], - "time": "2015-12-19T14:08:53+00:00" - }, - { - "name": "php-http/promise", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/promise.git", - "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", - "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", - "shasum": "" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "^1.0", - "phpspec/phpspec": "^2.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - }, - { - "name": "Joel Wurtz", - "email": "joel.wurtz@gmail.com" - } - ], - "description": "Promise used for asynchronous HTTP requests", - "homepage": "http://httplug.io", - "keywords": [ - "promise" - ], - "time": "2016-01-26T13:27:02+00:00" - }, { "name": "psr/cache", "version": "1.0.1", @@ -5744,7 +5246,6 @@ "elife/api-client": 20, "elife/api-sdk": 20, "elife/patterns": 20, - "hwi/oauth-bundle": 20, "elife/api": 20, "elife/api-validator": 20 }, diff --git a/src/AppKernel.php b/src/AppKernel.php index e80e48e3f..4f3a9ed66 100644 --- a/src/AppKernel.php +++ b/src/AppKernel.php @@ -7,8 +7,8 @@ use Csa\Bundle\GuzzleBundle\CsaGuzzleBundle; use eLife\Journal\Expression\ComposerLocateFunctionProvider; use eLife\Journal\Expression\TimeFunctionProvider; -use HWI\Bundle\OAuthBundle\HWIOAuthBundle; use Isometriks\Bundle\SpamBundle\IsometriksSpamBundle; +use KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle; use Nelmio\SecurityBundle\NelmioSecurityBundle; use PackageVersions\Versions; use Sensio\Bundle\DistributionBundle\SensioDistributionBundle; @@ -54,8 +54,8 @@ public function registerBundles() new CocurSlugifyBundle(), new CsaGuzzleBundle(), new FrameworkBundle(), - new HWIOAuthBundle(), new IsometriksSpamBundle(), + new KnpUOAuth2ClientBundle(), new MonologBundle(), new NelmioSecurityBundle(), new SecurityBundle(), diff --git a/src/Controller/AuthController.php b/src/Controller/AuthController.php index b8940d651..346b11bd9 100644 --- a/src/Controller/AuthController.php +++ b/src/Controller/AuthController.php @@ -2,24 +2,28 @@ namespace eLife\Journal\Controller; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Http\Util\TargetPathTrait; final class AuthController extends Controller { - public function redirectAction() : Response + use TargetPathTrait; + + public function redirectAction(Request $request) : Response { if (!$this->isGranted('FEATURE_CAN_AUTHENTICATE')) { throw new NotFoundHttpException('Not found'); } - $request = $this->get('request_stack')->getCurrentRequest(); - $path['_forwarded'] = $request->attributes; - $path['_controller'] = 'HWIOAuthBundle:Connect:redirectToService'; - $path['service'] = 'elife'; - $subRequest = $request->duplicate([], null, $path); + if ($referer = trim($request->headers->get('Referer'))) { + $firewall = $this->get('security.firewall.map')->getFirewallConfig($request); + $this->saveTargetPath($request->getSession(), $firewall->getName(), $referer); + } - return $this->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + return $this->get('oauth2.registry') + ->getClient('elife') + ->redirect(); } } diff --git a/src/DependencyInjection/OAuthClientPass.php b/src/DependencyInjection/OAuthClientPass.php index ce225da4c..3b729f494 100644 --- a/src/DependencyInjection/OAuthClientPass.php +++ b/src/DependencyInjection/OAuthClientPass.php @@ -9,7 +9,7 @@ final class OAuthClientPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - $container->getDefinition('hwi_oauth.http_client')->getArgument(0)->getArgument(0) - ->replaceArgument(0, $container->getDefinition('csa_guzzle.client.oauth')); + $container->findDefinition('knpu.oauth2.provider.elife') + ->addMethodCall('setHttpClient', [$container->findDefinition('csa_guzzle.client.oauth')]); } } diff --git a/src/Security/Authenticator/ElifeAuthenticator.php b/src/Security/Authenticator/ElifeAuthenticator.php new file mode 100644 index 000000000..2fbe34d9d --- /dev/null +++ b/src/Security/Authenticator/ElifeAuthenticator.php @@ -0,0 +1,97 @@ +clientRegistry = $clientRegistry; + $this->urlGenerator = $urlGenerator; + $this->httpUtils = $httpUtils; + } + + public function supports(Request $request) : bool + { + return 'log-in-check' === $request->attributes->get('_route'); + } + + public function getCredentials(Request $request) : AccessToken + { + try { + return $this->fetchAccessToken($this->getClient()); + } catch (AuthenticationException $e) { + throw $e; + } catch (Throwable $e) { + throw new AuthenticationException($e->getMessage(), 0, $e); + } + } + + /** + * @param AccessToken $credentials + */ + public function getUser($credentials, UserProviderInterface $userProvider) : UserInterface + { + return $userProvider->loadUserByUsername($this->getClient()->fetchUserFromToken($credentials)->getId()); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) : Response + { + if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { + $this->removeTargetPath($request->getSession(), $providerKey); + + try { + $targetPath = new Uri($targetPath); + } catch (InvalidArgumentException $e) { + $targetPath = null; + } + + if ($targetPath && !Uri::isAbsolute($targetPath)) { + $targetPath = null; + } + } + + return $this->httpUtils->createRedirectResponse($request, $targetPath ?? 'home'); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : Response + { + $this->saveAuthenticationErrorToSession($request, $exception); + + return new RedirectResponse($this->urlGenerator->generate('home')); + } + + public function start(Request $request, AuthenticationException $authException = null) : Response + { + return $this->getClient()->redirect(); + } + + private function getClient() : OAuth2Client + { + return $this->clientRegistry->getClient('elife'); + } +} diff --git a/src/Security/OAuth2/ElifeProvider.php b/src/Security/OAuth2/ElifeProvider.php new file mode 100644 index 000000000..bcdb0dbb2 --- /dev/null +++ b/src/Security/OAuth2/ElifeProvider.php @@ -0,0 +1,78 @@ +apiUrl = $options['api_url']; + $this->apiUrlPublic = $options['api_url_public'] ?? $options['api_url']; + } + + public function getBaseAuthorizationUrl() : string + { + return "{$this->apiUrlPublic}/oauth2/authorize"; + } + + public function getBaseAccessTokenUrl(array $params) : string + { + return "{$this->apiUrl}/oauth2/token"; + } + + public function getResourceOwnerDetailsUrl(AccessToken $token) + { + throw new BadMethodCallException('No resource owner details URL'); + } + + protected function fetchResourceOwnerDetails(AccessToken $token) : array + { + return array_intersect_key($token->getValues(), array_flip(['id', 'orcid', 'name'])); + } + + protected function getDefaultScopes() : array + { + return []; + } + + protected function checkResponse(ResponseInterface $response, $data) + { + if (isset($data['error'])) { + throw new IdentityProviderException($data['error_description'] ?? $data['error'], 0, (string) $response->getBody()); + } + + if ($response->getStatusCode() >= 400) { + throw new IdentityProviderException($response->getReasonPhrase(), 0, (string) $response->getBody()); + } + } + + protected function getAuthorizationParameters(array $options) : array + { + $parameters = parent::getAuthorizationParameters($options); + + unset($parameters['approval_prompt']); + + $parameters = array_filter($parameters); + + return $parameters; + } + + protected function createResourceOwner(array $response, AccessToken $token) : ElifeResourceOwner + { + return new ElifeResourceOwner($response['id'], $response['orcid'], $response['name']); + } +} diff --git a/src/Security/OAuth2/ElifeResourceOwner.php b/src/Security/OAuth2/ElifeResourceOwner.php index f4e35ac53..26754502d 100644 --- a/src/Security/OAuth2/ElifeResourceOwner.php +++ b/src/Security/OAuth2/ElifeResourceOwner.php @@ -2,59 +2,42 @@ namespace eLife\Journal\Security\OAuth2; -use Http\Client\Exception\HttpException; -use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException; -use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth2ResourceOwner; -use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Exception\AuthenticationException; - -final class ElifeResourceOwner extends GenericOAuth2ResourceOwner +use League\OAuth2\Client\Provider\ResourceOwnerInterface; + +final class ElifeResourceOwner implements ResourceOwnerInterface { - /** - * {@inheritdoc} - */ - public function getUserInformation(array $accessToken, array $extraParameters = []) - { - $response = $this->getUserResponse(); - $response->setData($accessToken); + private $id; + private $orcid; + private $name; - $response->setResourceOwner($this); - $response->setOAuthToken(new OAuthToken($accessToken)); + public function __construct(string $id, string $orcid, string $name) + { + $this->id = $id; + $this->orcid = $orcid; + $this->name = $name; + } - return $response; + public function getId() : string + { + return $this->id; } - /** - * {@inheritdoc} - */ - protected function doGetTokenRequest($url, array $parameters = []) + public function getOrcid() : string { - try { - return $this->httpRequest( - $url, - http_build_query($parameters, '', '&'), - ['Content-Type' => 'application/x-www-form-urlencoded'], - 'POST' - ); - } catch (HttpTransportException $e) { - $previous = $e->getPrevious(); - if (false === $previous instanceof HttpException) { - throw $e; - } - - if (false !== strpos($previous->getResponse()->getBody(), 'No name visible')) { - throw new AuthenticationException('No name visible', 0, $e); - } - - throw $e; - } + return $this->orcid; } - protected function configureOptions(OptionsResolver $resolver) + public function getName() : string { - parent::configureOptions($resolver); + return $this->name; + } - $resolver->remove('infos_url'); + public function toArray() : array + { + return [ + 'id' => $this->id, + 'orcid' => $this->orcid, + 'name' => $this->name, + ]; } } diff --git a/test/Assertions.php b/test/Assertions.php new file mode 100644 index 000000000..57b089ba0 --- /dev/null +++ b/test/Assertions.php @@ -0,0 +1,24 @@ +__toString(); + $normalizedActual = UriNormalizer::normalize(uri_for($actual), $flags)->__toString(); + + $this->assertEquals($normalizedExpected, $normalizedActual, $message); + } +} diff --git a/test/Controller/AuthenticationTest.php b/test/Controller/AuthenticationTest.php index 8e30d4ed0..6db78514b 100644 --- a/test/Controller/AuthenticationTest.php +++ b/test/Controller/AuthenticationTest.php @@ -50,7 +50,7 @@ public function it_lets_you_log_in_when_the_feature_flag_is_enabled() $this->assertTrue($response->isRedirect()); $location = Uri::withoutQueryValue(new Uri($response->headers->get('Location')), 'state'); - $this->assertSame('http://api.elifesciences.org/oauth2/authorize?response_type=code&client_id=journal_client_id&redirect_uri=http%3A%2F%2Flocalhost%2Flog-in%2Fcheck', $location->__toString()); + $this->assertSameUri('http://api.elifesciences.org/oauth2/authorize?response_type=code&client_id=journal_client_id&redirect_uri=http%3A%2F%2Flocalhost%2Flog-in%2Fcheck', $location); $state = parse_query((new Uri($response->headers->get('Location')))->getQuery())['state']; @@ -105,12 +105,15 @@ public function it_uses_the_referer_header_for_redirecting_after_logging_in(stri $response = $client->getResponse(); $this->assertTrue($response->isRedirect()); - $this->assertSame($expectedRedirect, $response->headers->get('Location')); + $this->assertSameUri($expectedRedirect, $response->headers->get('Location')); } public function refererProvider() : Traversable { yield 'no header' => ['', 'http://localhost/']; + yield 'string header' => ['foo', 'http://localhost/']; + yield 'route-name header' => ['about', 'http://localhost/']; + yield 'invalid uri header' => ['http://', 'http://localhost/']; yield 'homepage' => ['http://localhost/', 'http://localhost/']; yield 'local path' => ['http://localhost/foo', 'http://localhost/foo']; yield 'local path with query string' => ['http://localhost/foo?bar', 'http://localhost/foo?bar']; @@ -138,7 +141,7 @@ public function it_shows_error_messages() $this->assertTrue($response->isRedirect()); $location = Uri::withoutQueryValue(new Uri($response->headers->get('Location')), 'state'); - $this->assertSame('http://api.elifesciences.org/oauth2/authorize?response_type=code&client_id=journal_client_id&redirect_uri=http%3A%2F%2Flocalhost%2Flog-in%2Fcheck', $location->__toString()); + $this->assertSameUri('http://api.elifesciences.org/oauth2/authorize?response_type=code&client_id=journal_client_id&redirect_uri=http%3A%2F%2Flocalhost%2Flog-in%2Fcheck', $location); $state = parse_query((new Uri($response->headers->get('Location')))->getQuery())['state']; @@ -195,7 +198,7 @@ public function it_shows_an_error_message_when_no_name_is_available() $this->assertTrue($response->isRedirect()); $location = Uri::withoutQueryValue(new Uri($response->headers->get('Location')), 'state'); - $this->assertSame('http://api.elifesciences.org/oauth2/authorize?response_type=code&client_id=journal_client_id&redirect_uri=http%3A%2F%2Flocalhost%2Flog-in%2Fcheck', $location->__toString()); + $this->assertSameUri('http://api.elifesciences.org/oauth2/authorize?response_type=code&client_id=journal_client_id&redirect_uri=http%3A%2F%2Flocalhost%2Flog-in%2Fcheck', $location); $state = parse_query((new Uri($response->headers->get('Location')))->getQuery())['state']; diff --git a/test/NormalizingStorageAdapter.php b/test/NormalizingStorageAdapter.php new file mode 100644 index 000000000..f3698a47a --- /dev/null +++ b/test/NormalizingStorageAdapter.php @@ -0,0 +1,62 @@ +storageAdapter = $storageAdapter; + } + + public function fetch(RequestInterface $request) + { + return $this->storageAdapter->fetch($this->normalize($request)); + } + + public function save(RequestInterface $request, ResponseInterface $response) + { + $this->storageAdapter->save($this->normalize($request), $response); + } + + private function normalize(RequestInterface $request) : RequestInterface + { + $headers = array_change_key_case($request->getHeaders()); + $uri = UriNormalizer::normalize($request->getUri(), self::URI_FLAGS); + $body = $request->getBody()->__toString(); + + if ($body) { + try { + if ('application/x-www-form-urlencoded' === $request->getHeaderLine('Content-Type')) { + $body = UriNormalizer::normalize(new Uri("'?{$body}"), self::URI_FLAGS)->getQuery(); + } elseif (false !== strpos($request->getHeaderLine('Content-Type'), 'json')) { + $body = json_encode(json_decode($body)); + } + } catch (Throwable $e) { + // Do nothing. + } + } + + return new Request( + $request->getMethod(), + $uri, + $headers, + $body, + $request->getProtocolVersion() + ); + } +} diff --git a/test/WebTestCase.php b/test/WebTestCase.php index 0e46cd379..1b9d334f4 100644 --- a/test/WebTestCase.php +++ b/test/WebTestCase.php @@ -4,15 +4,16 @@ use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; -use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken; -use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUser; +use KnpU\OAuth2ClientBundle\Security\User\OAuthUser; use Symfony\Bundle\FrameworkBundle\Client; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; use Symfony\Component\BrowserKit\Cookie; +use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; abstract class WebTestCase extends BaseWebTestCase { use AppKernelTestCase; + use Assertions; final protected function logIn(Client $client) { @@ -20,18 +21,7 @@ final protected function logIn(Client $client) $session = $client->getContainer()->get('session'); - $token = new OAuthToken( - [ - 'access_token' => 'token', - 'expires_in' => 3920, - 'token_type' => 'Bearer', - 'id' => 'jcarberry', - 'orcid' => '0000-0002-1825-0097', - 'name' => 'Josiah Carberry', - ], - ['ROLE_USER', 'ROLE_OAUTH_USER'] - ); - $token->setUser(new OAuthUser('jcarberry')); + $token = new PostAuthenticationGuardToken(new OAuthUser('jcarberry', $roles = ['ROLE_USER', 'ROLE_OAUTH_USER']), 'main', $roles); $session->set('_security_main', serialize($token)); $session->save();