The start of something beautiful
This commit is contained in:
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
$header = <<<EOF
|
||||
This file is part of the HWIOAuthBundle package.
|
||||
|
||||
(c) Hardware Info <opensource@hardware.info>
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
EOF;
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setRules(array(
|
||||
'@Symfony' => true,
|
||||
'@Symfony:risky' => true,
|
||||
'@PHP71Migration' => true,
|
||||
'@PHPUnit60Migration:risky' => true,
|
||||
'combine_consecutive_issets' => true,
|
||||
'combine_consecutive_unsets' => true,
|
||||
'heredoc_to_nowdoc' => false,
|
||||
'header_comment' => ['header' => $header],
|
||||
'no_unreachable_default_argument_value' => false,
|
||||
'ordered_class_elements' => true,
|
||||
'ordered_imports' => true,
|
||||
'php_unit_method_casing' => ['case' => 'camel_case'],
|
||||
'php_unit_set_up_tear_down_visibility' => true,
|
||||
'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced'],
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'no_superfluous_phpdoc_tags' => [
|
||||
'allow_mixed' => true,
|
||||
'allow_unused_params' => false,
|
||||
],
|
||||
'phpdoc_types_order' => false,
|
||||
))
|
||||
->setRiskyAllowed(true)
|
||||
->setFinder(
|
||||
PhpCsFixer\Finder::create()
|
||||
->in(__DIR__.'/src')
|
||||
->in(__DIR__.'/tests')
|
||||
)
|
||||
;
|
||||
Vendored
+531
@@ -0,0 +1,531 @@
|
||||
Changelog
|
||||
=========
|
||||
## 2.2.0 (2024-02-28)
|
||||
* BC Break: Dropped support for PHP 7.4 & 8.0,
|
||||
* Added: Telegram resource owner,
|
||||
* Bugfix: Allow `use_authorization_to_get_token` to be configured to `false` for generic OAuth2,
|
||||
* Bugfix: Update API version for Facebook to latest available
|
||||
* Bugfix: Replace custom authenticator passport with custom badge usage,
|
||||
* Bugfix: Fix registration of failure handler,
|
||||
* Bugfix: Don't miss refresh token in registration controller,
|
||||
* Bugfix: Allow `null` as `$registrationForm` in `RegisterController`,
|
||||
* Bugfix: Fix connect functionality with authentication managers,
|
||||
|
||||
## 2.1.0 (2023-11-30)
|
||||
* BC Break: Dropped support for Symfony: `>6.0, <6.3`,
|
||||
* Added: New Passage resource owner,
|
||||
* Bugfix: Remove deprecations reported by Symfony 6.4,
|
||||
* Chore: Added support for Symfony 7,
|
||||
|
||||
## 2.0.0 (2023-10-01)
|
||||
* Bugfix: Prevent refreshing non-expired tokens
|
||||
* Bugfix: Remove deprecations reported by Symfony 6.x
|
||||
* Bugfix: Prevent fatal error when token doesn't have resource owner name set
|
||||
|
||||
## 2.0.0-BETA3 (2023-08-20)
|
||||
* BC Break: Dropped support for Symfony: 6.0.*,
|
||||
* BC Break: Class `Templating\Helper\OAuthHelper` was merged into `Twig\Extension\OAuthRuntime`,
|
||||
* BC Break: When resource owner class doesn't define `TYPE` constant or is `null`, then key will be calculated by converting its class name without `ResourceOwner` suffix to `snake_case`, if neither is felt, then `\LogicException` will be thrown,
|
||||
* Deprecated: method `UserResponseInterface::getUsername()` was deprecated in favour of `UserResponseInterface::getUserIdentifier()` to match changes in Symfony Security component,
|
||||
* Enhancement: `@internal` resourceOwner oauth types in Configuration are calculated automatically by scandir. All classes extended from `GenericOAuth[X]ResourceOwner` get `oauth[X]` type. If class only implements ResourceOwnerInterface then its oauth type is `unknown`. ResourceOwner key (parameter `type` in configs) should have defined ResourceOwner::TYPE constant. Each user defined custom ResourceOwner class that implemented `ResourceOwnerInterface` will be registered automatically. If `autoconfigure` option is disabled user have to add the tag `hwi_oauth.resource_owner` to the service definition,
|
||||
* Enhancement: Class `ConnectController` was split into two smaller ones, `Connect\ConnectController` & `Connect\RegisterController`,
|
||||
* Bugfix: Added `OAuth1ResourceOwner` & `OAuth2ResourceOwner` to cover case of implementing custom oauth resource owners,
|
||||
* Bugfix: Fixed Authorization Header in `CleverResourceOwner::doGetRequest`,
|
||||
* Bugfix: Catch also the `TransportExceptionInterface` in `AbstractResourceOwner::getResponseContent()` method,
|
||||
* Bugfix: Current matched Firewall is respected during generation of resource owner check path links,
|
||||
* Bugfix: Prevent fatal error in `OAuthUserProvider::loadUserByOAuthUserResponse()` when `nickname` is not available in OAuth response,
|
||||
* Bugfix: Use newer version of `firebase/php-jwt` library,
|
||||
* Chore: Removed not used Symfony Templating component
|
||||
|
||||
## 2.0.0-BETA2 (2022-01-16)
|
||||
* Deprecated: configuration parameter `firewall_names`, firewalls are now computed automatically - all firewalls that have defined `oauth` authenticator/provider will be collected,
|
||||
* Added: Ability to automatically refresh expired access tokens (only for derived from `GenericOAuth2ResourceOwner` resource owners), if option `refresh_on_expire` set to `true`,
|
||||
* Enhancement: Refresh token listener is disabled by default and will only be enabled if at least one resource owner has option `refresh_on_expure` set to `true`,
|
||||
* Enhancement: (`@internal`) Removed/replaced redundant argument `$firewallNames` from controllers. If controller class was copied and replaced, adapt list of arguments: In controller use `$resourceOwnerMapLocator->getFirewallNames()`,
|
||||
* Bugfix: `RefreshTokenListener` cannot be lazy. If current firewall is lazy (or anonymous: lazy) then current auth token is often initializing on `kernel.response`. In this case new access token will not be stored in session. Therefore, the expired token will be refreshed on each request,
|
||||
* Bugfix: `InteractiveLoginEvent` will be triggered also for `OAuthAuthenticator`,
|
||||
* Maintain: Changed config files from `*.xml` to `*.php` (services and routes). Xml routing configs `connect.xml`, `login.xml` and `redirect.xml` are steel present but deprecated. Please use `*.php` variants in your includes instead.
|
||||
|
||||
## 2.0.0-BETA1 (2021-12-10)
|
||||
* BC Break: Dropped PHP 7.3 support,
|
||||
* BC Break: Dropped support for Symfony: >=5.1 & <5.4,
|
||||
* BC Break: `OAuthExtension` is now a lazy Twig extension using a Runtime,
|
||||
* BC Break: removed support for `FOSUserBundle`,
|
||||
* BC Break: changed `process()` argument for `Form/RegistrationFormHandlerInterface`, from `Form $form` to `FormInterface $form`,
|
||||
* BC Break: changed form class name in template `Resources/views/Connect/connect_confirm.html.twig` from `fos_user_registration_register` to `registration_register`,
|
||||
* BC Break: removed configuration option `fosub` from `oauth_user_provider`,
|
||||
* BC Break: removed configuration options `hwi_oauth.fosub`, & all related DI parameters,
|
||||
* BC Break: removed DI parameter `hwi_oauth.registration.form.factory` in favour of declaring form class name as DI parameter: `hwi_oauth.connect.registration_form`,
|
||||
* BC Break: changed `ResourceOwnerMapInterface::hasResourceOwnerByName` signature, update if you use a custom resource owner,
|
||||
* BC Break: changed `ResourceOwnerMapInterface::getResourceOwnerByName` signature, update if you use a custom resource owner,
|
||||
* BC Break: changed `ResourceOwnerMapInterface::getResourceOwnerByRequest` signature, update if you use a custom resource owner,
|
||||
* BC Break: changed `ResourceOwnerMapInterface::getResourceOwnerCheckPath` signature, update if you use a custom resource owner,
|
||||
* BC Break: `ResourceOwnerMap` uses service locator instead of DI container,
|
||||
* BC Break: Removed abstract services: `hwi_oauth.abstract_resource_owner.generic`, `hwi_oauth.abstract_resource_owner.oauth1` & `hwi_oauth.abstract_resource_owner.oauth2`,
|
||||
* BC Break: Removed `setName()` method from `OAuth/ResourceOwnerInterface`,
|
||||
* BC Break: changed `__construct()` argument for `OAuth/ResourceOwner/AbstractResourceOwner`, from `HttpMethodsClient $httpClient` to `HttpClientInterface $httpClient`,
|
||||
* BC Break: replaced `php-http/httplug-bundle` with `symfony/http-client`
|
||||
* BC Break: removed `hwi_oauth.http` configuration,
|
||||
* BC Break: reworked bundles structure to match Symfony best practices:
|
||||
- bundle code moved to: `src/`,
|
||||
- tests moved to: `tests/`,
|
||||
- docs moved from `Resources/doc` into: `docs/`,
|
||||
* BC Break: routes provided by bundle now have `methods` requirements:
|
||||
- `hwi_oauth_connect_service`: `GET` & `POST`,
|
||||
- `hwi_oauth_connect_registration`: `GET` & `POST`,
|
||||
- `hwi_oauth_connect`: `GET`,
|
||||
- `hwi_oauth_service_redirect`: `GET`,
|
||||
* Added support for PHP 8.1,
|
||||
* Added support for Symfony 5.6,
|
||||
|
||||
## 1.4.5 (2021-12-08)
|
||||
* Bugfix: Fixed: BC break by restoring wrongly moved `AbstractOAuthToken::getCredentials()` method,
|
||||
|
||||
## 1.4.3 (2021-12-07)
|
||||
* Bugfix: Fixed support for PHP 8.1,
|
||||
* Bugfix: Fixed support for Symfony 5.4,
|
||||
* Bugfix: Fixed `VkontakteResourceOwner` option: `api_version` to not point to deprecated one,
|
||||
* Bugfix: `RequestStack::getMasterRequest()` is deprecated since Symfony 5.3, use `RequestStack::getMainRequest()` if exists,
|
||||
* Maintain: Added `GenericOAuth1ResourceOwnerTestCase`, `GenericOAuth2ResourceOwnerTestCase` & `ResourceOwnerTestCase` test case classes for easier unit testing custom resource owners
|
||||
|
||||
## 1.4.2 (2021-08-09)
|
||||
* Bugfix: remove `@final` declaration from `OAuthFactory` & `FOSUBUserProvider`,
|
||||
* Maintain: added `.gitattributes` to reduce amount of code in archives,
|
||||
|
||||
## 1.4.1 (2021-07-28)
|
||||
* Bugfix: Define missing `hwi_oauth.connect.confirmation` parameter,
|
||||
* Bugfix: Added missing success/failure handlers,
|
||||
|
||||
## 1.4.0 (2021-07-26)
|
||||
* BC Break: dropped Symfony 5.0 support as it is EOL,
|
||||
* BC Break: dropped PHP 7.2 support as it is EOL,
|
||||
* BC Break: changed `__construct()` argument for `OAuth/RequestDataStorage/SessionStorage`, from `SessionInterface $session` to `RequestStack $requestStack`,
|
||||
* BC Break: all internal classes are "softly" marked as `final`,
|
||||
* Added: Symfony 5.1 Security system support,
|
||||
* Added: Forward compatibility layer for session service deprecation,
|
||||
* Added: state support for service authentication URL's,
|
||||
* Added: ability to change the response after `HWIOAuthEvents::CONNECT_COMPLETED` is fired,
|
||||
* Added: PHPStan static analyse into CI,
|
||||
* Fixed: `OAuthProvide` to properly refresh data inside tokens,
|
||||
* Fixed: PHP notice in `AppleResourceOwner`,
|
||||
* Fixed: use new GitHub API in `GitHubResourceOwner`,
|
||||
* Fixed: functional tests with & without FOSUserBundle,
|
||||
* Fixed: controller don't depend on service container if possible,
|
||||
* Maintain: removed `Wunderlist` resource owner,
|
||||
* Maintain: removed several Symfony BC layers,
|
||||
* Maintain: removed Prophecy in favour of PHPUnit mocking,
|
||||
|
||||
## 1.3.0 (2021-01-03)
|
||||
* BC Break: dropped support for Symfony `<4.4`,
|
||||
* BC Break: dropped support for Doctrine Bundle `<2.0`,
|
||||
* Added PHP 8 support,
|
||||
* Upgraded Facebook API to v8.0,
|
||||
* Upgraded Twitch resource owner to incorporate latest Twitch API,
|
||||
* Fixed: undefined `id_token` exception in Azure resource owner,
|
||||
* Docs: changed firewall name to match flex receipt,
|
||||
* Maintain: moved from Travis CI to Github Actions,
|
||||
|
||||
## 1.2.0 (2020-10-19)
|
||||
* BC Break: dropped Symfony 4.3 support,
|
||||
* Added `first_name` & `last_name` in AzureResourceOwner,
|
||||
* Added: support for multiple OAuth2 state parameters,
|
||||
* Added: Apple resource owner,
|
||||
* Fixed: updated Azure `authorization` & `access_token` urls,
|
||||
* Fixed: Doctrine persistence deprecation errors,
|
||||
* Allow modification of the response in `FilterUserResponseEvent`,
|
||||
|
||||
## 1.1.0 (2020-04-06)
|
||||
* Added Symfony 5 support,
|
||||
* Added domain whitelist service to avoid open redirect on `target_path`,
|
||||
* Fixed: session service was not injected in `LoginController`,
|
||||
* Fixed: missing `setContainer` call to service configuration for `LoginController`,
|
||||
* Fixed: client id and client secret must be set in `Auth0ResourceOwner::doGetTokenRequest`,
|
||||
* Fixed: missing client id and client secret in `Auth0ResourceOwner`,
|
||||
* Twig dependency on `LoginController` is now optional,
|
||||
|
||||
## 1.0.0 (2020-01-17)
|
||||
* Dropped support for PHP 5.6, 7.0 and 7.1,
|
||||
* Dropped support for FOSUserBundle 1.3,
|
||||
* Dropped support for Symfony 2.8,
|
||||
* Minimum Symfony 3 requirement is 3.4,
|
||||
* Minimum Symfony 4 requirement is 4.3,
|
||||
* Fixed: WindowsLive Resource Owner token request,
|
||||
* Fixed: Update Facebook API to v3.1,
|
||||
* Fixed: Update Linkedin API to v2,
|
||||
* Fixed: YahooResourceOwner::doGetUserInformationRequest uses wrong arguments,
|
||||
* Fixed: Symfony deprecation warning in `symfony/config`,
|
||||
* Fixed: SensioConnect now uses new API URLs,
|
||||
* Fixed: Do not add Authorization header if no client_secret is present,
|
||||
* Fixed: `LoginController::connectAction` should not fail if no token is available,
|
||||
* Added: Genius.com resource owner,
|
||||
* Added: HTTPlug 2.0 support,
|
||||
* Added: Keycloak resource owner,
|
||||
* Added: The controller is now available as a service,
|
||||
* Added: Allow to use HTTP Basic auth for token request,
|
||||
* [BC break] Class `Configuration` has been marked final,
|
||||
* [BC break] Class `ConnectController` has been marked final,
|
||||
* [BC break] Class `HWIOAuthExtension` has been marked final,
|
||||
* [BC break] Class `OAuthExtension` has been marked final,
|
||||
* [BC break] Class `SetResourceOwnerServiceNameCompilerPass` has been marked final,
|
||||
* [BC break] Class `ConnectController` extends `AbstractController` instead of `Controller`,
|
||||
* [BC break] Service `hwi_oauth.http_client` has been marked private,
|
||||
* [BC break] Service `hwi_oauth.security.oauth_utils` has been marked private,
|
||||
* [BC break] Several service class parameters have been removed,
|
||||
|
||||
## 0.6.3 (2018-07-31)
|
||||
* Fixed: Vkontakte profile picture & nickname path,
|
||||
* Fixed: `Content-Length` header must be a string,
|
||||
* Fixed: Upgraded GitLab end point to v4,
|
||||
* Fixed: Resource owner map parameters must be public,
|
||||
* Fixed: Azure resource owner `infos_url` should not be empty,
|
||||
* Fixed: Don't start sessions twice & don't start sessions if already started,
|
||||
* Fixed: Updated BitBucket docs,
|
||||
* Added: Further compatibility changes for Symfony 4.1,
|
||||
* Added: LinkedIn `first-` & `last-` names,
|
||||
* Added: Facebook profile picture
|
||||
|
||||
## 0.6.2 (2018-03-28)
|
||||
* Fixed: VK requires API version now,
|
||||
* Fixed: Updated Slack resource owner to use new Slack API methods,
|
||||
* Fixed: Changing authorization and access token to v2 for LinkedIn,
|
||||
* Fixed: Fix double call of `getUserInformation()` in `ConnectController`,
|
||||
* Fixed: Fix serialization of `AccountNotLinkedException`,
|
||||
* Fixed: Check for grant_rule value `IS_AUTHENTICATED_FULLY` in DI configuration,
|
||||
* Fixed: Don't execute `OAuthProvider::refreshAccessToken()` when there is no refresh token
|
||||
|
||||
## 0.6.1 (2018-01-23)
|
||||
* BC BREAK: Replaced `PHPUnit_Framework_TestCase` with `PHPUnit\Framework\TestCase` in tests,
|
||||
* Added: Implemented `getUserInformation()` for Dropbox v2,
|
||||
* Fixed: Headers passed to `httpRequest()` method in various resource owners,
|
||||
* Fixed: Marked some services as `public` to make code compatible with Symfony 4
|
||||
|
||||
## 0.6.0 (2017-12-01)
|
||||
* BC BREAK: Fully replaced Buzz library with usage of HTTPlug & Guzzle 6,
|
||||
* BC BREAK: `hwi.http_client` config options are remove. HTTP configuration must rely on the HTTPlug client,
|
||||
* BC BREAK: Template engine other than Twig are no longer supported,
|
||||
* BC BREAK: Option `hwi_oauth.templating_engine` was removed,
|
||||
* Added: Symfony 4 support,
|
||||
* Added: `php-http/httplug-bundle` support, to auto-provide needed HTTPlug services and get full Symfony integration,
|
||||
* Added: `hwi.http.client` and `hwi.http.message_factory` config keys to provide your own HTTPlug services,
|
||||
* Added: `HWIOAuthEvents`,
|
||||
* Added: `ResourceOwnerInterface::addPaths()` method for easier managing paths in resource owners,
|
||||
* Fixed: Update Facebook API to v2.8,
|
||||
|
||||
## 0.5.3 (2017-01-08)
|
||||
* Fixed: Bitbucket2 resource owner,
|
||||
* Fixed: GitHub resource owner documentation,
|
||||
* Fixed: Don't require any form for the connect feature,
|
||||
* Fixed: Uncaught exception with custom error page,
|
||||
* Fixed: `php-cs-fixer` updated to latest version & run on base code
|
||||
|
||||
## 0.5.2 (2016-12-12)
|
||||
* Fixed: Prevent uncaught exception when redirecting to invalid route,
|
||||
* Fixed: Add more details too exception when account was not linked,
|
||||
* Fixed: Odnoklassinki resource owner,
|
||||
* Fixed: Office365 resource owner,
|
||||
* Fixed: StackExchange resource owner,
|
||||
* Fixed: WeChat resource owner,
|
||||
* Fixed: WindowsLive resource owner
|
||||
|
||||
## 0.5.1 (2016-10-03)
|
||||
* Fixed error that could occur with message "302 Header already sent",
|
||||
* Exclude tests from Composer autoloader
|
||||
|
||||
## 0.5.0 (2016-09-11)
|
||||
* Fixed: `OAuthHelper` should fallback to new `Request` in case of receiving `null`,
|
||||
* Fixed: Better `FOSUserBundle` integration,
|
||||
* Fixed: Serialization issue in `WechatResourceOwner`,
|
||||
* Fixed: Incorrect refresh token in `WechatResourceOwner`,
|
||||
* Fixed: Broken `TrelloResourceOwner`,
|
||||
* Fixed: Removed dead code in `OAuthProvider`,
|
||||
* Fixed: Update Facebook API to v2.7,
|
||||
* Added: Symfony 3 support,
|
||||
* Added: Redirect to `target_path` after successful registration/connection,
|
||||
* Added: Asana resource owner,
|
||||
* Added: Bitbucket resource owner,
|
||||
* Added: Clever resource owner,
|
||||
* Added: Itembase resource owner,
|
||||
* Added: Jawbon resource owner,
|
||||
* Added: Office365 resource owner,
|
||||
* Added: Wunderlist resource owner,
|
||||
* Added: Hungarian translation
|
||||
|
||||
## 0.4.3 (2016-09-11)
|
||||
* Fixed: Request parameters are not copied into new Request on forward,
|
||||
* Fixed: Fixed scope deprecating message,
|
||||
* Fixed: Resolved deprecated message in ConnectController,
|
||||
* Fixed: Removed usage of deprecated code in tests
|
||||
|
||||
## 0.4.2 (2016-07-27)
|
||||
* Fixed: Change Discogs URL from http to https,
|
||||
* Fixed: Update Facebook API URLs to not use outdated ones
|
||||
|
||||
## 0.4.1 (2016-03-08)
|
||||
* Fixed: Remove usage of deprecated Twig function `form_enctype` & replace with usage of `form_start`/`form_end`,
|
||||
* Fixed: Mark as not fully compatible with Symfony `~3.0`,
|
||||
* Fixed: Multiple firewalls can now have different resource owners,
|
||||
* Fixed: Wrong URL generated for Safesforce resource owner,
|
||||
* Added: `include_email` option into Twitter resource owner,
|
||||
* Added: Hungarian translation,
|
||||
* Added: Documentation about FOSUser integration
|
||||
|
||||
## 0.4.0 (2015-12-04)
|
||||
* [BC break] Added `UserResponseInterface#getFirstName()` method, also a new default path `firstname`
|
||||
was added, this path holds the first name of user,
|
||||
* [BC break] Added `UserResponseInterface#getLastName()` method, also a new default path `lastname`
|
||||
was added, this path holds the last name of user,
|
||||
* [BC break] Added `UserResponseInterface::getOAuthToken()` & basic implementation in `AbstractUserResponse`,
|
||||
* [BC break] `GenericOAuth1ResourceOwner::getRequestToken()` is now public method (was protected),
|
||||
* Added: configuration parameter `firewall_name` (will be removed in next major version)
|
||||
renamed to `firewall_names` to support multiple firewalls,
|
||||
* Added: configuration parameter: `failed_auth_path` which contains route name, on which user
|
||||
will be redirected after failure when connecting accounts (i.e. user denies connection),
|
||||
* Added: `appsecret_proof` functionality support to the Facebook resource owner,
|
||||
* Added: `sandbox` functionality support to the Salesforce resource owner,
|
||||
* Added Auth0 resource owner,
|
||||
* Added Azure resource owner,
|
||||
* Added BufferApp resource owner,
|
||||
* Added Deezer resource owner,
|
||||
* Added Discogs resource owner,
|
||||
* Added EveOnline resource owner,
|
||||
* Added Fiware resource owner,
|
||||
* Added Hubic resource owner,
|
||||
* Added Paypal resource owner,
|
||||
* Added Reddit resource owner,
|
||||
* Added Runkeeper resource owner,
|
||||
* Added Slack resource owner,
|
||||
* Added Spotify resource owner,
|
||||
* Added Soundcloud resource owner,
|
||||
* Added Strava resource owner,
|
||||
* Added Toshl resource owner,
|
||||
* Added Trakt resource owner,
|
||||
* Added Wechat resource owner,
|
||||
* Added Wordpress resource owner,
|
||||
* Added Xing resource owner,
|
||||
* Added Youtube resource owner,
|
||||
* Fixed: Revoking tokens for Facebook & Google resource owners,
|
||||
* Fixed: Instagram allows only GET calls to fetch user details,
|
||||
* Fixed: `ResourceOwnerMap` no longer depends on deprecated `ContainerAware` class,
|
||||
* Fixed: Wrong usage of `json_decode` in Mail.ru resource owner,
|
||||
* Fixed: Transform storage exceptions in OAuth1 resource owners into `AuthenticationException`
|
||||
* Fixed: Default scopes & fields for VKontakte resource owner
|
||||
|
||||
## 0.3.9 (2015-08-28)
|
||||
* Fix: Remove deprecated Twig features
|
||||
* Fix: Undefined variable in `FOSUBUserProvider::refreshUser`
|
||||
* Fix: Restore property accessor for Symfony 2.3
|
||||
|
||||
## 0.3.8 (2015-05-04)
|
||||
* Fix: Remove BC break for Symfony < 2.5,
|
||||
* Fix: Compatibility issues with Symfony 2.6+,
|
||||
* Fix: Deprecated graph URLs for `FacebookResourceOwner`
|
||||
|
||||
## 0.3.7 (2014-11-15)
|
||||
* Fix: `SessionStorage::save()` could throw php error,
|
||||
* Fix: `OAuthToken::isExpired()` always returned `false`,
|
||||
* Fix: `FoursquareResourceOwner`, `TwitchResourceOwner`, `SensioConnectResourceOwner`
|
||||
not working with bearer header,
|
||||
* Fix: Don't use deprecated fields in `FacebookResourceOwner`,
|
||||
* Fix: `FOSUBUserProvider::refreshUser()` always returning old user,
|
||||
|
||||
## 0.3.6 (2014-06-02)
|
||||
* Fix: `InstagramResourceOwner` regression while getting user details,
|
||||
* Fix: Add smooth migration for session (de)serialization
|
||||
|
||||
## 0.3.5 (2014-05-30)
|
||||
* Fix: `LinkedinResourceOwner` regression while getting user details,
|
||||
* Fix: OAuth `revoke` functionality to be available wider,
|
||||
* Fix: Removed undocumented functionality from `SinaWeiboResourceOwner`,
|
||||
* Fix: Always remove default ports from URLs to match OAuth 1.0a, Spec: 9.1.2
|
||||
|
||||
## 0.3.4 (2014-05-12)
|
||||
* Fix: Instagram OAuth redirect to one url,
|
||||
* Fix: `FOSUBUserProvider` should also implement `UserProviderInterface`,
|
||||
* Fix: `YahooResourceOwner` `infos_url` to use new format,
|
||||
* Fix: Send authorization via headers instead of URL parameter,
|
||||
* Fix: `GithubResourceOwner` revoke method,
|
||||
* Fix: Add login routing documentation note
|
||||
|
||||
## 0.3.3 (2014-02-17)
|
||||
* Fix: Incorrect redirect URL when no parameters are set,
|
||||
* Fix: Add missing parameter `prompt` for `GoogleResourceOwner`,
|
||||
* Fix: `WordpressResourceOwner` user details API call,
|
||||
* Fix: PHP Notice when `oauth_callback_confirmed` was set too `false`,
|
||||
* Fix: PHP Fatal when session returns boolean instead of object,
|
||||
* Fix: Add missing query parameters for `FacebookResourceOwner`
|
||||
|
||||
## 0.3.2 (2014-02-07)
|
||||
* Fix: Prevent `SessionUnavailableException` when returns back from service,
|
||||
* Fix: `EntityUserProvider` should implement `UserProviderInterface`,
|
||||
* Fix: `createdAt` property was missing when serializing the `OAuthToken`,
|
||||
* Added Italian translations
|
||||
|
||||
## 0.3.1 (2014-01-17)
|
||||
* Fix: Change Twitter API call to use SSL URL,
|
||||
* Fix: Problems with options in `VkontakteResourceOwner`,
|
||||
* Fix: Problems with OAuth 1.0a token & `YahooResourceOwner`,
|
||||
* Fix: Throw exception in `FOSUBUserProvider` when username is missing
|
||||
* Added SalesForce resource owner
|
||||
|
||||
## 0.3.0 (2013-09-28)
|
||||
* [BC break] `AccountConnectorInterface::connect()` method now requires the first
|
||||
parameter to be instance of `Symfony\Component\Security\Core\User\UserInterface`
|
||||
* [BC break] `ConnectController::authenticateUser()` method now requires the first
|
||||
parameter to be instance of `Symfony\Component\HttpFoundation\Request`
|
||||
* [BC break] Removed `AbstractResourceOwner::addOptions()` method
|
||||
* [BC break] `OAuthUtils::getAuthorizationUrl()` & `OAuthUtils::getLoginUrl()` methods
|
||||
now expect first parameter to be instance of `Symfony\Component\HttpFoundation\Request`
|
||||
* [BC break] LinkedIn resource owner now uses OAuth2 approach, visit official
|
||||
web page for details how to migrate: https://developer.linkedin.com/documents/authentication#migration
|
||||
* [BC break] Dropbox resource owner now uses OAuth2 approach
|
||||
* Added ability to merge response parts into single path
|
||||
* Added Bitly resource owner
|
||||
* Added Box resource owner
|
||||
* Added Dailymotion resource owner
|
||||
* Added DeviantArt resource owner
|
||||
* Added Eventbrite resource owner
|
||||
* Added Mail.ru resource owner
|
||||
* Added Sina Weibo resource owner
|
||||
* Added QQ.com resource owner
|
||||
* Added Trello resource owner
|
||||
* Added Wordpress resource owner
|
||||
|
||||
## 0.3.0-alpha2 (2013-07-29)
|
||||
* [BC break] Added `ResourceOwnerInterface::isCsrfTokenValid()` method
|
||||
* [BC break] Removed `OAuth1RequestTokenStorageInterface` along with the implementations
|
||||
* [BC break] `AbstractResourceOwner::__construct()` now requires `RequestDataStorageInterface`
|
||||
instance as last argument
|
||||
* Fix: Yandex resource owner using invalid parameter when requesting user data
|
||||
* Fix: To prevent unusual content headers response from resource owners should
|
||||
be first threaten as json and only in case of failure threaten as query text
|
||||
* Fix: Instagram resource owner is not able to receive user data more than once
|
||||
* Added ability to disable confirmation page when connecting accounts
|
||||
* Added CSRF protection for OAuth2 providers (turned off by default)
|
||||
* Added `RequestDataStorageInterface` along with implementation
|
||||
* Added Stereomood resource owner
|
||||
|
||||
## 0.3.0-alpha1 (2013-07-03)
|
||||
* [BC break] `GenericOAuth2ResourceOwner::getAccessToken()` now returns an array
|
||||
instead of a string. This array contains the access token and its 'expires_in'
|
||||
value, along with any other parameters returned from the authentication provider
|
||||
* [BC break] Added `OAuthAwareExceptionInterface#setToken()`, `OAuthAwareExceptionInterface#getRefreshToken()`,
|
||||
`OAuthAwareExceptionInterface#getRawToken()`, `OAuthAwareExceptionInterface#getExpiresIn()`
|
||||
methods
|
||||
* [BC break] Renamed `AbstractResourceOwner::doGetAccessTokenRequest` to `doGetTokenRequest`
|
||||
* [BC break] Removed `AdvancedPathUserResponse` & `AdvancedUserResponseInterface`
|
||||
* [BC break] Added `UserResponseInterface#getEmail()`, `UserResponseInterface#getProfilePicture()`,
|
||||
`UserResponseInterface#getRefreshToken()`, `UserResponseInterface#getExpiresIn()`,
|
||||
`UserResponseInterface#setOAuthToken()` methods
|
||||
* [BC break] Removed `UserResponseInterface::setAccessToken()` method
|
||||
* [BC break] Removed `AbstractUserResponse::getOAuthToken()` method because it was ambiguous
|
||||
* [BC break] `PathUserResponse#setPaths()` method no longer overwrite default paths
|
||||
* [BC break] `PathUserResponse#getPath()` method no longer throws an exception if path
|
||||
not exists
|
||||
* [BC break] `PathUserResponse#getValueForPath()` removed second argument from this method,
|
||||
it will not throw exception anymore if response or value is missing, but now will return
|
||||
`null` instead
|
||||
* [BC break] Added `ResourceOwnerInterface#getOption($name)` method
|
||||
* [BC break] `ResourceOwnerInterface#getUserInformation()` now must receive array (`$accessToken`)
|
||||
as first parameter, also added second parameter (`$extraParameters`) to be consistent
|
||||
along all implementations
|
||||
* Added `OAuthToken::getRefreshToken()`, `OAuthToken::setRefreshToken()`, `OAuthToken::getExpiresIn()`,
|
||||
`OAuthToken::setExpiresIn()`, `OAuthToken::getRawToken()`, `OAuthToken::setRawToken()`
|
||||
* Added `AbstractResourceOwner#addOptions()` & `ResourceOwnerInterface#setOption($name, $value)`
|
||||
methods which allows easy overwriting resource specific options
|
||||
* Added support for options: `access_type`, `request_visible_actions`, `approval_prompt` & `hd`
|
||||
in Google resource owner
|
||||
* Added 37signals resource owner
|
||||
* Added Amazon resource owner
|
||||
* Added Bitbucket resource owner
|
||||
* Added Disqus resource owner
|
||||
* Added Dropbox resource owner
|
||||
* Added Flickr resource owner
|
||||
* Added Instagram resource owner
|
||||
* Added Odnoklassniki resource owner
|
||||
* Added Yandex resource owner
|
||||
|
||||
## 0.2.10 (2013-12-09)
|
||||
* Fix: use `Symfony\Component\Security\Core\User\UserInterface` in `EntityUserProvider::refreshUser`
|
||||
* Fix: made `SessionStorage` compatible with Symfony 2.0
|
||||
|
||||
## 0.2.9 (2013-09-25)
|
||||
* Fix: Regression done in version `0.2.8` blocking usage without `FOSUserBundle`
|
||||
* Fix: `OAuthUtils::getAuthorizationUrl()` ignoring given redirect URL
|
||||
|
||||
## 0.2.8 (2013-09-19)
|
||||
* Fix: Added missing parts in user providers like: `loadUserByUsername()`
|
||||
or `refreshUser()` methods
|
||||
* Fix: Registering of user provider services
|
||||
* Fix: Make `OAuthUtils::signRequest()` compatible with OAuth1.0a specification
|
||||
|
||||
## 0.2.7 (2013-08-03)
|
||||
* Fix: Polish oauth error detection to cover cases from i.e. Facebook resource owner
|
||||
* Fix: Changed authorization url for Vkontakte resource owner
|
||||
|
||||
## 0.2.6 (2013-06-24)
|
||||
* Fix: Use same check for FOSUserBundle compatibility to prevent strange errors
|
||||
with calls of undefined services
|
||||
* Fix: User-land aliased (resource owner) services have the appropriate name
|
||||
|
||||
## 0.2.5 (2013-05-29)
|
||||
* Fix: Use user identifier represented as string for Twitter to prevent issues with
|
||||
losing accuracy for large numbers (i.e. Javascript) or type comparison (i.e. MongoDB)
|
||||
* Fix: Don't depend on `arg_separator.output` data for URL generation to prevent issues
|
||||
|
||||
## 0.2.4 (2013-05-15)
|
||||
* Fix: Throw `Symfony\Component\Security\Core\Exception\AccessDeniedException`
|
||||
& `Symfony\Component\HttpKernel\Exception\NotFoundHttpException` instead of `\Exception`
|
||||
to make cases more clear
|
||||
* Fix: Detect `oauth_problem` as authorization error and inform user instead logging error
|
||||
in background
|
||||
* Fix: Request extra parameters should have higher priority than default
|
||||
* Fix: How urls are build in resource owners
|
||||
* Fix: Missing parameter in `YahooResourceOwner`
|
||||
|
||||
## 0.2.3 (2013-05-06)
|
||||
* Added `AbstractUserResponse::getOAuthToken()` method to allow fetching only OAuth token details
|
||||
* Added french translation
|
||||
* Fix: FB incompatibility with 'error' field in response
|
||||
|
||||
## 0.2.2 (2013-04-15)
|
||||
* Fix: FOSUB registration form handler
|
||||
* Fix: Use API 1.1 for Twitter, not the deprecated 1.0
|
||||
|
||||
## 0.2.1 (2013-03-27)
|
||||
* Fixed issue with FOSUserBundle 2.x integration
|
||||
|
||||
## 0.2.0 (2013-03-26)
|
||||
* Added support for a `target_path_parameter` in order to control the redirect path after login
|
||||
* Added `hwi_oauth_authorization_url()` twig helper function
|
||||
* Added Jira resource owner
|
||||
* Added Yahoo resource owner
|
||||
* Added setting `realm` in configuration
|
||||
* Added support for FOSUserBundle 2.x integration
|
||||
* Added Stack Exchange resource owner
|
||||
* Fix: configuration parameter `firewall_name` is required
|
||||
* Fix: prevent throwing `AlreadyBoundException` when using FOSUserBundle 1.x integration
|
||||
* Fix: check for availability of `profilePicture` in views before calling it
|
||||
* Fix: `InMemoryProvider` now shows user nickname as name instead of unique identifier
|
||||
* Fix: don't set `realm` option if is empty in request headers
|
||||
* Fix: for infinity loop blockade and error token response handling
|
||||
|
||||
## 0.1-alpha (2012-08-27)
|
||||
* [BC break] Renamed path `username` to `identifier` to make it more clear that this path should
|
||||
hold the unique user identifier (previously `username`)
|
||||
* [BC break] Method `UserResponseInterface#getUsername()` now always returns a real
|
||||
unique user identifier, and uses path `identifier`
|
||||
* [BC break] `OAuth1RequestTokenStorageInterface#save()` second param `$token` must
|
||||
now be an array
|
||||
* [BC break] Configuration type 'generic' is renamed to 'oauth2'
|
||||
* [BC break] `redirect.xml` routing has to be imported. See the setup docs
|
||||
* Added `UserResponseInterface#getRealName()` method, also a new default path `realname`
|
||||
was added, this path holds the real name of user
|
||||
* Added `UserResponseInterface#getNickName()` method, also a new default path `nickname`
|
||||
was added, this path holds the nickname of user
|
||||
* Added `UserResponseInterface#getAccessToken()` and `UserResponseInterface#setAccessToken`
|
||||
* Added `OAuthToken#getCredentials()` returns an empty string to be consistent with
|
||||
the security component. The access token can still be retrieved from the
|
||||
`getAccessToken()` method
|
||||
* Added change that forces all authentication requests are now redirected to the login path
|
||||
* Added change that makes `firewall_name` option required setting
|
||||
* Added OAuth 1.0a support (linkedin/twitter/generic)
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012-2019 Hardware Info - https://hardware.info
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
HWIOAuthBundle
|
||||
==============
|
||||
|
||||
[](https://github.com/hwi/HWIOAuthBundle/actions/workflows/ci.yaml) [](https://packagist.org/packages/hwi/oauth-bundle) [](https://packagist.org/packages/hwi/oauth-bundle) [](https://packagist.org/packages/hwi/oauth-bundle)
|
||||
|
||||
The HWIOAuthBundle adds support for authenticating users via OAuth1.0a or OAuth2 in Symfony.
|
||||
|
||||
> __Note__: this bundle adds easy way to implement any of OAuth1.0a or OAuth2 provider!
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
All the installation instructions are located in the documentation, check it for a specific version:
|
||||
|
||||
* [__2.x__](https://github.com/hwi/HWIOAuthBundle/blob/master/docs/1-setting_up_the_bundle.md) (current) - with support for Symfony: `^5.4`, `^6.3` & `^7.0` (PHP: `^8.1`),
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The bulk of the documentation is stored in the `Resources/doc/index.md`
|
||||
file in this bundle. Read the documentation for version:
|
||||
|
||||
* [__2.x__](https://github.com/hwi/HWIOAuthBundle/blob/master/docs/index.md)
|
||||
|
||||
This bundle contains support for 58 different providers:
|
||||
* 37signals,
|
||||
* Amazon,
|
||||
* Apple,
|
||||
* Asana,
|
||||
* Auth0,
|
||||
* Azure,
|
||||
* Bitbucket,
|
||||
* Bitly,
|
||||
* Box,
|
||||
* BufferApp,
|
||||
* Clever,
|
||||
* Dailymotion,
|
||||
* Deezer,
|
||||
* DeviantArt,
|
||||
* Discogs,
|
||||
* Disqus,
|
||||
* Dropbox,
|
||||
* EVE Online,
|
||||
* Facebook,
|
||||
* FI-WARE,
|
||||
* Flickr,
|
||||
* Foursquare,
|
||||
* Genius,
|
||||
* GitHub,
|
||||
* Google,
|
||||
* Hubic,
|
||||
* Instagram,
|
||||
* Itembase,
|
||||
* Jawbone,
|
||||
* JIRA,
|
||||
* Keycloak,
|
||||
* LinkedIn,
|
||||
* Mail.ru
|
||||
* Odnoklassniki,
|
||||
* Office365,
|
||||
* Passage,
|
||||
* PayPal,
|
||||
* QQ,
|
||||
* RunKeeper,
|
||||
* Salesforce,
|
||||
* Sensio Connect,
|
||||
* Sina Weibo,
|
||||
* Slack,
|
||||
* Soundcloud,
|
||||
* Spotify,
|
||||
* Stack Exchange,
|
||||
* Stereomood,
|
||||
* Strava,
|
||||
* Toshl,
|
||||
* Trakt,
|
||||
* Trello,
|
||||
* Twitch,
|
||||
* Twitter,
|
||||
* VKontakte,
|
||||
* Windows Live,
|
||||
* Wordpress,
|
||||
* XING,
|
||||
* Yahoo,
|
||||
* Yandex,
|
||||
* Youtube
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This bundle is under the MIT license. See the complete [license in the bundle](https://github.com/hwi/HWIOAuthBundle/blob/master/LICENSE).
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.1 | :white_check_mark: |
|
||||
| <2.1 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability, please send an email to: stloyd@gmail.com
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"name": "hwi/oauth-bundle",
|
||||
"type": "symfony-bundle",
|
||||
"homepage": "https://github.com/hwi/HWIOAuthBundle",
|
||||
"license": "MIT",
|
||||
"description": "Support for authenticating users using both OAuth1.0a and OAuth2 in Symfony.",
|
||||
"keywords": [
|
||||
"authentication",
|
||||
"firewall",
|
||||
"oauth",
|
||||
"oauth1",
|
||||
"oauth2",
|
||||
"security",
|
||||
|
||||
"amazon",
|
||||
"apple",
|
||||
"asana",
|
||||
"auth0",
|
||||
"azure",
|
||||
"bitbucket",
|
||||
"bitly",
|
||||
"box",
|
||||
"bufferapp",
|
||||
"clever",
|
||||
"dailymotion",
|
||||
"deezer",
|
||||
"deviantart",
|
||||
"discogs",
|
||||
"disqus",
|
||||
"dropbox",
|
||||
"eventbrite",
|
||||
"eve online",
|
||||
"facebook",
|
||||
"fiware",
|
||||
"flickr",
|
||||
"foursquare",
|
||||
"genius",
|
||||
"github",
|
||||
"gitlab",
|
||||
"google",
|
||||
"hubic",
|
||||
"instagram",
|
||||
"jawbone",
|
||||
"jira",
|
||||
"linkedin",
|
||||
"mail.ru",
|
||||
"odnoklassniki",
|
||||
"paypal",
|
||||
"qq",
|
||||
"reddit",
|
||||
"runkeeper",
|
||||
"salesforce",
|
||||
"sensio connect",
|
||||
"sina weibo",
|
||||
"slack",
|
||||
"sound cloud",
|
||||
"spotify",
|
||||
"stack exchange",
|
||||
"stereomood",
|
||||
"strava",
|
||||
"toshl",
|
||||
"trakt",
|
||||
"trello",
|
||||
"twitch",
|
||||
"twitter",
|
||||
"vkontakte",
|
||||
"windows live",
|
||||
"wordpress",
|
||||
"xing",
|
||||
"yahoo",
|
||||
"yandex",
|
||||
"youtube",
|
||||
"37signals"
|
||||
],
|
||||
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alexander",
|
||||
"email": "iam.asm89@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Joseph Bielawski",
|
||||
"email": "stloyd@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Geoffrey Bachelet",
|
||||
"email": "geoffrey.bachelet@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Contributors",
|
||||
"homepage": "https://github.com/hwi/HWIOAuthBundle/contributors"
|
||||
}
|
||||
],
|
||||
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"symfony/deprecation-contracts": "^3.0",
|
||||
"symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/security-bundle": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/options-resolver": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/form": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/http-client": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/routing": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/twig-bundle": "^5.4 || ^6.3 || ^7.0"
|
||||
},
|
||||
|
||||
"require-dev": {
|
||||
"doctrine/doctrine-bundle": "^2.4",
|
||||
"doctrine/orm": "^2.9",
|
||||
"symfony/browser-kit": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/css-selector": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/phpunit-bridge": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/property-access": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/validator": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/stopwatch": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/translation": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/yaml": "^5.4 || ^6.3 || ^7.0",
|
||||
"phpunit/phpunit": "^9.6.11",
|
||||
"friendsofphp/php-cs-fixer": "^3.23",
|
||||
"symfony/monolog-bundle": "^3.4",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpstan/phpstan-symfony": "^1.3",
|
||||
"phpstan/extension-installer": "^1.3",
|
||||
"firebase/php-jwt": "^6.8"
|
||||
},
|
||||
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true,
|
||||
"phpstan/extension-installer": true
|
||||
}
|
||||
},
|
||||
|
||||
"conflict": {
|
||||
"symfony/security-bundle": ">=6.0,<6.3.4",
|
||||
"twig/twig": "<1.43|>=2.0,<2.13"
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"csfixer": "vendor/bin/php-cs-fixer fix --verbose --dry-run",
|
||||
"csfixer-fix": "vendor/bin/php-cs-fixer fix --verbose",
|
||||
"phpunit": "vendor/bin/phpunit",
|
||||
"phpstan": "vendor/bin/phpstan"
|
||||
},
|
||||
|
||||
"suggest": {
|
||||
"doctrine/doctrine-bundle": "to use Doctrine user provider",
|
||||
"firebase/php-jwt": "to use JWT utility functions",
|
||||
"symfony/property-access": "to use FOSUB integration with this bundle",
|
||||
"symfony/twig-bundle": "to use the Twig hwi_oauth_* functions"
|
||||
},
|
||||
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"HWI\\Bundle\\OAuthBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"HWI\\Bundle\\OAuthBundle\\Test\\": "src/Test/",
|
||||
"HWI\\Bundle\\OAuthBundle\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Connect;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Account connector objects are responsible for connecting an OAuth response
|
||||
* to the appropriate fields of the user object.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
interface AccountConnectorInterface
|
||||
{
|
||||
/**
|
||||
* Connects the response to the user object.
|
||||
*
|
||||
* @param UserInterface $user The user object
|
||||
* @param UserResponseInterface $response The oauth response
|
||||
*/
|
||||
public function connect(UserInterface $user, UserResponseInterface $response);
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Controller\Connect;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface;
|
||||
use HWI\Bundle\OAuthBundle\Event\FilterUserResponseEvent;
|
||||
use HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent;
|
||||
use HWI\Bundle\OAuthBundle\HWIOAuthEvents;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
|
||||
use Symfony\Component\EventDispatcher\Event as DeprecatedEvent;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AccountStatusException;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
|
||||
use Symfony\Component\Security\Http\SecurityEvents;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractController
|
||||
{
|
||||
protected ResourceOwnerMapLocator $resourceOwnerMapLocator;
|
||||
protected RequestStack $requestStack;
|
||||
protected EventDispatcherInterface $dispatcher;
|
||||
protected TokenStorageInterface $tokenStorage;
|
||||
protected UserCheckerInterface $userChecker;
|
||||
protected Environment $twig;
|
||||
protected ?AccountConnectorInterface $accountConnector;
|
||||
|
||||
public function __construct(
|
||||
ResourceOwnerMapLocator $resourceOwnerMapLocator,
|
||||
RequestStack $requestStack,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
UserCheckerInterface $userChecker,
|
||||
Environment $twig,
|
||||
?AccountConnectorInterface $accountConnector
|
||||
) {
|
||||
$this->resourceOwnerMapLocator = $resourceOwnerMapLocator;
|
||||
$this->requestStack = $requestStack;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->userChecker = $userChecker;
|
||||
$this->twig = $twig;
|
||||
$this->accountConnector = $accountConnector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource owner by name.
|
||||
*
|
||||
* @throws NotFoundHttpException if there is no resource owner with the given name
|
||||
*/
|
||||
protected function getResourceOwnerByName(string $name): ResourceOwnerInterface
|
||||
{
|
||||
foreach ($this->resourceOwnerMapLocator->getResourceOwnerMaps() as $ownerMap) {
|
||||
if ($resourceOwner = $ownerMap->getResourceOwnerByName($name)) {
|
||||
return $resourceOwner;
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotFoundHttpException(sprintf("No resource owner with name '%s'.", $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user with Symfony Security.
|
||||
*
|
||||
* @param string|array $accessToken
|
||||
*/
|
||||
protected function authenticateUser(Request $request, UserInterface $user, string $resourceOwnerName, $accessToken, bool $fakeLogin = true): void
|
||||
{
|
||||
try {
|
||||
$this->userChecker->checkPreAuth($user);
|
||||
$this->userChecker->checkPostAuth($user);
|
||||
} catch (AccountStatusException $e) {
|
||||
// Don't authenticate locked, disabled or expired users
|
||||
return;
|
||||
}
|
||||
|
||||
$token = new OAuthToken($accessToken, $user->getRoles());
|
||||
$token->setResourceOwnerName($resourceOwnerName);
|
||||
$token->setUser($user);
|
||||
|
||||
// required for compatibility with Symfony 5.4
|
||||
if (method_exists($token, 'setAuthenticated')) {
|
||||
$token->setAuthenticated(true, false);
|
||||
}
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
|
||||
if ($fakeLogin) {
|
||||
// Since we're "faking" normal login, we need to throw our INTERACTIVE_LOGIN event manually
|
||||
$this->dispatch(
|
||||
new InteractiveLoginEvent($request, $token),
|
||||
SecurityEvents::INTERACTIVE_LOGIN
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $service name of the resource owner to connect to
|
||||
*
|
||||
* @throws NotFoundHttpException if there is no resource owner with the given name
|
||||
*/
|
||||
protected function getConfirmationResponse(Request $request, array $accessToken, string $service): Response
|
||||
{
|
||||
/** @var OAuthToken $currentToken */
|
||||
$currentToken = $this->tokenStorage->getToken();
|
||||
/** @var UserInterface $currentUser */
|
||||
$currentUser = $currentToken->getUser();
|
||||
|
||||
$resourceOwner = $this->getResourceOwnerByName($service);
|
||||
$userInformation = $resourceOwner->getUserInformation($accessToken);
|
||||
|
||||
$event = new GetResponseUserEvent($currentUser, $request);
|
||||
$this->dispatch($event, HWIOAuthEvents::CONNECT_CONFIRMED);
|
||||
|
||||
$this->accountConnector->connect($currentUser, $userInformation);
|
||||
|
||||
if ($currentToken instanceof OAuthToken) {
|
||||
// Update user token with new details
|
||||
$newToken =
|
||||
(isset($accessToken['access_token']) || isset($accessToken['oauth_token'])) ?
|
||||
$accessToken : $currentToken->getRawToken();
|
||||
|
||||
$this->authenticateUser($request, $currentUser, $service, $newToken, false);
|
||||
}
|
||||
|
||||
if (null === $response = $event->getResponse()) {
|
||||
if ($targetPath = $this->getTargetPath($request->getSession())) {
|
||||
$response = new RedirectResponse($targetPath);
|
||||
} else {
|
||||
$response = new Response($this->twig->render('@HWIOAuth/Connect/connect_success.html.twig', [
|
||||
'userInformation' => $userInformation,
|
||||
'service' => $service,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
$event = new FilterUserResponseEvent($currentUser, $request, $response);
|
||||
$this->dispatch($event, HWIOAuthEvents::CONNECT_COMPLETED);
|
||||
|
||||
return $event->getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Event|DeprecatedEvent $event
|
||||
*/
|
||||
protected function dispatch($event, ?string $eventName = null): void
|
||||
{
|
||||
$this->dispatcher->dispatch($event, $eventName);
|
||||
}
|
||||
|
||||
protected function getSession(): ?SessionInterface
|
||||
{
|
||||
if (method_exists($this->requestStack, 'getSession')) {
|
||||
return $this->requestStack->getSession();
|
||||
}
|
||||
|
||||
if ((null !== $request = $this->requestStack->getCurrentRequest()) && $request->hasSession()) {
|
||||
return $request->getSession();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getTargetPath(?SessionInterface $session): ?string
|
||||
{
|
||||
if (!$session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->resourceOwnerMapLocator->getFirewallNames() as $firewallName) {
|
||||
$sessionKey = '_security.'.$firewallName.'.target_path';
|
||||
if ($session->has($sessionKey)) {
|
||||
return $session->get($sessionKey);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Controller\Connect;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface;
|
||||
use HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent;
|
||||
use HWI\Bundle\OAuthBundle\HWIOAuthEvents;
|
||||
use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
|
||||
use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ConnectController extends AbstractController
|
||||
{
|
||||
private OAuthUtils $oauthUtils;
|
||||
private AuthorizationCheckerInterface $authorizationChecker;
|
||||
private FormFactoryInterface $formFactory;
|
||||
private RouterInterface $router;
|
||||
private string $grantRule;
|
||||
private bool $failedUseReferer;
|
||||
private string $failedAuthPath;
|
||||
private bool $enableConnectConfirmation;
|
||||
|
||||
public function __construct(
|
||||
OAuthUtils $oauthUtils,
|
||||
ResourceOwnerMapLocator $resourceOwnerMapLocator,
|
||||
RequestStack $requestStack,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
UserCheckerInterface $userChecker,
|
||||
AuthorizationCheckerInterface $authorizationChecker,
|
||||
FormFactoryInterface $formFactory,
|
||||
Environment $twig,
|
||||
RouterInterface $router,
|
||||
string $grantRule,
|
||||
bool $failedUseReferer,
|
||||
string $failedAuthPath,
|
||||
bool $enableConnectConfirmation,
|
||||
?AccountConnectorInterface $accountConnector
|
||||
) {
|
||||
parent::__construct(
|
||||
$resourceOwnerMapLocator,
|
||||
$requestStack,
|
||||
$dispatcher,
|
||||
$tokenStorage,
|
||||
$userChecker,
|
||||
$twig,
|
||||
$accountConnector
|
||||
);
|
||||
|
||||
$this->oauthUtils = $oauthUtils;
|
||||
$this->grantRule = $grantRule;
|
||||
$this->failedUseReferer = $failedUseReferer;
|
||||
$this->failedAuthPath = $failedAuthPath;
|
||||
$this->enableConnectConfirmation = $enableConnectConfirmation;
|
||||
$this->authorizationChecker = $authorizationChecker;
|
||||
$this->formFactory = $formFactory;
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a user to a given account if the user is logged in and connect is enabled.
|
||||
*
|
||||
* @param string $service name of the resource owner to connect to
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws NotFoundHttpException if `connect` functionality was not enabled
|
||||
* @throws AccessDeniedException if no user is authenticated
|
||||
*/
|
||||
public function connectServiceAction(Request $request, string $service): Response
|
||||
{
|
||||
if (!$this->accountConnector) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$hasUser = $this->authorizationChecker->isGranted($this->grantRule);
|
||||
if (!$hasUser) {
|
||||
throw new AccessDeniedException('Cannot connect an account.');
|
||||
}
|
||||
|
||||
// Get the data from the resource owner
|
||||
$resourceOwner = $this->getResourceOwnerByName($service);
|
||||
|
||||
$session = $request->hasSession() ? $request->getSession() : $this->getSession();
|
||||
if ($session && !$session->isStarted()) {
|
||||
$session->start();
|
||||
}
|
||||
|
||||
$key = $request->query->get('key', (string) time());
|
||||
|
||||
$accessToken = null;
|
||||
if ($resourceOwner->handles($request)) {
|
||||
$accessToken = $resourceOwner->getAccessToken(
|
||||
$request,
|
||||
$this->oauthUtils->getServiceAuthUrl($request, $resourceOwner)
|
||||
);
|
||||
|
||||
if ($session) {
|
||||
// save in session
|
||||
$session->set('_hwi_oauth.connect_confirmation.'.$key, $accessToken);
|
||||
}
|
||||
} elseif ($session) {
|
||||
$accessToken = $session->get('_hwi_oauth.connect_confirmation.'.$key);
|
||||
}
|
||||
|
||||
// Redirect to the login path if the token is empty (Eg. User cancelled auth)
|
||||
if (null === $accessToken) {
|
||||
if ($this->failedUseReferer && $targetPath = $this->getTargetPath($session)) {
|
||||
return new RedirectResponse($targetPath);
|
||||
}
|
||||
|
||||
return new RedirectResponse($this->router->generate($this->failedAuthPath));
|
||||
}
|
||||
|
||||
// Show confirmation page?
|
||||
if (!$this->enableConnectConfirmation) {
|
||||
return $this->getConfirmationResponse($request, $accessToken, $service);
|
||||
}
|
||||
|
||||
$form = $this->formFactory->create();
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
return $this->getConfirmationResponse($request, $accessToken, $service);
|
||||
}
|
||||
|
||||
/** @var TokenInterface $token */
|
||||
$token = $this->tokenStorage->getToken();
|
||||
|
||||
$event = new GetResponseUserEvent($token->getUser(), $request);
|
||||
|
||||
$this->dispatch($event, HWIOAuthEvents::CONNECT_INITIALIZE);
|
||||
|
||||
if ($response = $event->getResponse()) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return new Response($this->twig->render('@HWIOAuth/Connect/connect_confirm.html.twig', [
|
||||
'key' => $key,
|
||||
'service' => $service,
|
||||
'form' => $form->createView(),
|
||||
'userInformation' => $resourceOwner->getUserInformation($accessToken),
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Controller\Connect;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface;
|
||||
use HWI\Bundle\OAuthBundle\Event\FilterUserResponseEvent;
|
||||
use HWI\Bundle\OAuthBundle\Event\FormEvent;
|
||||
use HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent;
|
||||
use HWI\Bundle\OAuthBundle\Form\RegistrationFormHandlerInterface;
|
||||
use HWI\Bundle\OAuthBundle\HWIOAuthEvents;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Exception\AccountNotLinkedException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class RegisterController extends AbstractController
|
||||
{
|
||||
private AuthorizationCheckerInterface $authorizationChecker;
|
||||
private FormFactoryInterface $formFactory;
|
||||
private ?RegistrationFormHandlerInterface $formHandler;
|
||||
private string $grantRule;
|
||||
private ?string $registrationForm;
|
||||
|
||||
public function __construct(
|
||||
ResourceOwnerMapLocator $resourceOwnerMapLocator,
|
||||
RequestStack $requestStack,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
UserCheckerInterface $userChecker,
|
||||
AuthorizationCheckerInterface $authorizationChecker,
|
||||
FormFactoryInterface $formFactory,
|
||||
Environment $twig,
|
||||
string $grantRule,
|
||||
?string $registrationForm,
|
||||
?AccountConnectorInterface $accountConnector,
|
||||
?RegistrationFormHandlerInterface $formHandler
|
||||
) {
|
||||
parent::__construct(
|
||||
$resourceOwnerMapLocator,
|
||||
$requestStack,
|
||||
$dispatcher,
|
||||
$tokenStorage,
|
||||
$userChecker,
|
||||
$twig,
|
||||
$accountConnector
|
||||
);
|
||||
|
||||
$this->grantRule = $grantRule;
|
||||
$this->registrationForm = $registrationForm;
|
||||
$this->formHandler = $formHandler;
|
||||
$this->authorizationChecker = $authorizationChecker;
|
||||
$this->formFactory = $formFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a registration form if there is no user logged in and connecting
|
||||
* is enabled.
|
||||
*
|
||||
* @param string $key key used for retrieving the right information for the registration form
|
||||
*
|
||||
* @throws NotFoundHttpException if `connect` functionality was not enabled
|
||||
* @throws AccessDeniedException if any user is authenticated
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function registrationAction(Request $request, string $key): Response
|
||||
{
|
||||
if (!$this->accountConnector || !$this->formHandler) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$hasUser = $this->authorizationChecker->isGranted($this->grantRule);
|
||||
if ($hasUser) {
|
||||
throw new AccessDeniedException('Cannot connect already registered account.');
|
||||
}
|
||||
|
||||
$error = null;
|
||||
$session = $request->hasSession() ? $request->getSession() : $this->getSession();
|
||||
if ($session) {
|
||||
if (!$session->isStarted()) {
|
||||
$session->start();
|
||||
}
|
||||
$error = $session->get('_hwi_oauth.registration_error.'.$key);
|
||||
$session->remove('_hwi_oauth.registration_error.'.$key);
|
||||
}
|
||||
|
||||
if (!$error instanceof AccountNotLinkedException) {
|
||||
throw new \RuntimeException('Cannot register an account.', 0, $error instanceof \Exception ? $error : null);
|
||||
}
|
||||
|
||||
if (!$this->registrationForm) {
|
||||
throw new \InvalidArgumentException('Registration form class must be set.');
|
||||
}
|
||||
|
||||
$userInformation = $this
|
||||
->getResourceOwnerByName($error->getResourceOwnerName())
|
||||
->getUserInformation($error->getRawToken())
|
||||
;
|
||||
|
||||
$form = $this->formFactory->create($this->registrationForm);
|
||||
|
||||
if ($this->formHandler->process($request, $form, $userInformation)) {
|
||||
$event = new FormEvent($form, $request);
|
||||
$this->dispatch($event, HWIOAuthEvents::REGISTRATION_SUCCESS);
|
||||
|
||||
/** @var UserInterface $user */
|
||||
$user = $form->getData();
|
||||
|
||||
$this->accountConnector->connect($user, $userInformation);
|
||||
|
||||
// Authenticate the user
|
||||
$this->authenticateUser($request, $user, $error->getResourceOwnerName(), $error->getRawToken());
|
||||
|
||||
if (null === $response = $event->getResponse()) {
|
||||
if ($targetPath = $this->getTargetPath($session)) {
|
||||
$response = new RedirectResponse($targetPath);
|
||||
} else {
|
||||
$response = new Response($this->twig->render('@HWIOAuth/Connect/registration_success.html.twig', [
|
||||
'userInformation' => $userInformation,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
$event = new FilterUserResponseEvent($user, $request, $response);
|
||||
$this->dispatch($event, HWIOAuthEvents::REGISTRATION_COMPLETED);
|
||||
|
||||
return $event->getResponse();
|
||||
}
|
||||
|
||||
if ($session) {
|
||||
// reset the error in the session
|
||||
$session->set('_hwi_oauth.registration_error.'.$key, $error);
|
||||
}
|
||||
|
||||
/** @var UserInterface $user */
|
||||
$user = $form->getData();
|
||||
|
||||
$event = new GetResponseUserEvent($user, $request);
|
||||
$this->dispatch($event, HWIOAuthEvents::REGISTRATION_INITIALIZE);
|
||||
|
||||
if ($response = $event->getResponse()) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return new Response($this->twig->render('@HWIOAuth/Connect/registration.html.twig', [
|
||||
'key' => $key,
|
||||
'form' => $form->createView(),
|
||||
'userInformation' => $userInformation,
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Controller;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Exception\AccountNotLinkedException;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class LoginController
|
||||
{
|
||||
private bool $connect;
|
||||
private string $grantRule;
|
||||
private AuthenticationUtils $authenticationUtils;
|
||||
private RouterInterface $router;
|
||||
private AuthorizationCheckerInterface $authorizationChecker;
|
||||
private RequestStack $requestStack;
|
||||
private Environment $twig;
|
||||
|
||||
public function __construct(
|
||||
AuthenticationUtils $authenticationUtils,
|
||||
RouterInterface $router,
|
||||
AuthorizationCheckerInterface $authorizationChecker,
|
||||
RequestStack $requestStack,
|
||||
Environment $twig,
|
||||
bool $connect,
|
||||
string $grantRule
|
||||
) {
|
||||
$this->authenticationUtils = $authenticationUtils;
|
||||
$this->router = $router;
|
||||
$this->authorizationChecker = $authorizationChecker;
|
||||
$this->requestStack = $requestStack;
|
||||
$this->twig = $twig;
|
||||
$this->connect = $connect;
|
||||
$this->grantRule = $grantRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that handles the login 'form'. If connecting is enabled the
|
||||
* user will be redirected to the appropriate login urls or registration forms.
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function connectAction(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$hasUser = $this->authorizationChecker->isGranted($this->grantRule);
|
||||
} catch (AuthenticationCredentialsNotFoundException $exception) {
|
||||
$hasUser = false;
|
||||
}
|
||||
|
||||
$error = $this->authenticationUtils->getLastAuthenticationError();
|
||||
|
||||
// if connecting is enabled and there is no user, redirect to the registration form
|
||||
if ($this->connect && !$hasUser && $error instanceof AccountNotLinkedException) {
|
||||
$key = time();
|
||||
$session = $request->hasSession() ? $request->getSession() : $this->getSession();
|
||||
if ($session) {
|
||||
if (!$session->isStarted()) {
|
||||
$session->start();
|
||||
}
|
||||
|
||||
$session->set('_hwi_oauth.registration_error.'.$key, $error);
|
||||
}
|
||||
|
||||
return new RedirectResponse($this->router->generate('hwi_oauth_connect_registration', ['key' => $key], UrlGeneratorInterface::ABSOLUTE_PATH));
|
||||
}
|
||||
|
||||
if (null !== $error) {
|
||||
$error = $error->getMessageKey();
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->twig->render('@HWIOAuth/Connect/login.html.twig', ['error' => $error])
|
||||
);
|
||||
}
|
||||
|
||||
private function getSession(): ?SessionInterface
|
||||
{
|
||||
if (method_exists($this->requestStack, 'getSession')) {
|
||||
return $this->requestStack->getSession();
|
||||
}
|
||||
|
||||
if ((null !== $request = $this->requestStack->getCurrentRequest()) && $request->hasSession()) {
|
||||
return $request->getSession();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Controller;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Security\Http\ResourceOwnerMapLocator;
|
||||
use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
|
||||
use HWI\Bundle\OAuthBundle\Util\DomainWhitelist;
|
||||
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class RedirectToServiceController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly OAuthUtils $oauthUtils,
|
||||
private readonly DomainWhitelist $domainWhitelist,
|
||||
private readonly ResourceOwnerMapLocator $resourceOwnerMapLocator,
|
||||
private readonly ?string $targetPathParameter,
|
||||
private readonly bool $failedUseReferer,
|
||||
private readonly bool $useReferer
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundHttpException
|
||||
*/
|
||||
public function redirectToServiceAction(Request $request, string $service): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$authorizationUrl = $this->oauthUtils->getAuthorizationUrl($request, $service);
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new NotFoundHttpException($e->getMessage(), $e);
|
||||
}
|
||||
|
||||
$this->storeReturnPath($request, $authorizationUrl);
|
||||
|
||||
return new RedirectResponse($authorizationUrl);
|
||||
}
|
||||
|
||||
private function storeReturnPath(Request $request, string $authorizationUrl): void
|
||||
{
|
||||
try {
|
||||
$session = $request->getSession();
|
||||
} catch (SessionNotFoundException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
$param = $this->targetPathParameter;
|
||||
|
||||
foreach ($this->resourceOwnerMapLocator->getFirewallNames() as $firewallName) {
|
||||
$sessionKey = '_security.'.$firewallName.'.target_path';
|
||||
$sessionKeyFailure = '_security.'.$firewallName.'.failed_target_path';
|
||||
|
||||
if (!empty($param) && $targetUrl = $request->get($param)) {
|
||||
if (!$this->domainWhitelist->isValidTargetUrl($targetUrl)) {
|
||||
throw new AccessDeniedHttpException('Not allowed to redirect to '.$targetUrl);
|
||||
}
|
||||
|
||||
$session->set($sessionKey, $targetUrl);
|
||||
}
|
||||
|
||||
if ($this->failedUseReferer && !$session->has($sessionKeyFailure) && ($targetUrl = $request->headers->get('Referer')) && $targetUrl !== $authorizationUrl) {
|
||||
$session->set($sessionKeyFailure, $targetUrl);
|
||||
}
|
||||
|
||||
if ($this->useReferer && !$session->has($sessionKey) && ($targetUrl = $request->headers->get('Referer')) && $targetUrl !== $authorizationUrl) {
|
||||
$session->set($sessionKey, $targetUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\DependencyInjection\CompilerPass;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\DependencyInjection\HWIOAuthExtension;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
final class EnableRefreshOAuthTokenListenerCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
/** @var HWIOAuthExtension $extension */
|
||||
$extension = $container->getExtension('hwi_oauth');
|
||||
if (!$extension->isRefreshTokenListenerEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($extension->getFirewallNames() as $firewallName => $_) {
|
||||
$container->findDefinition('hwi_oauth.context_listener.token_refresher.'.$firewallName)
|
||||
->addMethodCall('enable');
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\DependencyInjection\CompilerPass;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\DependencyInjection\Configuration;
|
||||
use HWI\Bundle\OAuthBundle\DependencyInjection\HWIOAuthExtension;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
|
||||
/**
|
||||
* Registers "hwi_oauth.resource_owner.$type.class" Parameters and checks resource owner configurations, whether given
|
||||
* type exists (Apps can add own ResourceOwners).
|
||||
*
|
||||
* Adds resource owner maps to the locator and utils.
|
||||
*/
|
||||
final class ResourceOwnerCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$this->registerResourceOwnerTypeClassParameters($container);
|
||||
$this->addResourceOwnerMapToLocatorAndUtils($container);
|
||||
}
|
||||
|
||||
private function registerResourceOwnerTypeClassParameters(ContainerBuilder $container): void
|
||||
{
|
||||
foreach ($container->findTaggedServiceIds('hwi_oauth.resource_owner') as $serviceId => $_) {
|
||||
$definition = $container->findDefinition($serviceId);
|
||||
Configuration::registerResourceOwner($definition->getClass());
|
||||
}
|
||||
|
||||
foreach (Configuration::getResourceOwnerTypesClassMap() as $type => $resourceOwnerClass) {
|
||||
$parameterName = "hwi_oauth.resource_owner.$type.class";
|
||||
if (!$container->hasParameter($parameterName)) {
|
||||
$container->setParameter($parameterName, $resourceOwnerClass);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether resource owner set with parameter '%hwi_oauth.resource_owner.[type].class%' type exists
|
||||
/** @var ServiceLocator $locator */
|
||||
$locator = $container->get('hwi_oauth.resource_owners.locator');
|
||||
|
||||
foreach ($locator->getProvidedServices() as $resourceOwnerName => $_) {
|
||||
try {
|
||||
$definition = $container->findDefinition('hwi_oauth.resource_owner.'.$resourceOwnerName);
|
||||
} catch (ServiceNotFoundException $e) {
|
||||
// Resource owner defined with "options.service"
|
||||
continue;
|
||||
}
|
||||
|
||||
$resourceOwnerClass = $definition->getClass();
|
||||
|
||||
// Check whether a ResourceOwner class exists only if resource owner was set by its "options.type"
|
||||
if (false === preg_match('~^%(?P<parameter>hwi_oauth.resource_owner.(?P<type>.+).class)%$~', $resourceOwnerClass, $match)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!($match['type'] ?? null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Configuration::isResourceOwnerSupported($match['type'])) {
|
||||
$e = new \InvalidArgumentException(sprintf('Unknown resource owner type "%s"', $match['type']));
|
||||
|
||||
throw new InvalidConfigurationException(sprintf('Invalid configuration for path "hwi_oauth.resource_owners.%s.type": %s', $resourceOwnerName, $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function addResourceOwnerMapToLocatorAndUtils(ContainerBuilder $container): void
|
||||
{
|
||||
/** @var HWIOAuthExtension $extension */
|
||||
$extension = $container->getExtension('hwi_oauth');
|
||||
|
||||
$locatorDef = $container->findDefinition('hwi_oauth.resource_ownermap_locator');
|
||||
$oauthUtilsDef = $container->findDefinition('hwi_oauth.security.oauth_utils');
|
||||
|
||||
foreach ($extension->getFirewallNames() as $firewallName => $_) {
|
||||
$resourceOwnerMapId = 'hwi_oauth.resource_ownermap.'.$firewallName;
|
||||
|
||||
$container->findDefinition($resourceOwnerMapId)
|
||||
->setArgument('$locator', new Reference('hwi_oauth.resource_owners.locator'));
|
||||
|
||||
$resourceOwnerMapRef = new Reference($resourceOwnerMapId);
|
||||
|
||||
$locatorDef->addMethodCall('set', [$firewallName, $resourceOwnerMapRef]);
|
||||
$oauthUtilsDef->addMethodCall('addResourceOwnerMap', [$firewallName, $resourceOwnerMapRef]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\DependencyInjection;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth1ResourceOwner;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericOAuth2ResourceOwner;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
|
||||
use Symfony\Component\Config\Definition\BaseNode;
|
||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* Configuration for the extension.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
final class Configuration implements ConfigurationInterface
|
||||
{
|
||||
/**
|
||||
* type => ResourceOwner mapping for hwi_oauth.resource_owner.*.class parameters.
|
||||
*
|
||||
* @var array<string, class-string<GenericOAuth1ResourceOwner|GenericOAuth2ResourceOwner|ResourceOwnerInterface>>
|
||||
*/
|
||||
private static array $resourceOwnerTypesClassMap = [];
|
||||
|
||||
/**
|
||||
* Array of supported resource owners.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private static array $resourceOwnerTypes = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if ([] === self::$resourceOwnerTypes) {
|
||||
self::loadResourceOwners();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getResourceOwnerTypesClassMap(): array
|
||||
{
|
||||
return self::$resourceOwnerTypesClassMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type (oauth1 or oauth2) of given resource owner.
|
||||
*/
|
||||
public static function getResourceOwnerType(string $resourceOwner): ?string
|
||||
{
|
||||
$resourceOwner = strtolower($resourceOwner);
|
||||
|
||||
return self::$resourceOwnerTypes[$resourceOwner] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that given resource owner is supported by this bundle.
|
||||
*/
|
||||
public static function isResourceOwnerSupported(string $resourceOwner): bool
|
||||
{
|
||||
return isset(self::$resourceOwnerTypes[strtolower($resourceOwner)]);
|
||||
}
|
||||
|
||||
public static function registerResourceOwner(string $resourceOwnerClass): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($resourceOwnerClass);
|
||||
if (!$reflection->implementsInterface(ResourceOwnerInterface::class)) {
|
||||
throw new \LogicException('Resource owner class should implement "ResourceOwnerInterface", or extended class "GenericOAuth1ResourceOwner"/"GenericOAuth2ResourceOwner".');
|
||||
}
|
||||
|
||||
$type = \defined("$resourceOwnerClass::TYPE") ? $resourceOwnerClass::TYPE : null;
|
||||
if (null === $type) {
|
||||
if (preg_match('~(?P<resource_owner>[^\\\\]+)ResourceOwner$~', $resourceOwnerClass, $match)) {
|
||||
$type = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $match['resource_owner']));
|
||||
} else {
|
||||
throw new \LogicException(sprintf('Resource owner class either should have "TYPE" const defined or end with "ResourceOwner" so that type can be calculated by converting its class name without suffix to "snake_case". Given class name is "%s"', $resourceOwnerClass));
|
||||
}
|
||||
}
|
||||
|
||||
$oAuth = 'unknown';
|
||||
if ($reflection->isSubclassOf(GenericOAuth2ResourceOwner::class)) {
|
||||
$oAuth = 'oauth2';
|
||||
} elseif ($reflection->isSubclassOf(GenericOAuth1ResourceOwner::class)) {
|
||||
$oAuth = 'oauth1';
|
||||
}
|
||||
|
||||
self::$resourceOwnerTypes[$type] = $oAuth;
|
||||
self::$resourceOwnerTypesClassMap[$type] = $resourceOwnerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the configuration tree builder.
|
||||
*/
|
||||
public function getConfigTreeBuilder(): TreeBuilder
|
||||
{
|
||||
$builder = new TreeBuilder('hwi_oauth');
|
||||
|
||||
/** @var ArrayNodeDefinition $rootNode */
|
||||
$rootNode = $builder->getRootNode();
|
||||
|
||||
$rootNode
|
||||
->fixXmlConfig('firewall_name')
|
||||
->children()
|
||||
->arrayNode('firewall_names')
|
||||
->setDeprecated(...$this->getDeprecationParams())
|
||||
->defaultValue([])
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->scalarNode('target_path_parameter')->defaultNull()->end()
|
||||
->arrayNode('target_path_domains_whitelist')
|
||||
->defaultValue([])
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->booleanNode('use_referer')->defaultFalse()->end()
|
||||
->booleanNode('failed_use_referer')->defaultFalse()->end()
|
||||
->scalarNode('failed_auth_path')->defaultValue('hwi_oauth_connect')->end()
|
||||
->scalarNode('grant_rule')
|
||||
->defaultValue('IS_AUTHENTICATED_REMEMBERED')
|
||||
->validate()
|
||||
->ifTrue(function ($role) {
|
||||
return !('IS_AUTHENTICATED_REMEMBERED' === $role || 'IS_AUTHENTICATED_FULLY' === $role);
|
||||
})
|
||||
->thenInvalid('Unknown grant role set "%s".')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
$this->addConnectConfiguration($rootNode);
|
||||
$this->addResourceOwnersConfiguration($rootNode);
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
private function addResourceOwnersConfiguration(ArrayNodeDefinition $node): void
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
$node
|
||||
->fixXmlConfig('resource_owner')
|
||||
->children()
|
||||
->arrayNode('resource_owners')
|
||||
->isRequired()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->ignoreExtraKeys()
|
||||
->children()
|
||||
->scalarNode('base_url')->end()
|
||||
->scalarNode('access_token_url')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('authorization_url')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('request_token_url')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('revoke_token_url')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('infos_url')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('client_id')->cannotBeEmpty()->end()
|
||||
->scalarNode('client_secret')->cannotBeEmpty()->end()
|
||||
->scalarNode('realm')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('scope')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('user_response_class')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('service')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('class')
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('type')
|
||||
// will be validated in ResourceOwnerCompilerPass, other apps can register own resource
|
||||
// owner maps later with tag hwi_oauth.resource_owner
|
||||
->validate()
|
||||
->ifEmpty()
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('use_authorization_to_get_token')
|
||||
->validate()
|
||||
->ifTrue(function ($v) {
|
||||
if (false === $v) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return empty($v);
|
||||
})
|
||||
->thenUnset()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('paths')
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('variable')
|
||||
->validate()
|
||||
->ifTrue(function ($v) {
|
||||
if (null === $v) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (\is_array($v)) {
|
||||
return 0 === \count($v);
|
||||
}
|
||||
|
||||
if (\is_string($v)) {
|
||||
return empty($v);
|
||||
}
|
||||
|
||||
return !is_numeric($v);
|
||||
})
|
||||
->thenInvalid('Path can be only string or array type.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('options')
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($c) {
|
||||
// skip if this contains a service
|
||||
if (isset($c['service'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// for each type at least these have to be set
|
||||
foreach (['client_id', 'client_secret'] as $child) {
|
||||
if (!isset($c[$child])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($c['type']) && !isset($c['class'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
->thenInvalid("You should set at least the 'type' or 'class' with 'client_id' and the 'client_secret' of a resource owner.")
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($c) {
|
||||
return isset($c['type'], $c['class']);
|
||||
})
|
||||
->then(function ($c) {
|
||||
trigger_deprecation('hwi/oauth-bundle', '2.0', 'No need to set both "type" and "class" for resource owner.');
|
||||
|
||||
return $c;
|
||||
})
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($c) {
|
||||
// Skip if this contains a service or a class
|
||||
if (isset($c['service']) || isset($c['class'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only validate the 'oauth2' and 'oauth1' type
|
||||
if ('oauth2' !== $c['type'] && 'oauth1' !== $c['type']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$children = ['authorization_url', 'access_token_url', 'request_token_url', 'infos_url'];
|
||||
foreach ($children as $child) {
|
||||
// This option exists only for OAuth1.0a
|
||||
if ('request_token_url' === $child && 'oauth2' === $c['type']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($c[$child])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
->thenInvalid("All parameters are mandatory for types 'oauth2' and 'oauth1'. Check if you're missing one of: 'access_token_url', 'authorization_url', 'infos_url' and 'request_token_url' for 'oauth1'.")
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($c) {
|
||||
// skip if this contains a service
|
||||
if (isset($c['service']) || isset($c['class'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only validate the 'oauth2' and 'oauth1' type
|
||||
if ('oauth2' !== $c['type'] && 'oauth1' !== $c['type']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// one of this two options must be set
|
||||
if (0 === \count($c['paths'])) {
|
||||
return !isset($c['user_response_class']);
|
||||
}
|
||||
|
||||
foreach (['identifier', 'nickname', 'realname'] as $child) {
|
||||
if (!isset($c['paths'][$child])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
->thenInvalid("At least the 'identifier', 'nickname' and 'realname' paths should be configured for 'oauth2' and 'oauth1' types.")
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($c) {
|
||||
if (isset($c['service'])) {
|
||||
// ignore paths & options if none were set
|
||||
return 0 !== \count($c['paths']) || 0 !== \count($c['options']) || 3 < \count($c);
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
->thenInvalid("If you're setting a 'service', no other arguments should be set.")
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($c) {
|
||||
return isset($c['class']);
|
||||
})
|
||||
->then(function ($c) {
|
||||
self::registerResourceOwner($c['class']);
|
||||
|
||||
return $c;
|
||||
})
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
private function addConnectConfiguration(ArrayNodeDefinition $node): void
|
||||
{
|
||||
$node
|
||||
->children()
|
||||
->arrayNode('connect')
|
||||
->children()
|
||||
->booleanNode('confirmation')->defaultTrue()->end()
|
||||
->scalarNode('account_connector')->cannotBeEmpty()->end()
|
||||
->scalarNode('registration_form_handler')->cannotBeEmpty()->end()
|
||||
->scalarNode('registration_form')->cannotBeEmpty()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
private static function loadResourceOwners(): void
|
||||
{
|
||||
$files = (new Finder())
|
||||
->in(__DIR__.'/../OAuth/ResourceOwner')
|
||||
->name('~^(.+)ResourceOwner\.php$~')
|
||||
->files();
|
||||
|
||||
foreach ($files as $f) {
|
||||
if (!str_contains($f->getFilename(), 'ResourceOwner')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip known abstract classes
|
||||
if (\in_array($f->getFilename(), ['AbstractResourceOwner.php', 'GenericOAuth1ResourceOwner.php', 'GenericOAuth2ResourceOwner.php'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::registerResourceOwner('HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\\'.str_replace('.php', '', $f->getFilename()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct deprecation params as an array for setDeprecated().
|
||||
*
|
||||
* symfony/config v5.1 introduces a deprecation notice when calling
|
||||
* setDeprecated() with less than 3 args and the getDeprecation() method was
|
||||
* introduced at the same time. By checking if getDeprecation() exists,
|
||||
* we can determine the correct param count to use when calling setDeprecated().
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function getDeprecationParams(): array
|
||||
{
|
||||
if (method_exists(BaseNode::class, 'getDeprecation')) {
|
||||
return [
|
||||
'hwi/oauth-bundle',
|
||||
'2.0',
|
||||
'option "%path%.%node%" is deprecated. Firewall names are collected automatically.',
|
||||
];
|
||||
}
|
||||
|
||||
return ['Since hwi/oauth-bundle 2.0: option "hwi_oauth.firewall_names" is deprecated. Firewall names are collected automatically.'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\DependencyInjection;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\Config\Definition\Processor;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
|
||||
/**
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class HWIOAuthExtension extends Extension
|
||||
{
|
||||
/**
|
||||
* @var \ArrayIterator<string, true>
|
||||
*/
|
||||
private \ArrayIterator $firewallNames;
|
||||
|
||||
private bool $refreshTokenListenerEnabled = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->firewallNames = new \ArrayIterator();
|
||||
}
|
||||
|
||||
public function getConfiguration(array $config, ContainerBuilder $container): Configuration
|
||||
{
|
||||
return new Configuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \RuntimeException
|
||||
* @throws InvalidConfigurationException
|
||||
* @throws BadMethodCallException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws OutOfBoundsException
|
||||
* @throws ServiceNotFoundException
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config/'));
|
||||
$loader->load('controller.php');
|
||||
$loader->load('oauth.php');
|
||||
$loader->load('resource_owners.php');
|
||||
$loader->load('twig.php');
|
||||
$loader->load('util.php');
|
||||
|
||||
$container->registerForAutoconfiguration(ResourceOwnerInterface::class)
|
||||
->addTag('hwi_oauth.resource_owner');
|
||||
|
||||
$processor = new Processor();
|
||||
$configuration = $this->getConfiguration($configs, $container);
|
||||
|
||||
$config = $processor->processConfiguration($configuration, $configs);
|
||||
|
||||
// set target path parameter
|
||||
$container->setParameter('hwi_oauth.target_path_parameter', $config['target_path_parameter']);
|
||||
|
||||
// set target path domains whitelist parameter
|
||||
$container->setParameter('hwi_oauth.target_path_domains_whitelist', $config['target_path_domains_whitelist']);
|
||||
|
||||
// set use referer parameter
|
||||
$container->setParameter('hwi_oauth.use_referer', $config['use_referer']);
|
||||
|
||||
// set failed use referer parameter
|
||||
$container->setParameter('hwi_oauth.failed_use_referer', $config['failed_use_referer']);
|
||||
|
||||
// set failed auth path
|
||||
$container->setParameter('hwi_oauth.failed_auth_path', $config['failed_auth_path']);
|
||||
|
||||
// set grant rule
|
||||
$container->setParameter('hwi_oauth.grant_rule', $config['grant_rule']);
|
||||
|
||||
// setup services for all configured resource owners
|
||||
$resourceOwners = [];
|
||||
$resourceOwnerReferenceMap = [];
|
||||
foreach ($config['resource_owners'] as $name => $options) {
|
||||
$resourceOwners[$name] = $name;
|
||||
$resourceOwnerReferenceMap[$name] = $this->createResourceOwnerService($container, $name, $options);
|
||||
|
||||
if (!$this->refreshTokenListenerEnabled) {
|
||||
$this->refreshTokenListenerEnabled = $options['options']['refresh_on_expire'] ?? false;
|
||||
}
|
||||
}
|
||||
$container->setParameter('hwi_oauth.resource_owners', $resourceOwners);
|
||||
$container->setAlias(
|
||||
'hwi_oauth.resource_owners.locator',
|
||||
(string) ServiceLocatorTagPass::register($container, $resourceOwnerReferenceMap)
|
||||
);
|
||||
|
||||
$this->createConnectIntegration($container, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a resource owner service.
|
||||
*
|
||||
* @param ContainerBuilder $container The container builder
|
||||
* @param string $name The name of the service
|
||||
* @param array $options Additional options of the service
|
||||
*
|
||||
* @throws InvalidConfigurationException
|
||||
* @throws BadMethodCallException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function createResourceOwnerService(ContainerBuilder $container, string $name, array $options): Reference
|
||||
{
|
||||
// alias services
|
||||
if (isset($options['service'])) {
|
||||
return new Reference($options['service']);
|
||||
}
|
||||
|
||||
// handle external resource owners with given class
|
||||
if (isset($options['class'])) {
|
||||
if (!is_subclass_of($options['class'], ResourceOwnerInterface::class, true)) {
|
||||
throw new InvalidConfigurationException(sprintf('Class "%s" must implement interface "HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface".', $options['class']));
|
||||
}
|
||||
|
||||
$definition = new Definition($options['class']);
|
||||
} else {
|
||||
$definition = new Definition("%hwi_oauth.resource_owner.{$options['type']}.class%");
|
||||
}
|
||||
unset($options['class'], $options['type']);
|
||||
|
||||
$definition->setArgument('$httpClient', new Reference('hwi_oauth.http_client'));
|
||||
$definition->setArgument('$httpUtils', new Reference('security.http_utils'));
|
||||
$definition->setArgument('$options', $options);
|
||||
$definition->setArgument('$name', $name);
|
||||
$definition->setArgument('$storage', new Reference('hwi_oauth.storage.session'));
|
||||
|
||||
$container->setDefinition('hwi_oauth.resource_owner.'.$name, $definition);
|
||||
|
||||
return new Reference('hwi_oauth.resource_owner.'.$name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAlias(): string
|
||||
{
|
||||
return 'hwi_oauth';
|
||||
}
|
||||
|
||||
public function getFirewallNames(): \ArrayIterator
|
||||
{
|
||||
return $this->firewallNames;
|
||||
}
|
||||
|
||||
public function isRefreshTokenListenerEnabled(): bool
|
||||
{
|
||||
return $this->refreshTokenListenerEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check of the connect controllers etc should be enabled.
|
||||
*
|
||||
* @throws BadMethodCallException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function createConnectIntegration(ContainerBuilder $container, array $config): void
|
||||
{
|
||||
$container->setParameter('hwi_oauth.connect', isset($config['connect']));
|
||||
$container->setParameter('hwi_oauth.connect.confirmation', $config['connect']['confirmation'] ?? false);
|
||||
$container->setParameter('hwi_oauth.connect.registration_form', $config['connect']['registration_form'] ?? null);
|
||||
|
||||
if (isset($config['connect']['account_connector'])) {
|
||||
$container->setAlias('hwi_oauth.account.connector', new Alias($config['connect']['account_connector'], true));
|
||||
}
|
||||
|
||||
if (isset($config['connect']['registration_form_handler'])) {
|
||||
$container->setAlias('hwi_oauth.registration.form.handler', new Alias($config['connect']['registration_form_handler'], true));
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+328
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Security\Http\Authenticator\OAuthAuthenticator;
|
||||
use HWI\Bundle\OAuthBundle\Security\Http\Firewall\RefreshAccessTokenListener;
|
||||
use HWI\Bundle\OAuthBundle\Security\Http\Firewall\RefreshAccessTokenListenerOld;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface;
|
||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Parameter;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
* @author Vadim Borodavko <vadim.borodavko@gmail.com>
|
||||
*/
|
||||
final class OAuthAuthenticatorFactory extends AbstractFactory implements AuthenticatorFactoryInterface, FirewallListenerFactoryInterface
|
||||
{
|
||||
public function __construct(private \ArrayIterator $firewallNames)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrayNodeDefinition $node
|
||||
*/
|
||||
public function addConfiguration(NodeDefinition $node): void
|
||||
{
|
||||
parent::addConfiguration($node);
|
||||
|
||||
$builder = $node->children();
|
||||
$builder
|
||||
->scalarNode('login_path')->cannotBeEmpty()->isRequired()->end()
|
||||
;
|
||||
|
||||
$this->addOAuthProviderConfiguration($node);
|
||||
$this->addResourceOwnersConfiguration($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAuthenticator(
|
||||
ContainerBuilder $container,
|
||||
string $firewallName,
|
||||
array $config,
|
||||
string $userProviderId
|
||||
): string {
|
||||
$authenticatorId = 'security.authenticator.oauth.'.$firewallName;
|
||||
|
||||
$this->createResourceOwnerMap($container, $firewallName, $config);
|
||||
|
||||
$container
|
||||
->register($authenticatorId, OAuthAuthenticator::class)
|
||||
->addArgument(new Reference('security.http_utils'))
|
||||
->addArgument(
|
||||
$this->createOAuthAwareUserProvider($container, $firewallName, $config['oauth_user_provider'])
|
||||
)
|
||||
->addArgument($this->createResourceOwnerMapReference($firewallName))
|
||||
->addArgument($config['resource_owners'])
|
||||
->addArgument(new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))
|
||||
->addArgument(new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))
|
||||
->addArgument(new Reference('http_kernel'))
|
||||
->addArgument(array_intersect_key($config, $this->options))
|
||||
;
|
||||
|
||||
$this->firewallNames[$firewallName] = true;
|
||||
|
||||
return $authenticatorId;
|
||||
}
|
||||
|
||||
public function createListeners(ContainerBuilder $container, string $firewallName, array $config): array
|
||||
{
|
||||
$authenticatorId = 'security.authenticator.oauth.'.$firewallName;
|
||||
$providerId = 'hwi_oauth.authentication.provider.oauth.'.$firewallName;
|
||||
|
||||
$listenerId = 'hwi_oauth.context_listener.token_refresher.'.$firewallName;
|
||||
|
||||
$listenerDef = $container->setDefinition($listenerId, new ChildDefinition('hwi_oauth.context_listener.abstract_token_refresher'));
|
||||
|
||||
$listenerDef->addMethodCall('setResourceOwnerMap', [$this->createResourceOwnerMapReference($firewallName)]);
|
||||
|
||||
if ($container->hasDefinition($authenticatorId)) {
|
||||
// new auth manager
|
||||
$listenerDef
|
||||
->setClass(RefreshAccessTokenListener::class)
|
||||
->replaceArgument(0, new Reference($authenticatorId));
|
||||
} else {
|
||||
// old auth manager
|
||||
$listenerDef
|
||||
->setClass(RefreshAccessTokenListenerOld::class)
|
||||
->replaceArgument(0, new Reference($providerId));
|
||||
}
|
||||
|
||||
return [$listenerId];
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'oauth';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPosition(): string
|
||||
{
|
||||
return 'http';
|
||||
}
|
||||
|
||||
public function getFirewallNames(): \ArrayIterator
|
||||
{
|
||||
return $this->firewallNames;
|
||||
}
|
||||
|
||||
protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config): string
|
||||
{
|
||||
$id = $this->getFailureHandlerId($id);
|
||||
$options = array_intersect_key($config, $this->defaultFailureHandlerOptions);
|
||||
|
||||
$failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.custom_failure_handler'));
|
||||
$failureHandler->replaceArgument(0, new ChildDefinition('hwi_oauth.authentication.failure_handler'));
|
||||
$failureHandler->replaceArgument(1, $options);
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
|
||||
{
|
||||
$providerId = 'hwi_oauth.authentication.provider.oauth.'.$id;
|
||||
|
||||
$this->createResourceOwnerMap($container, $id, $config);
|
||||
|
||||
$container
|
||||
->setDefinition($providerId, new ChildDefinition('hwi_oauth.authentication.provider.oauth'))
|
||||
->addArgument($this->createOAuthAwareUserProvider($container, $id, $config['oauth_user_provider']))
|
||||
->addArgument($this->createResourceOwnerMapReference($id))
|
||||
->addArgument(new Reference('hwi_oauth.user_checker'))
|
||||
->addArgument(new Reference('security.token_storage'))
|
||||
;
|
||||
|
||||
$this->firewallNames[$id] = true;
|
||||
|
||||
return $providerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntryPoint($container, $id, $config, ?string $defaultEntryPointId): ?string
|
||||
{
|
||||
$entryPointId = 'hwi_oauth.authentication.entry_point.oauth.'.$id;
|
||||
|
||||
$container
|
||||
->setDefinition($entryPointId, new ChildDefinition('hwi_oauth.authentication.entry_point.oauth'))
|
||||
->addArgument($config['login_path'])
|
||||
->addArgument($config['use_forward'])
|
||||
;
|
||||
|
||||
return $entryPointId;
|
||||
}
|
||||
|
||||
protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider): string
|
||||
{
|
||||
// @phpstan-ignore-next-line Symfony <5.4 BC layer
|
||||
$listenerId = parent::createListener($container, $id, $config, $userProvider);
|
||||
|
||||
$checkPaths = $config['resource_owners'];
|
||||
|
||||
$container
|
||||
->getDefinition($listenerId)
|
||||
->addMethodCall('setResourceOwnerMap', [$this->createResourceOwnerMapReference($id)])
|
||||
->addMethodCall('setCheckPaths', [$checkPaths])
|
||||
;
|
||||
|
||||
return $listenerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getListenerId(): string
|
||||
{
|
||||
return 'hwi_oauth.authentication.listener.oauth';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the resource owner map.
|
||||
*/
|
||||
private function createResourceOwnerMapReference(string $firewallName): Reference
|
||||
{
|
||||
return new Reference('hwi_oauth.resource_ownermap.'.$firewallName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a resource owner map for the given configuration.
|
||||
*/
|
||||
private function createResourceOwnerMap(ContainerBuilder $container, string $firewallName, array $config): void
|
||||
{
|
||||
$resourceOwnersMap = [];
|
||||
foreach ($config['resource_owners'] as $name => $checkPath) {
|
||||
$resourceOwnersMap[$name] = $checkPath;
|
||||
}
|
||||
$container->setParameter('hwi_oauth.resource_ownermap.configured.'.$firewallName, $resourceOwnersMap);
|
||||
|
||||
$container
|
||||
->setDefinition(
|
||||
$this->createResourceOwnerMapReference($firewallName),
|
||||
new ChildDefinition('hwi_oauth.abstract_resource_ownermap')
|
||||
)
|
||||
->replaceArgument('$resourceOwners', new Parameter('hwi_oauth.resource_ownermap.configured.'.$firewallName))
|
||||
->setPublic(true)
|
||||
;
|
||||
}
|
||||
|
||||
private function createOAuthAwareUserProvider(ContainerBuilder $container, $id, $config): Reference
|
||||
{
|
||||
$serviceId = 'hwi_oauth.user.provider.entity.'.$id;
|
||||
|
||||
// todo: move this to factories?
|
||||
switch (key($config)) {
|
||||
case 'oauth':
|
||||
$container
|
||||
->setDefinition($serviceId, new ChildDefinition('hwi_oauth.user.provider'))
|
||||
;
|
||||
break;
|
||||
case 'orm':
|
||||
$container
|
||||
->setDefinition($serviceId, new ChildDefinition('hwi_oauth.user.provider.entity'))
|
||||
->addArgument($config['orm']['class'])
|
||||
->addArgument($config['orm']['properties'])
|
||||
->addArgument($config['orm']['manager_name'])
|
||||
;
|
||||
break;
|
||||
case 'service':
|
||||
$container
|
||||
->setAlias($serviceId, $config['service']);
|
||||
break;
|
||||
}
|
||||
|
||||
return new Reference($serviceId);
|
||||
}
|
||||
|
||||
private function addOAuthProviderConfiguration(ArrayNodeDefinition $node): void
|
||||
{
|
||||
$builder = $node->children();
|
||||
$builder
|
||||
->arrayNode('oauth_user_provider')
|
||||
->isRequired()
|
||||
->children()
|
||||
->arrayNode('orm')
|
||||
->children()
|
||||
->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('manager_name')->defaultNull()->end()
|
||||
->arrayNode('properties')
|
||||
->isRequired()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('scalar')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('service')->cannotBeEmpty()->end()
|
||||
->scalarNode('oauth')->end()
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($c) {
|
||||
return 1 !== \count($c) || !\in_array(key($c), ['oauth', 'orm', 'service'], true);
|
||||
})
|
||||
->thenInvalid("You should configure (only) one of: 'oauth', 'orm', 'service'.")
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
private function addResourceOwnersConfiguration(ArrayNodeDefinition $node): void
|
||||
{
|
||||
$builder = $node->children();
|
||||
$builder
|
||||
->arrayNode('resource_owners')
|
||||
->isRequired()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('scalar')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($c) {
|
||||
$checkPaths = [];
|
||||
foreach ($c as $checkPath) {
|
||||
if (\in_array($checkPath, $checkPaths, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$checkPaths[] = $checkPath;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
->thenInvalid('Each resource owner should have a unique "check_path".')
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Event;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
abstract class AbstractEvent extends Event
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Event;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* @author Marek Štípek
|
||||
*/
|
||||
final class FilterUserResponseEvent extends UserEvent
|
||||
{
|
||||
private Response $response;
|
||||
|
||||
public function __construct(UserInterface $user, Request $request, Response $response)
|
||||
{
|
||||
parent::__construct($user, $request);
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function getResponse(): Response
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
public function setResponse(Response $response): void
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Event;
|
||||
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @author Marek Štípek
|
||||
*/
|
||||
final class FormEvent extends AbstractEvent
|
||||
{
|
||||
private FormInterface $form;
|
||||
private Request $request;
|
||||
private ?Response $response = null;
|
||||
|
||||
public function __construct(FormInterface $form, Request $request)
|
||||
{
|
||||
$this->form = $form;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function getForm(): FormInterface
|
||||
{
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
public function getRequest(): Request
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
public function setResponse(Response $response): void
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function getResponse(): ?Response
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Event;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @author Marek Štípek
|
||||
*/
|
||||
final class GetResponseUserEvent extends UserEvent
|
||||
{
|
||||
private ?Response $response = null;
|
||||
|
||||
public function setResponse(Response $response): void
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function getResponse(): ?Response
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Event;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* @author Marek Štípek
|
||||
*/
|
||||
class UserEvent extends AbstractEvent
|
||||
{
|
||||
/**
|
||||
* @var UserInterface
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
private $request;
|
||||
|
||||
public function __construct(UserInterface $user, Request $request)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserInterface
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Request
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\Form;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* RegistrationFormHandlerInterface.
|
||||
*
|
||||
* Interface for objects that are able to handle a form.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
interface RegistrationFormHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Processes the form for a given request.
|
||||
*
|
||||
* @return bool True if the processing was successful
|
||||
*/
|
||||
public function process(Request $request, FormInterface $form, UserResponseInterface $userInformation);
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\DependencyInjection\CompilerPass\EnableRefreshOAuthTokenListenerCompilerPass;
|
||||
use HWI\Bundle\OAuthBundle\DependencyInjection\CompilerPass\ResourceOwnerCompilerPass;
|
||||
use HWI\Bundle\OAuthBundle\DependencyInjection\Security\Factory\OAuthAuthenticatorFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
* @author Alexander <geoffrey.bachelet@gmail.com>
|
||||
*/
|
||||
class HWIOAuthBundle extends Bundle
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(ContainerBuilder $container): void
|
||||
{
|
||||
parent::build($container);
|
||||
|
||||
/** @var SecurityExtension $extension */
|
||||
$extension = $container->getExtension('security');
|
||||
|
||||
$firewallNames = $this->extension->getFirewallNames();
|
||||
|
||||
if (method_exists($extension, 'addAuthenticatorFactory')) {
|
||||
$extension->addAuthenticatorFactory(new OAuthAuthenticatorFactory($firewallNames));
|
||||
} elseif (method_exists($extension, 'addSecurityListenerFactory')) {
|
||||
// Symfony < 5.4 BC layer
|
||||
$extension->addSecurityListenerFactory(new OAuthAuthenticatorFactory($firewallNames));
|
||||
} else {
|
||||
throw new \RuntimeException('Unsupported Symfony Security component version');
|
||||
}
|
||||
|
||||
$container->addCompilerPass(new ResourceOwnerCompilerPass());
|
||||
$container->addCompilerPass(new EnableRefreshOAuthTokenListenerCompilerPass());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContainerExtension(): ?ExtensionInterface
|
||||
{
|
||||
// return the right extension instead of "auto-registering" it. Now the
|
||||
// alias can be hwi_oauth instead of hwi_o_auth.
|
||||
return $this->extension ?: $this->extension = $this->createContainerExtension();
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle;
|
||||
|
||||
/**
|
||||
* @author Marek Štípek
|
||||
*/
|
||||
final class HWIOAuthEvents
|
||||
{
|
||||
/**
|
||||
* @Event("HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent")
|
||||
*/
|
||||
public const REGISTRATION_INITIALIZE = 'hwi_oauth.registration.initialize';
|
||||
|
||||
/**
|
||||
* @Event("HWI\Bundle\OAuthBundle\Event\FormEvent")
|
||||
*/
|
||||
public const REGISTRATION_SUCCESS = 'hwi_oauth.registration.success';
|
||||
|
||||
/**
|
||||
* @Event("HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent")
|
||||
*/
|
||||
public const REGISTRATION_COMPLETED = 'hwi_oauth.registration.completed';
|
||||
|
||||
/**
|
||||
* @Event("HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent")
|
||||
*/
|
||||
public const CONNECT_INITIALIZE = 'hwi_oauth.connect.initialize';
|
||||
|
||||
/**
|
||||
* @Event("HWI\Bundle\OAuthBundle\Event\GetResponseUserEvent")
|
||||
*/
|
||||
public const CONNECT_CONFIRMED = 'hwi_oauth.connect.confirmed';
|
||||
|
||||
/**
|
||||
* @Event("HWI\Bundle\OAuthBundle\Event\FilterUserResponseEvent")
|
||||
*/
|
||||
public const CONNECT_COMPLETED = 'hwi_oauth.connect.completed';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\Exception;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
final class HttpTransportException extends AuthenticationException
|
||||
{
|
||||
private string $ownerName;
|
||||
|
||||
public function __construct(string $message, string $ownerName, int $code = 0, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->ownerName = $ownerName;
|
||||
}
|
||||
|
||||
public function getOwnerName(): string
|
||||
{
|
||||
return $this->ownerName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\Exception;
|
||||
|
||||
final class StateRetrievalException extends \InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* @param string $key The provided string key
|
||||
*/
|
||||
public static function forKey(string $key): self
|
||||
{
|
||||
return new static(sprintf('No value found in state for key [%s]', $key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\RequestDataStorage;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\RequestDataStorageInterface;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* Request token storage implementation using the Symfony session.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
* @author Francisco Facioni <fran6co@gmail.com>
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class SessionStorage implements RequestDataStorageInterface
|
||||
{
|
||||
private RequestStack $requestStack;
|
||||
|
||||
public function __construct(RequestStack $requestStack)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fetch(ResourceOwnerInterface $resourceOwner, $key, $type = 'token')
|
||||
{
|
||||
$key = $this->generateKey($resourceOwner, $key, $type);
|
||||
if (null === $data = $this->getSession()->get($key)) {
|
||||
throw new \InvalidArgumentException('No data available in storage.');
|
||||
}
|
||||
|
||||
// Request tokens are one time use only
|
||||
if (\in_array($type, ['token', 'csrf_state'], true)) {
|
||||
$this->getSession()->remove($key);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(ResourceOwnerInterface $resourceOwner, $value, $type = 'token')
|
||||
{
|
||||
if ('token' === $type) {
|
||||
if (!\is_array($value) || !isset($value['oauth_token'])) {
|
||||
throw new \InvalidArgumentException('Invalid request token.');
|
||||
}
|
||||
|
||||
$key = $this->generateKey($resourceOwner, $value['oauth_token'], 'token');
|
||||
} else {
|
||||
$key = $this->generateKey($resourceOwner, $this->getStorageKey($value), $type);
|
||||
}
|
||||
|
||||
$this->getSession()->set($key, $this->getStorageValue($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Key to for fetching or saving a token.
|
||||
*/
|
||||
private function generateKey(ResourceOwnerInterface $resourceOwner, string $key, string $type): string
|
||||
{
|
||||
return sprintf('_hwi_oauth.%s.%s.%s.%s', $resourceOwner->getName(), $resourceOwner->getOption('client_id'), $type, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|object $value
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
private function getStorageValue($value)
|
||||
{
|
||||
if (\is_object($value)) {
|
||||
$value = serialize($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|object $value
|
||||
*/
|
||||
private function getStorageKey($value): string
|
||||
{
|
||||
if (\is_array($value)) {
|
||||
$storageKey = reset($value);
|
||||
} elseif (\is_object($value)) {
|
||||
$storageKey = $value::class;
|
||||
} else {
|
||||
$storageKey = $value;
|
||||
}
|
||||
|
||||
return (string) $storageKey;
|
||||
}
|
||||
|
||||
private function getSession(): SessionInterface
|
||||
{
|
||||
if (method_exists($this->requestStack, 'getSession')) {
|
||||
return $this->requestStack->getSession();
|
||||
}
|
||||
|
||||
if ((null !== $request = $this->requestStack->getCurrentRequest()) && $request->hasSession()) {
|
||||
return $request->getSession();
|
||||
}
|
||||
|
||||
throw new \LogicException('There is currently no session available.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth;
|
||||
|
||||
/**
|
||||
* Interface for classes providing a request tokens storage.
|
||||
*
|
||||
* The storage is needed because the OAuth1.0a authentication flow requires
|
||||
* requests to be signed with the same values in consecutive requests.
|
||||
*
|
||||
* Additionally we require this to provide CSRF protection for all resource
|
||||
* owners.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
* @author Francisco Facioni <fran6co@gmail.com>
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
interface RequestDataStorageInterface
|
||||
{
|
||||
/**
|
||||
* Fetch a request data from the storage.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $type
|
||||
*/
|
||||
public function fetch(ResourceOwnerInterface $resourceOwner, $key, $type = 'token');
|
||||
|
||||
/**
|
||||
* Save a request data to the storage.
|
||||
*
|
||||
* @param array|string|object $value
|
||||
* @param string $type
|
||||
*/
|
||||
public function save(ResourceOwnerInterface $resourceOwner, $value, $type = 'token');
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\RequestDataStorageInterface;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Response\PathUserResponse;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\State\State;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\StateInterface;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\OptionsResolver\Exception\AccessException;
|
||||
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\HttpUtils;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
* @author Francisco Facioni <fran6co@gmail.com>
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
abstract class AbstractResourceOwner implements ResourceOwnerInterface
|
||||
{
|
||||
protected array $options = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<int, string>|string|null>
|
||||
*/
|
||||
protected array $paths = [];
|
||||
|
||||
protected HttpClientInterface $httpClient;
|
||||
protected HttpUtils $httpUtils;
|
||||
protected string $name;
|
||||
protected StateInterface $state;
|
||||
protected RequestDataStorageInterface $storage;
|
||||
private bool $stateLoaded = false;
|
||||
|
||||
/**
|
||||
* @param array $options Options for the resource owner
|
||||
* @param string $name Name for the resource owner
|
||||
*/
|
||||
public function __construct(
|
||||
HttpClientInterface $httpClient,
|
||||
HttpUtils $httpUtils,
|
||||
array $options,
|
||||
string $name,
|
||||
RequestDataStorageInterface $storage
|
||||
) {
|
||||
$this->httpClient = $httpClient;
|
||||
$this->httpUtils = $httpUtils;
|
||||
$this->name = $name;
|
||||
$this->storage = $storage;
|
||||
|
||||
if (!empty($options['paths'])) {
|
||||
$this->addPaths($options['paths']);
|
||||
}
|
||||
unset($options['paths']);
|
||||
|
||||
if (!empty($options['options'])) {
|
||||
$options += $options['options'];
|
||||
unset($options['options']);
|
||||
}
|
||||
unset($options['options']);
|
||||
|
||||
// Resolve merged options
|
||||
$resolver = new OptionsResolver();
|
||||
$this->configureOptions($resolver);
|
||||
$this->options = $resolver->resolve($options);
|
||||
|
||||
$this->state = new State($this->options['state'] ?: null);
|
||||
|
||||
$this->configure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives a chance for extending providers to customize stuff.
|
||||
*/
|
||||
public function configure()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOption($name)
|
||||
{
|
||||
if (!\array_key_exists($name, $this->options)) {
|
||||
throw new \InvalidArgumentException(sprintf('Unknown option "%s"', $name));
|
||||
}
|
||||
|
||||
return $this->options[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addPaths(array $paths)
|
||||
{
|
||||
$this->paths = array_merge($this->paths, $paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getState(): StateInterface
|
||||
{
|
||||
if ($this->stateLoaded) {
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
// lazy-loading for stored states
|
||||
try {
|
||||
$storedData = $this->storage->fetch($this, State::class, 'state');
|
||||
} catch (\Throwable $e) {
|
||||
$storedData = null;
|
||||
}
|
||||
if (null !== $storedData && false !== $storedState = unserialize($storedData)) {
|
||||
foreach ($storedState->getAll() as $key => $value) {
|
||||
$this->addStateParameter($key, $value);
|
||||
}
|
||||
}
|
||||
$this->stateLoaded = true;
|
||||
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addStateParameter(string $key, string $value): void
|
||||
{
|
||||
if (!$this->state->has($key)) {
|
||||
$this->state->add($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function storeState(?StateInterface $state = null): void
|
||||
{
|
||||
if (null === $state || 0 === \count($state->getAll())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->storage->save($this, $state, 'state');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an access token for a given code.
|
||||
*
|
||||
* @param HttpRequest $request The request object from where the code is going to extracted
|
||||
* @param mixed $redirectUri The uri to redirect the client back to
|
||||
* @param array $extraParameters An array of parameters to add to the url
|
||||
*
|
||||
* @return array array containing the access token and it's 'expires_in' value,
|
||||
* along with any other parameters returned from the authentication
|
||||
* provider
|
||||
*
|
||||
* @throws AuthenticationException If an OAuth error occurred or no access token is found
|
||||
* @throws HttpTransportException
|
||||
*/
|
||||
abstract public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = []);
|
||||
|
||||
/**
|
||||
* Refresh an access token using a refresh token.
|
||||
*
|
||||
* @param string $refreshToken Refresh token
|
||||
* @param array $extraParameters An array of parameters to add to the url
|
||||
*
|
||||
* @return array array containing the access token and it's 'expires_in' value,
|
||||
* along with any other parameters returned from the authentication
|
||||
* provider
|
||||
*
|
||||
* @throws AuthenticationException If an OAuth error occurred or no access token is found
|
||||
* @throws HttpTransportException
|
||||
*/
|
||||
public function refreshAccessToken($refreshToken, array $extraParameters = [])
|
||||
{
|
||||
throw new AuthenticationException('OAuth error: "Method unsupported."');
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke an OAuth access token or refresh token.
|
||||
*
|
||||
* @param string $token the token (access token or a refresh token) that should be revoked
|
||||
*
|
||||
* @return bool returns True if the revocation was successful, otherwise False
|
||||
*
|
||||
* @throws AuthenticationException If an OAuth error occurred
|
||||
* @throws HttpTransportException
|
||||
*/
|
||||
public function revokeToken($token)
|
||||
{
|
||||
throw new AuthenticationException('OAuth error: "Method unsupported."');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response object to return.
|
||||
*
|
||||
* @return UserResponseInterface
|
||||
*/
|
||||
protected function getUserResponse()
|
||||
{
|
||||
$response = new $this->options['user_response_class']();
|
||||
if ($response instanceof PathUserResponse) {
|
||||
$response->setPaths($this->paths);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeUrl($url, array $parameters = [])
|
||||
{
|
||||
$normalizedUrl = $url;
|
||||
if (!empty($parameters)) {
|
||||
$normalizedUrl .= (str_contains($url, '?') ? '&' : '?').http_build_query($parameters, '', '&');
|
||||
}
|
||||
|
||||
return $normalizedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an HTTP request.
|
||||
*
|
||||
* @param string $url The url to fetch
|
||||
* @param string|array $content The content of the request
|
||||
* @param array $headers The headers of the request
|
||||
* @param string $method The HTTP method to use
|
||||
*
|
||||
* @return ResponseInterface The response content
|
||||
*
|
||||
* @throws HttpTransportException
|
||||
*/
|
||||
protected function httpRequest($url, $content = null, array $headers = [], $method = null)
|
||||
{
|
||||
if (null === $method) {
|
||||
$method = null === $content || '' === $content ? 'GET' : 'POST';
|
||||
}
|
||||
|
||||
$options = ['headers' => $headers];
|
||||
$options['headers'] += ['User-Agent' => 'HWIOAuthBundle (https://github.com/hwi/HWIOAuthBundle)'];
|
||||
if (\is_string($content)) {
|
||||
if (!isset($options['headers']['Content-Length'])) {
|
||||
$options['headers'] += ['Content-Length' => (string) \strlen($content)];
|
||||
}
|
||||
}
|
||||
$options['body'] = $content;
|
||||
|
||||
try {
|
||||
return $this->httpClient->request(
|
||||
$method,
|
||||
$url,
|
||||
$options
|
||||
);
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getResponseContent(ResponseInterface $rawResponse): array
|
||||
{
|
||||
try {
|
||||
return $rawResponse->toArray(false);
|
||||
} catch (JsonException $e) {
|
||||
parse_str($rawResponse->getContent(false), $response);
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws HttpTransportException
|
||||
*/
|
||||
abstract protected function doGetTokenRequest($url, array $parameters = []);
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws HttpTransportException
|
||||
*/
|
||||
abstract protected function doGetUserInformationRequest($url, array $parameters = []);
|
||||
|
||||
/**
|
||||
* Configure the option resolver.
|
||||
*
|
||||
* @throws AccessException
|
||||
* @throws UndefinedOptionsException
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setRequired([
|
||||
'client_id',
|
||||
'client_secret',
|
||||
'authorization_url',
|
||||
'access_token_url',
|
||||
'infos_url',
|
||||
]);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'scope' => null,
|
||||
'state' => null,
|
||||
'csrf' => false,
|
||||
'user_response_class' => PathUserResponse::class,
|
||||
'auth_with_one_url' => false,
|
||||
]);
|
||||
|
||||
$resolver->setAllowedValues('csrf', [true, false]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Fabian Kiss <fabian.kiss@ymc.ch>
|
||||
*/
|
||||
final class AmazonResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'amazon';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'user_id',
|
||||
'nickname' => 'name',
|
||||
'realname' => 'name',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://www.amazon.com/ap/oa',
|
||||
'access_token_url' => 'https://api.amazon.com/auth/o2/token',
|
||||
'infos_url' => 'https://api.amazon.com/user/profile',
|
||||
|
||||
'scope' => 'profile',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use HWI\Bundle\OAuthBundle\Security\OAuthErrorHandler;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
* @author Josip Letica <leticajosip.09@gmail.com>
|
||||
* @author Sébastien Alfaiate <seb33300@hotmail.com>
|
||||
*/
|
||||
final class AppleResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'apple';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'sub',
|
||||
'firstname' => 'firstName',
|
||||
'lastname' => 'lastName',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
return parent::getAuthorizationUrl($redirectUri, array_merge([
|
||||
'response_mode' => $this->options['response_mode'],
|
||||
], $extraParameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
if (!isset($accessToken['id_token'])) {
|
||||
throw new \InvalidArgumentException('Undefined index id_token');
|
||||
}
|
||||
|
||||
$jwt = self::jwtDecode($accessToken['id_token']);
|
||||
$data = $jwt ? json_decode(base64_decode($jwt), true) : [];
|
||||
|
||||
if (isset($accessToken['firstName'], $accessToken['lastName'])) {
|
||||
$data['firstName'] = $accessToken['firstName'];
|
||||
$data['lastName'] = $accessToken['lastName'];
|
||||
}
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData(json_encode($data));
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
|
||||
{
|
||||
OAuthErrorHandler::handleOAuthError($request);
|
||||
|
||||
$parameters = array_merge([
|
||||
'code' => $request->request->get('code'),
|
||||
'grant_type' => 'authorization_code',
|
||||
'client_id' => $this->options['client_id'],
|
||||
'client_secret' => $this->getClientSecret(),
|
||||
'redirect_uri' => $redirectUri,
|
||||
], $extraParameters);
|
||||
|
||||
$response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
|
||||
$response = $this->getResponseContent($response);
|
||||
|
||||
$this->validateResponseContent($response);
|
||||
|
||||
$userInfo = $request->request->get('user');
|
||||
if (null !== $userInfo) {
|
||||
$userInfo = json_decode($userInfo, true, 512, \JSON_THROW_ON_ERROR);
|
||||
if (isset($userInfo['name'])) {
|
||||
$response['firstName'] = $userInfo['name']['firstName'];
|
||||
$response['lastName'] = $userInfo['name']['lastName'];
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function refreshAccessToken($refreshToken, array $extraParameters = [])
|
||||
{
|
||||
$parameters = [
|
||||
'client_id' => $this->options['client_id'],
|
||||
'client_secret' => $this->options['client_secret'],
|
||||
];
|
||||
|
||||
return parent::refreshAccessToken($refreshToken, array_merge($parameters, $extraParameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handles(Request $request)
|
||||
{
|
||||
return $request->request->has('code');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://appleid.apple.com/auth/authorize',
|
||||
'access_token_url' => 'https://appleid.apple.com/auth/token',
|
||||
'infos_url' => '',
|
||||
'use_commas_in_scope' => false,
|
||||
'display' => null,
|
||||
'scope' => 'name email',
|
||||
'appsecret_proof' => false,
|
||||
'response_mode' => 'form_post',
|
||||
'auth_key' => null,
|
||||
'key_id' => null,
|
||||
'team_id' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
private static function jwtDecode(string $idToken)
|
||||
{
|
||||
// from http://stackoverflow.com/a/28748285/624544
|
||||
[, $jwt] = explode('.', $idToken, 3);
|
||||
|
||||
// if the token was urlencoded, do some fixes to ensure that it is valid base64 encoded
|
||||
$jwt = str_replace(['-', '_'], ['+', '/'], $jwt);
|
||||
|
||||
// complete token if needed
|
||||
switch (\strlen($jwt) % 4) {
|
||||
case 0:
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
$jwt .= '=';
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid base64 format sent back');
|
||||
}
|
||||
|
||||
return $jwt;
|
||||
}
|
||||
|
||||
private function getClientSecret(): string
|
||||
{
|
||||
if ('auto' !== $this->options['client_secret']) {
|
||||
return $this->options['client_secret'];
|
||||
}
|
||||
|
||||
if (!isset($this->options['auth_key'], $this->options['key_id'], $this->options['team_id'])) {
|
||||
throw new \InvalidArgumentException('Options "auth_key", "key_id" and "team_id" must be defined to use automatic "client_secret" generation.');
|
||||
}
|
||||
|
||||
if (!class_exists(JWT::class)) {
|
||||
throw new \RuntimeException('PHP-JWT library is required to use automatic "client_secret" generation. Please try "composer require firebase/php-jwt".');
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'iss' => $this->options['team_id'],
|
||||
'iat' => time(),
|
||||
'exp' => time() + 600,
|
||||
'aud' => 'https://appleid.apple.com',
|
||||
'sub' => $this->options['client_id'],
|
||||
];
|
||||
|
||||
return JWT::encode($payload, $this->options['auth_key'], 'ES256', $this->options['key_id']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Guillaume Potier <guillaume@wisembly.com>
|
||||
*/
|
||||
final class AsanaResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'asana';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'data.id',
|
||||
'nickname' => 'data.name',
|
||||
'realname' => 'data.name',
|
||||
'email' => 'data.email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://app.asana.com/-/oauth_authorize',
|
||||
'access_token_url' => 'https://app.asana.com/-/oauth_token',
|
||||
'infos_url' => 'https://app.asana.com/api/1.0/users/me',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Hernan Rajchert <hrajchert@gmail.com>
|
||||
*/
|
||||
final class Auth0ResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'auth0';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'sub',
|
||||
'nickname' => 'nickname',
|
||||
'realname' => 'name',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'picture',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$auth0Client = base64_encode(json_encode([
|
||||
'name' => 'HWIOAuthBundle',
|
||||
'version' => 'unknown',
|
||||
'environment' => [
|
||||
'name' => 'PHP',
|
||||
'version' => \PHP_VERSION,
|
||||
],
|
||||
]));
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => '{base_url}/authorize?auth0Client='.$auth0Client,
|
||||
'access_token_url' => '{base_url}/oauth/token',
|
||||
'infos_url' => '{base_url}/userinfo',
|
||||
'auth0_client' => $auth0Client,
|
||||
]);
|
||||
|
||||
$resolver->setRequired([
|
||||
'base_url',
|
||||
]);
|
||||
|
||||
$normalizer = function (Options $options, $value) {
|
||||
return str_replace('{base_url}', $options['base_url'], $value);
|
||||
};
|
||||
|
||||
$resolver->setNormalizer('authorization_url', $normalizer);
|
||||
$resolver->setNormalizer('access_token_url', $normalizer);
|
||||
$resolver->setNormalizer('infos_url', $normalizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function httpRequest($url, $content = null, array $headers = [], $method = null)
|
||||
{
|
||||
if (isset($this->options['auth0_client'])) {
|
||||
$headers['Auth0-Client'] = $this->options['auth0_client'];
|
||||
}
|
||||
|
||||
return parent::httpRequest($url, $content, $headers, $method);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Baptiste Clavié <clavie.b@gmail.com>
|
||||
*/
|
||||
final class AzureResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'azure';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'sub',
|
||||
'nickname' => 'unique_name',
|
||||
'lastname' => 'family_name',
|
||||
'firstname' => 'given_name',
|
||||
'realname' => ['given_name', 'family_name'],
|
||||
'email' => ['upn', 'email'],
|
||||
'profilepicture' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure()
|
||||
{
|
||||
$this->options['access_token_url'] = sprintf($this->options['access_token_url'], $this->options['application']);
|
||||
$this->options['authorization_url'] = sprintf($this->options['authorization_url'], $this->options['application']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
// from http://stackoverflow.com/a/28748285/624544
|
||||
[, $jwt] = explode('.', \array_key_exists('id_token', $accessToken) ? $accessToken['id_token'] : $accessToken['access_token'], 3);
|
||||
|
||||
// if the token was urlencoded, do some fixes to ensure that it is valid base64 encoded
|
||||
$jwt = str_replace(['-', '_'], ['+', '/'], $jwt);
|
||||
|
||||
// complete token if needed
|
||||
switch (\strlen($jwt) % 4) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
$jwt .= '=';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid base64 format sent back');
|
||||
}
|
||||
|
||||
$response = parent::getUserInformation($accessToken, $extraParameters);
|
||||
$response->setData(base64_decode($jwt));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'infos_url' => 'https://graph.microsoft.com/v1.0/me',
|
||||
'authorization_url' => 'https://login.microsoftonline.com/%s/oauth2/v2.0/authorize',
|
||||
'access_token_url' => 'https://login.microsoftonline.com/%s/oauth2/v2.0/token',
|
||||
'application' => 'common',
|
||||
'api_version' => 'v1.0',
|
||||
'csrf' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author David Sanchez <david38sanchez@gmail.com>
|
||||
*/
|
||||
final class Bitbucket2ResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'bitbucket2';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'uuid',
|
||||
'nickname' => 'username',
|
||||
'email' => 'email',
|
||||
'realname' => 'display_name',
|
||||
'profilepicture' => 'links.avatar.href',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$response = parent::getUserInformation($accessToken, $extraParameters);
|
||||
$responseData = $response->getData();
|
||||
|
||||
// fetch the email addresses linked to the account
|
||||
if (empty($responseData['email'])) {
|
||||
$content = $this->httpRequest($this->normalizeUrl($this->options['emails_url']), null, ['Authorization' => 'Bearer '.$accessToken['access_token']]);
|
||||
foreach ($this->getResponseContent($content)['values'] as $email) {
|
||||
// we only need the primary email address
|
||||
if (true === $email['is_primary']) {
|
||||
$responseData['email'] = $email['email'];
|
||||
}
|
||||
}
|
||||
|
||||
$response->setData($responseData);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://bitbucket.org/site/oauth2/authorize',
|
||||
'access_token_url' => 'https://bitbucket.org/site/oauth2/access_token',
|
||||
'infos_url' => 'https://api.bitbucket.org/2.0/user',
|
||||
'emails_url' => 'https://api.bitbucket.org/2.0/user/emails',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class BitbucketResourceOwner extends GenericOAuth1ResourceOwner
|
||||
{
|
||||
public const TYPE = 'bitbucket';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'user.username',
|
||||
'nickname' => 'user.username',
|
||||
'realname' => 'user.display_name',
|
||||
'profilepicture' => 'user.avatar',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://bitbucket.org/api/1.0/oauth/authenticate',
|
||||
'request_token_url' => 'https://bitbucket.org/api/1.0/oauth/request_token',
|
||||
'access_token_url' => 'https://bitbucket.org/api/1.0/oauth/access_token',
|
||||
'infos_url' => 'https://bitbucket.org/api/1.0/user',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class BitlyResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'bitly';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'data.login',
|
||||
'nickname' => 'data.display_name',
|
||||
'realname' => 'data.full_name',
|
||||
'profilepicture' => 'data.profile_image',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'use_bearer_authorization' => false,
|
||||
'authorization_url' => 'https://bitly.com/oauth/authorize',
|
||||
'access_token_url' => 'https://api-ssl.bitly.com/oauth/access_token',
|
||||
'infos_url' => 'https://api-ssl.bitly.com/v3/user/info?format=json',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class BoxResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'box';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'name',
|
||||
'realname' => 'name',
|
||||
'email' => 'login',
|
||||
'profilepicture' => 'avatar_url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokeToken($token)
|
||||
{
|
||||
$parameters = [
|
||||
'client_id' => $this->options['client_id'],
|
||||
'client_secret' => $this->options['client_secret'],
|
||||
'token' => $token,
|
||||
];
|
||||
|
||||
$response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url']), $parameters, [], 'POST');
|
||||
|
||||
return 200 === $response->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://www.box.com/api/oauth2/authorize',
|
||||
'access_token_url' => 'https://www.box.com/api/oauth2/token',
|
||||
'revoke_token_url' => 'https://www.box.com/api/oauth2/revoke',
|
||||
'infos_url' => 'https://api.box.com/2.0/users/me',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author João Paulo Cercal <sistemas@cekurte.com>
|
||||
*/
|
||||
final class BufferAppResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'bufferapp';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'id',
|
||||
'realname' => 'id',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://bufferapp.com/oauth2/authorize',
|
||||
'access_token_url' => 'https://api.bufferapp.com/1/oauth2/token.json',
|
||||
'infos_url' => 'https://api.bufferapp.com/1/user.json',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Matt Farmer <work@mattfarmer.net>
|
||||
*/
|
||||
final class CleverResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'clever';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'data.id',
|
||||
'email' => 'data.email',
|
||||
'firstname' => 'data.name.first',
|
||||
'lastname' => 'data.name.last',
|
||||
'realname' => [
|
||||
'data.name.first',
|
||||
'data.name.middle',
|
||||
'data.name.last',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://clever.com/oauth/authorize',
|
||||
'access_token_url' => 'https://clever.com/oauth/tokens',
|
||||
'infos_url' => 'https://api.clever.com/me',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetTokenRequest($url, array $parameters = [])
|
||||
{
|
||||
$authPreHash = $this->options['client_id'].':'.$this->options['client_secret'];
|
||||
|
||||
return $this->httpRequest(
|
||||
$url,
|
||||
http_build_query($parameters, '', '&'),
|
||||
[
|
||||
'Authorization' => 'Basic '.base64_encode($authPreHash),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class DailymotionResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'dailymotion';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'screenname',
|
||||
'realname' => 'fullname', // requires 'userinfo' scope
|
||||
'email' => 'email', // requires 'email' scope
|
||||
'profilepicture' => 'avatar_medium_url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
return parent::getAuthorizationUrl($redirectUri, array_merge(['display' => $this->options['display']], $extraParameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://api.dailymotion.com/oauth/authorize',
|
||||
'access_token_url' => 'https://api.dailymotion.com/oauth/token',
|
||||
'infos_url' => 'https://api.dailymotion.com/me',
|
||||
|
||||
'display' => null,
|
||||
]);
|
||||
|
||||
// @link http://www.dailymotion.com/doc/api/authentication.html#dialog-form-factors
|
||||
$resolver->setAllowedValues('display', ['page', 'popup', 'mobile', null]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Kieu Anh Tuan <passkey1510@gmail.com>
|
||||
*/
|
||||
final class DeezerResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'deezer';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'name',
|
||||
'realname' => 'firstname',
|
||||
'email' => 'email',
|
||||
'firstname' => 'firstname',
|
||||
'lastname' => 'lastname',
|
||||
'profilepicture' => 'picture',
|
||||
'gender' => 'gender',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://connect.deezer.com/oauth/auth.php',
|
||||
'access_token_url' => 'https://connect.deezer.com/oauth/access_token.php',
|
||||
'infos_url' => 'https://api.deezer.com/user/me',
|
||||
'use_bearer_authorization' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class DeviantartResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'deviantart';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'username',
|
||||
'nickname' => 'username',
|
||||
'profilepicture' => 'usericonurl',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://www.deviantart.com/oauth2/draft15/authorize',
|
||||
'access_token_url' => 'https://www.deviantart.com/oauth2/draft15/token',
|
||||
'infos_url' => 'https://www.deviantart.com/api/draft15/user/whoami',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class DiscogsResourceOwner extends GenericOAuth1ResourceOwner
|
||||
{
|
||||
public const TYPE = 'discogs';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'username',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://www.discogs.com/oauth/authorize',
|
||||
'request_token_url' => 'https://api.discogs.com/oauth/request_token',
|
||||
'access_token_url' => 'https://api.discogs.com/oauth/access_token',
|
||||
'infos_url' => 'https://api.discogs.com/oauth/identity',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Alexander Müller <amr@kapthon.com>
|
||||
*/
|
||||
final class DisqusResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'disqus';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'response.id',
|
||||
'nickname' => 'response.username',
|
||||
'realname' => 'response.name',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetUserInformationRequest($url, array $parameters = [])
|
||||
{
|
||||
// Disqus requires api key and secret for user information requests
|
||||
$url = $this->normalizeUrl($url, [
|
||||
'api_key' => $this->options['client_id'],
|
||||
'api_secret' => $this->options['client_secret'],
|
||||
]);
|
||||
|
||||
return parent::doGetUserInformationRequest($url, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://disqus.com/api/oauth/2.0/authorize/',
|
||||
'access_token_url' => 'https://disqus.com/api/oauth/2.0/access_token/',
|
||||
'infos_url' => 'https://disqus.com/api/3.0/users/details.json',
|
||||
|
||||
'scope' => 'read',
|
||||
|
||||
'use_commas_in_scope' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Jamie Sutherland<me@jamiesutherland.com>
|
||||
*/
|
||||
final class DropboxResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'dropbox';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'account_id',
|
||||
'nickname' => 'email',
|
||||
'realname' => 'email',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* Dropbox API v2 requires a POST request to simply get user info!
|
||||
*
|
||||
* @return UserResponseInterface
|
||||
*/
|
||||
public function getUserInformation(array $accessToken,
|
||||
array $extraParameters = []
|
||||
) {
|
||||
if ($this->options['use_bearer_authorization']) {
|
||||
$content = $this->httpRequest(
|
||||
$this->normalizeUrl($this->options['infos_url'], $extraParameters),
|
||||
'null',
|
||||
[
|
||||
'Authorization' => 'Bearer '.$accessToken['access_token'],
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json; charset=utf-8',
|
||||
], 'POST');
|
||||
} else {
|
||||
$content = $this->doGetUserInformationRequest(
|
||||
$this->normalizeUrl(
|
||||
$this->options['infos_url'],
|
||||
array_merge([$this->options['attr_name'] => $accessToken['access_token']], $extraParameters)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->toArray(false));
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://www.dropbox.com/oauth2/authorize',
|
||||
'access_token_url' => 'https://api.dropbox.com/oauth2/token',
|
||||
'infos_url' => 'https://api.dropboxapi.com/2/users/get_current_account',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Ivan Stankovic <ivan.stankovic@webstorm.rs>
|
||||
*/
|
||||
final class EveOnlineResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'eveonline';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'CharacterID',
|
||||
'nickname' => 'CharacterName',
|
||||
'realname' => 'CharacterName',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://login.eveonline.com/oauth/authorize',
|
||||
'access_token_url' => 'https://login.eveonline.com/oauth/token',
|
||||
'infos_url' => 'https://login.eveonline.com/oauth/verify',
|
||||
'use_commas_in_scope' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class EventbriteResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'eventbrite';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'user.user_id',
|
||||
'nickname' => 'user.first_name',
|
||||
'firstname' => 'user.first_name',
|
||||
'lastname' => 'user.last_name',
|
||||
'realname' => ['user.first_name', 'user.last_name'],
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetTokenRequest($url, array $parameters = [])
|
||||
{
|
||||
return $this->httpRequest($url, http_build_query($parameters, '', '&'), [], 'POST');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://www.eventbrite.com/oauth/authorize',
|
||||
'access_token_url' => 'https://www.eventbrite.com/oauth/token',
|
||||
'infos_url' => 'https://www.eventbrite.com/json/user_get',
|
||||
|
||||
'use_bearer_authorization' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
*/
|
||||
final class FacebookResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'facebook';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'name',
|
||||
'firstname' => 'first_name',
|
||||
'lastname' => 'last_name',
|
||||
'realname' => 'name',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'picture.data.url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
if ($this->options['appsecret_proof']) {
|
||||
$extraParameters['appsecret_proof'] = hash_hmac('sha256', $accessToken['access_token'], $this->options['client_secret']);
|
||||
}
|
||||
|
||||
return parent::getUserInformation($accessToken, $extraParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$extraOptions = [];
|
||||
if (isset($this->options['display'])) {
|
||||
$extraOptions['display'] = $this->options['display'];
|
||||
}
|
||||
|
||||
if (isset($this->options['auth_type'])) {
|
||||
$extraOptions['auth_type'] = $this->options['auth_type'];
|
||||
}
|
||||
|
||||
return parent::getAuthorizationUrl($redirectUri, array_merge($extraOptions, $extraParameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$parameters = [];
|
||||
if ($request->query->has('fb_source')) {
|
||||
$parameters['fb_source'] = $request->query->get('fb_source');
|
||||
}
|
||||
|
||||
if ($request->query->has('fb_appcenter')) {
|
||||
$parameters['fb_appcenter'] = $request->query->get('fb_appcenter');
|
||||
}
|
||||
|
||||
return parent::getAccessToken($request, $this->normalizeUrl($redirectUri, $parameters), $extraParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokeToken($token)
|
||||
{
|
||||
$parameters = [
|
||||
'client_id' => $this->options['client_id'],
|
||||
'client_secret' => $this->options['client_secret'],
|
||||
];
|
||||
|
||||
$response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url'], ['access_token' => $token]), $parameters, [], 'DELETE');
|
||||
|
||||
return 200 === $response->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://www.facebook.com/v19.0/dialog/oauth',
|
||||
'access_token_url' => 'https://graph.facebook.com/v19.0/oauth/access_token',
|
||||
'revoke_token_url' => 'https://graph.facebook.com/v19.0/me/permissions',
|
||||
'infos_url' => 'https://graph.facebook.com/v19.0/me?fields=id,first_name,last_name,name,email,picture.type(large)',
|
||||
'use_commas_in_scope' => true,
|
||||
'display' => null,
|
||||
'auth_type' => null,
|
||||
'appsecret_proof' => false,
|
||||
]);
|
||||
|
||||
$resolver
|
||||
->setAllowedValues('display', ['page', 'popup', 'touch', null]) // @link https://developers.facebook.com/docs/reference/dialogs/#display
|
||||
->setAllowedValues('auth_type', ['rerequest', null]) // @link https://developers.facebook.com/docs/reference/javascript/FB.login/
|
||||
->setAllowedTypes('appsecret_proof', 'bool') // @link https://developers.facebook.com/docs/graph-api/securing-requests
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Resource owner for the fiware keyrock idm oauth 2.0 service
|
||||
* more infos at https://github.com/ging/fi-ware-idm/wiki/Using-the-FIWARE-LAB-instance.
|
||||
*
|
||||
* @author Christian Kaspar <christian@sponsoo.de>
|
||||
*/
|
||||
final class FiwareResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'fiware';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'nickName',
|
||||
'realname' => 'displayName',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$parameters = array_merge([
|
||||
'code' => $request->query->get('code'),
|
||||
'grant_type' => 'authorization_code',
|
||||
'redirect_uri' => $redirectUri,
|
||||
], $extraParameters);
|
||||
|
||||
$headers = [
|
||||
'Authorization' => 'Basic '.base64_encode($this->options['client_id'].':'.$this->options['client_secret']),
|
||||
];
|
||||
|
||||
$response = $this->httpRequest($this->options['access_token_url'], http_build_query($parameters, '', '&'), $headers, 'POST');
|
||||
$responseContent = $this->getResponseContent($response);
|
||||
|
||||
$this->validateResponseContent($responseContent);
|
||||
|
||||
return $responseContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
if ($this->options['use_bearer_authorization']) {
|
||||
$content = $this->httpRequest(
|
||||
$this->normalizeUrl(
|
||||
$this->options['infos_url'],
|
||||
['access_token' => $accessToken['access_token']]
|
||||
),
|
||||
null,
|
||||
['Authorization' => 'Bearer']
|
||||
);
|
||||
} else {
|
||||
$content = $this->doGetUserInformationRequest(
|
||||
$this->normalizeUrl(
|
||||
$this->options['infos_url'],
|
||||
[$this->options['attr_name'] => $accessToken['access_token']]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->getContent());
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => '{base_url}/oauth2/authorize',
|
||||
'access_token_url' => '{base_url}/oauth2/token',
|
||||
'revoke_token_url' => '{base_url}/oauth2/revoke',
|
||||
'infos_url' => '{base_url}/user',
|
||||
]);
|
||||
|
||||
$resolver->setRequired([
|
||||
'base_url',
|
||||
]);
|
||||
|
||||
$normalizer = function (Options $options, $value) {
|
||||
return str_replace('{base_url}', $options['base_url'], $value);
|
||||
};
|
||||
|
||||
$resolver
|
||||
->setNormalizer('authorization_url', $normalizer)
|
||||
->setNormalizer('access_token_url', $normalizer)
|
||||
->setNormalizer('revoke_token_url', $normalizer)
|
||||
->setNormalizer('infos_url', $normalizer)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Dmitri Lakachauskis <lakiboy83@gmail.com>
|
||||
*/
|
||||
final class FlickrResourceOwner extends GenericOAuth1ResourceOwner
|
||||
{
|
||||
public const TYPE = 'flickr';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'user_nsid',
|
||||
'nickname' => 'username',
|
||||
'realname' => 'fullname',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$token = $this->getRequestToken($redirectUri, $extraParameters);
|
||||
|
||||
return $this->normalizeUrl($this->options['authorization_url'], [
|
||||
'oauth_token' => $token['oauth_token'],
|
||||
'perms' => $this->options['perms'],
|
||||
'nojsoncallback' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($accessToken);
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'http://www.flickr.com/services/oauth/authorize',
|
||||
'request_token_url' => 'http://www.flickr.com/services/oauth/request_token',
|
||||
'access_token_url' => 'http://www.flickr.com/services/oauth/access_token',
|
||||
|
||||
// Flickr don't use `infos_url`
|
||||
'infos_url' => null,
|
||||
|
||||
'perms' => 'read',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class FoursquareResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'foursquare';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'response.user.id',
|
||||
'firstname' => 'response.user.firstName',
|
||||
'lastname' => 'response.user.lastName',
|
||||
'nickname' => 'response.user.firstName',
|
||||
'realname' => ['response.user.firstName', 'response.user.lastName'],
|
||||
'email' => 'response.user.contact.email',
|
||||
'profilepicture' => 'response.user.photo',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getResponseContent(ResponseInterface $rawResponse): array
|
||||
{
|
||||
$response = parent::getResponseContent($rawResponse);
|
||||
|
||||
// Foursquare use quite custom response structure in case of error
|
||||
if (isset($response['meta']['errorType'])) {
|
||||
// Prevent to mark deprecated calls as errors
|
||||
if (200 === (int) $response['meta']['code']) {
|
||||
$response['error'] = $response['meta']['errorType'];
|
||||
// Try to add some details of error if available
|
||||
if (isset($response['meta']['errorMessage'])) {
|
||||
$response['error'] .= ' '.$response['meta']['errorMessage'];
|
||||
} elseif (isset($response['meta']['errorDetail'])) {
|
||||
$response['error'] .= ' '.$response['meta']['errorDetail'];
|
||||
}
|
||||
}
|
||||
|
||||
unset($response['meta']);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetUserInformationRequest($url, array $parameters = [])
|
||||
{
|
||||
// Foursquare require to pass the 'v' ('version' = date in format 'YYYYMMDD') parameter when requesting API
|
||||
$url = $this->normalizeUrl($url, [
|
||||
'v' => $this->options['version'],
|
||||
]);
|
||||
|
||||
// Foursquare require to pass the OAuth token as 'oauth_token' instead of 'access_token'
|
||||
$url = str_replace('access_token', 'oauth_token', $url);
|
||||
|
||||
return $this->httpRequest($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://foursquare.com/oauth2/authenticate',
|
||||
'access_token_url' => 'https://foursquare.com/oauth2/access_token',
|
||||
'infos_url' => 'https://api.foursquare.com/v2/users/self',
|
||||
|
||||
// @link https://developer.foursquare.com/overview/versioning
|
||||
'version' => '20121206',
|
||||
|
||||
'use_bearer_authorization' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use HWI\Bundle\OAuthBundle\Security\Helper\NonceGenerator;
|
||||
use HWI\Bundle\OAuthBundle\Security\OAuthErrorHandler;
|
||||
use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* @author Francisco Facioni <fran6co@gmail.com>
|
||||
*/
|
||||
abstract class GenericOAuth1ResourceOwner extends AbstractResourceOwner
|
||||
{
|
||||
public const TYPE = null; // it must be null
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$parameters = array_merge([
|
||||
'oauth_consumer_key' => $this->options['client_id'],
|
||||
'oauth_timestamp' => time(),
|
||||
'oauth_nonce' => NonceGenerator::generate(),
|
||||
'oauth_version' => '1.0',
|
||||
'oauth_signature_method' => $this->options['signature_method'],
|
||||
'oauth_token' => $accessToken['oauth_token'],
|
||||
], $extraParameters);
|
||||
|
||||
$url = $this->options['infos_url'];
|
||||
$parameters['oauth_signature'] = OAuthUtils::signRequest(
|
||||
'GET',
|
||||
$url,
|
||||
$parameters,
|
||||
$this->options['client_secret'],
|
||||
$accessToken['oauth_token_secret'],
|
||||
$this->options['signature_method']
|
||||
);
|
||||
|
||||
$content = $this->doGetUserInformationRequest($url, $parameters);
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->getContent());
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$token = $this->getRequestToken($redirectUri, $extraParameters);
|
||||
|
||||
return $this->normalizeUrl($this->options['authorization_url'], ['oauth_token' => $token['oauth_token']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = [])
|
||||
{
|
||||
OAuthErrorHandler::handleOAuthError($request);
|
||||
|
||||
try {
|
||||
if (null === $requestToken = $this->storage->fetch($this, $request->query->get('oauth_token'))) {
|
||||
throw new \RuntimeException('No request token found in the storage.');
|
||||
}
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new AuthenticationException('Given token is not valid.');
|
||||
}
|
||||
|
||||
$parameters = array_merge([
|
||||
'oauth_consumer_key' => $this->options['client_id'],
|
||||
'oauth_timestamp' => time(),
|
||||
'oauth_nonce' => NonceGenerator::generate(),
|
||||
'oauth_version' => '1.0',
|
||||
'oauth_signature_method' => $this->options['signature_method'],
|
||||
'oauth_token' => $requestToken['oauth_token'],
|
||||
'oauth_verifier' => $request->query->get('oauth_verifier'),
|
||||
], $extraParameters);
|
||||
|
||||
$url = $this->options['access_token_url'];
|
||||
$parameters['oauth_signature'] = OAuthUtils::signRequest(
|
||||
'POST',
|
||||
$url,
|
||||
$parameters,
|
||||
$this->options['client_secret'],
|
||||
$requestToken['oauth_token_secret'],
|
||||
$this->options['signature_method']
|
||||
);
|
||||
|
||||
$response = $this->doGetTokenRequest($url, $parameters);
|
||||
$response = $this->getResponseContent($response);
|
||||
|
||||
if (isset($response['oauth_problem'])) {
|
||||
throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['oauth_problem']));
|
||||
}
|
||||
|
||||
if (!isset($response['oauth_token'], $response['oauth_token_secret'])) {
|
||||
throw new AuthenticationException('Not a valid request token.');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handles(HttpRequest $request)
|
||||
{
|
||||
return $request->query->has('oauth_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCsrfTokenValid($csrfToken)
|
||||
{
|
||||
// OAuth1.0a passes token with every call
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRequestToken($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$timestamp = time();
|
||||
|
||||
$parameters = array_merge([
|
||||
'oauth_consumer_key' => $this->options['client_id'],
|
||||
'oauth_timestamp' => $timestamp,
|
||||
'oauth_nonce' => NonceGenerator::generate(),
|
||||
'oauth_version' => '1.0',
|
||||
'oauth_callback' => $redirectUri,
|
||||
'oauth_signature_method' => $this->options['signature_method'],
|
||||
], $extraParameters);
|
||||
|
||||
$url = $this->options['request_token_url'];
|
||||
$parameters['oauth_signature'] = OAuthUtils::signRequest(
|
||||
'POST',
|
||||
$url,
|
||||
$parameters,
|
||||
$this->options['client_secret'],
|
||||
'',
|
||||
$this->options['signature_method']
|
||||
);
|
||||
|
||||
$apiResponse = $this->httpRequest($url, null, [], 'POST', $parameters);
|
||||
|
||||
$response = $this->getResponseContent($apiResponse);
|
||||
|
||||
if (isset($response['oauth_problem'])) {
|
||||
throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['oauth_problem']));
|
||||
}
|
||||
|
||||
if (isset($response['oauth_callback_confirmed']) && 'true' !== $response['oauth_callback_confirmed']) {
|
||||
throw new AuthenticationException('Defined OAuth callback was not confirmed.');
|
||||
}
|
||||
|
||||
if (!isset($response['oauth_token'], $response['oauth_token_secret'])) {
|
||||
throw new AuthenticationException('Not a valid request token.');
|
||||
}
|
||||
|
||||
$response['timestamp'] = $timestamp;
|
||||
|
||||
$this->storage->save($this, $response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetTokenRequest($url, array $parameters = [])
|
||||
{
|
||||
return $this->httpRequest($url, null, [], 'POST', $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetUserInformationRequest($url, array $parameters = [])
|
||||
{
|
||||
return $this->httpRequest($url, null, [], null, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function httpRequest($url, $content = null, array $headers = [], $method = null, array $parameters = [])
|
||||
{
|
||||
foreach ($parameters as $key => $value) {
|
||||
$parameters[$key] = $key.'="'.rawurlencode($value ?: '').'"';
|
||||
}
|
||||
|
||||
if (!$this->options['realm']) {
|
||||
array_unshift($parameters, 'realm="'.rawurlencode($this->options['realm'] ?? '').'"');
|
||||
}
|
||||
|
||||
$headers['Authorization'] = 'OAuth '.implode(', ', $parameters);
|
||||
|
||||
return parent::httpRequest($url, $content, $headers, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setRequired([
|
||||
'request_token_url',
|
||||
]);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'realm' => null,
|
||||
'signature_method' => 'HMAC-SHA1',
|
||||
]);
|
||||
|
||||
$resolver->setAllowedValues('signature_method', ['HMAC-SHA1', 'RSA-SHA1', 'PLAINTEXT']);
|
||||
}
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use HWI\Bundle\OAuthBundle\Security\Helper\NonceGenerator;
|
||||
use HWI\Bundle\OAuthBundle\Security\OAuthErrorHandler;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
abstract class GenericOAuth2ResourceOwner extends AbstractResourceOwner
|
||||
{
|
||||
public const TYPE = null; // it must be null
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
if ($this->options['use_bearer_authorization']) {
|
||||
$content = $this->httpRequest(
|
||||
$this->normalizeUrl($this->options['infos_url'], $extraParameters),
|
||||
null,
|
||||
['Authorization' => 'Bearer '.$accessToken['access_token']]
|
||||
);
|
||||
} else {
|
||||
$content = $this->doGetUserInformationRequest(
|
||||
$this->normalizeUrl(
|
||||
$this->options['infos_url'],
|
||||
array_merge([$this->options['attr_name'] => $accessToken['access_token']], $extraParameters)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->toArray(false));
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
if ($this->options['csrf']) {
|
||||
$this->handleCsrfToken();
|
||||
}
|
||||
|
||||
$parameters = array_merge([
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->options['client_id'],
|
||||
'scope' => $this->options['scope'],
|
||||
'state' => $this->state->encode(),
|
||||
'redirect_uri' => $redirectUri,
|
||||
], $extraParameters);
|
||||
|
||||
return $this->normalizeUrl($this->options['authorization_url'], $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = [])
|
||||
{
|
||||
OAuthErrorHandler::handleOAuthError($request);
|
||||
|
||||
$parameters = array_merge([
|
||||
'code' => $request->query->get('code'),
|
||||
'grant_type' => 'authorization_code',
|
||||
'redirect_uri' => $redirectUri,
|
||||
], $extraParameters);
|
||||
|
||||
$response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
|
||||
$response = $this->getResponseContent($response);
|
||||
|
||||
$this->validateResponseContent($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function refreshAccessToken($refreshToken, array $extraParameters = [])
|
||||
{
|
||||
$parameters = array_merge([
|
||||
'refresh_token' => $refreshToken,
|
||||
'grant_type' => 'refresh_token',
|
||||
], $extraParameters);
|
||||
|
||||
$response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
|
||||
$response = $this->getResponseContent($response);
|
||||
|
||||
$this->validateResponseContent($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokeToken($token)
|
||||
{
|
||||
if (!isset($this->options['revoke_token_url'])) {
|
||||
throw new AuthenticationException('OAuth error: "Method unsupported."');
|
||||
}
|
||||
|
||||
$parameters = [
|
||||
'client_id' => $this->options['client_id'],
|
||||
'client_secret' => $this->options['client_secret'],
|
||||
];
|
||||
|
||||
$response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url'], ['token' => $token]), $parameters, [], 'DELETE');
|
||||
|
||||
return 200 === $response->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handles(HttpRequest $request)
|
||||
{
|
||||
return $request->query->has('code');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCsrfTokenValid($csrfToken)
|
||||
{
|
||||
// Mark token valid when validation is disabled
|
||||
if (!$this->options['csrf']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (null === $csrfToken) {
|
||||
throw new AuthenticationException('Given CSRF token is not valid.');
|
||||
}
|
||||
|
||||
try {
|
||||
return null !== $this->storage->fetch($this, urldecode($csrfToken), 'csrf_state');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new AuthenticationException('Given CSRF token is not valid.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function shouldRefreshOnExpire()
|
||||
{
|
||||
return $this->options['refresh_on_expire'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetTokenRequest($url, array $parameters = [])
|
||||
{
|
||||
$headers = [];
|
||||
if ($this->options['use_authorization_to_get_token']) {
|
||||
if ($this->options['client_secret']) {
|
||||
$headers['Authorization'] = 'Basic '.base64_encode($this->options['client_id'].':'.$this->options['client_secret']);
|
||||
}
|
||||
} else {
|
||||
$parameters['client_id'] = $this->options['client_id'];
|
||||
$parameters['client_secret'] = $this->options['client_secret'];
|
||||
}
|
||||
|
||||
return $this->httpRequest($url, http_build_query($parameters, '', '&'), $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetUserInformationRequest($url, array $parameters = [])
|
||||
{
|
||||
return $this->httpRequest($url, http_build_query($parameters, '', '&'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $response the 'parsed' content based on the response headers
|
||||
*
|
||||
* @throws AuthenticationException If an OAuth error occurred or no access token is found
|
||||
*/
|
||||
protected function validateResponseContent($response)
|
||||
{
|
||||
if (isset($response['error_description'])) {
|
||||
throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['error_description']));
|
||||
}
|
||||
|
||||
if (isset($response['error'])) {
|
||||
throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['error']['message'] ?? $response['error']));
|
||||
}
|
||||
|
||||
if (!isset($response['access_token'])) {
|
||||
throw new AuthenticationException('Not a valid access token.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'attr_name' => 'access_token',
|
||||
'use_commas_in_scope' => false,
|
||||
'use_bearer_authorization' => true,
|
||||
'use_authorization_to_get_token' => true,
|
||||
'refresh_on_expire' => false,
|
||||
]);
|
||||
|
||||
$resolver->setDefined('revoke_token_url');
|
||||
$resolver->setAllowedValues('refresh_on_expire', [true, false]);
|
||||
|
||||
// Unfortunately some resource owners break the spec by using commas instead
|
||||
// of spaces to separate scopes (Disqus, Facebook, Github, Vkontante)
|
||||
$scopeNormalizer = function (Options $options, $value) {
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$options['use_commas_in_scope']) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return str_replace(',', ' ', $value);
|
||||
};
|
||||
|
||||
$resolver->setNormalizer('scope', $scopeNormalizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function httpRequest($url, $content = null, array $headers = [], $method = null)
|
||||
{
|
||||
$headers += ['Content-Type' => 'application/x-www-form-urlencoded'];
|
||||
|
||||
return parent::httpRequest($url, $content, $headers, $method);
|
||||
}
|
||||
|
||||
private function handleCsrfToken(): void
|
||||
{
|
||||
if (null === $this->state->getCsrfToken()) {
|
||||
$this->state->setCsrfToken(NonceGenerator::generate());
|
||||
}
|
||||
|
||||
$this->storage->save($this, $this->state->getCsrfToken(), 'csrf_state');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Krystian Marcisz <simivar@gmail.com>
|
||||
*/
|
||||
final class GeniusResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'genius';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'response.user.id',
|
||||
'nickname' => 'response.user.name',
|
||||
'realname' => 'response.user.name',
|
||||
'email' => 'response.user.email',
|
||||
'profilepicture' => 'response.user.avatar.medium.url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://api.genius.com/oauth/authorize',
|
||||
'access_token_url' => 'https://api.genius.com/oauth/token',
|
||||
'infos_url' => 'https://api.genius.com/account',
|
||||
'use_bearer_authorization' => true,
|
||||
'use_commas_in_scope' => true,
|
||||
'scope' => 'me',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
final class GitHubResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'github';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'login',
|
||||
'realname' => 'name',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'avatar_url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$response = parent::getUserInformation($accessToken, $extraParameters);
|
||||
|
||||
$responseData = $response->getData();
|
||||
if (empty($responseData['email'])) {
|
||||
// fetch the email addresses linked to the account
|
||||
$content = $this->httpRequest(
|
||||
$this->normalizeUrl($this->options['emails_url']), null, ['Authorization' => 'Bearer '.$accessToken['access_token']]
|
||||
);
|
||||
|
||||
foreach ($this->getResponseContent($content) as $email) {
|
||||
if (!empty($email['primary'])) {
|
||||
// we only need the primary email address
|
||||
$responseData['email'] = $email['email'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$response->setData($responseData);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokeToken($token)
|
||||
{
|
||||
$response = $this->httpRequest(
|
||||
sprintf($this->options['revoke_token_url'], $this->options['client_id']),
|
||||
json_encode(['access_token' => $token]),
|
||||
[
|
||||
'Authorization' => 'Basic '.base64_encode($this->options['client_id'].':'.$this->options['client_secret']),
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'DELETE'
|
||||
);
|
||||
|
||||
return 204 === $response->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://github.com/login/oauth/authorize',
|
||||
'access_token_url' => 'https://github.com/login/oauth/access_token',
|
||||
'revoke_token_url' => 'https://api.github.com/applications/%s/token',
|
||||
'infos_url' => 'https://api.github.com/user',
|
||||
'emails_url' => 'https://api.github.com/user/emails',
|
||||
|
||||
'use_commas_in_scope' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Indra Gunawan <hello@indra.my.id>
|
||||
*/
|
||||
final class GitLabResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'gitlab';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'username',
|
||||
'realname' => 'name',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'avatar_url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokeToken($token)
|
||||
{
|
||||
$parameters = [
|
||||
'token' => $token,
|
||||
'client_id' => $this->options['client_id'],
|
||||
'client_secret' => $this->options['client_secret'],
|
||||
];
|
||||
|
||||
$response = $this->httpRequest(
|
||||
$this->options['revoke_token_url'],
|
||||
$parameters,
|
||||
[],
|
||||
'POST'
|
||||
);
|
||||
|
||||
return 200 === $response->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://gitlab.com/oauth/authorize',
|
||||
'access_token_url' => 'https://gitlab.com/oauth/token',
|
||||
'revoke_token_url' => 'https://gitlab.com/oauth/revoke',
|
||||
'infos_url' => 'https://gitlab.com/api/v4/user',
|
||||
|
||||
'scope' => 'read_user',
|
||||
'use_commas_in_scope' => false,
|
||||
'use_bearer_authorization' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
final class GoogleResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'google';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'name',
|
||||
'realname' => 'name',
|
||||
'firstname' => 'given_name',
|
||||
'lastname' => 'family_name',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'picture',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$url = parent::getAuthorizationUrl($redirectUri, array_merge([
|
||||
'access_type' => $this->options['access_type'],
|
||||
'approval_prompt' => $this->options['approval_prompt'],
|
||||
'request_visible_actions' => $this->options['request_visible_actions'],
|
||||
'prompt' => $this->options['prompt'],
|
||||
], $extraParameters));
|
||||
|
||||
// This parameter have specific value (uses "&" as a separator of domains)
|
||||
if (null !== $this->options['hd']) {
|
||||
$url .= '&hd='.implode('&', array_map('trim', explode(',', $this->options['hd'])));
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokeToken($token)
|
||||
{
|
||||
$response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url'], ['token' => $token]));
|
||||
|
||||
return 200 === $response->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://accounts.google.com/o/oauth2/auth',
|
||||
'access_token_url' => 'https://accounts.google.com/o/oauth2/token',
|
||||
'revoke_token_url' => 'https://accounts.google.com/o/oauth2/revoke',
|
||||
'infos_url' => 'https://www.googleapis.com/oauth2/v1/userinfo',
|
||||
|
||||
'scope' => 'https://www.googleapis.com/auth/userinfo.profile',
|
||||
|
||||
'access_type' => null,
|
||||
'approval_prompt' => null,
|
||||
'display' => null,
|
||||
// Identifying a particular hosted domain account to be accessed (for example, 'mycollege.edu')
|
||||
'hd' => null,
|
||||
'login_hint' => null,
|
||||
'prompt' => null,
|
||||
'request_visible_actions' => null,
|
||||
]);
|
||||
|
||||
$resolver
|
||||
// @link https://developers.google.com/accounts/docs/OAuth2WebServer#offline
|
||||
->setAllowedValues('access_type', ['online', 'offline', null])
|
||||
// sometimes we need to force for approval prompt (e.g. when we lost refresh token)
|
||||
->setAllowedValues('approval_prompt', ['force', 'auto', null])
|
||||
// @link https://developers.google.com/accounts/docs/OAuth2Login#authenticationuriparameters
|
||||
->setAllowedValues('display', ['page', 'popup', 'touch', 'wap', null])
|
||||
->setAllowedValues('login_hint', ['email address', 'sub', null])
|
||||
->setAllowedValues('prompt', ['consent', 'select_account', null])
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Vincent Cassé <vincent@casse.me>
|
||||
*/
|
||||
final class HubicResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'hubic';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'email',
|
||||
'nickname' => 'email',
|
||||
'firstname' => 'firstname',
|
||||
'lastname' => 'lastname',
|
||||
'realname' => 'firstname',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://api.hubic.com/oauth/auth/',
|
||||
'access_token_url' => 'https://api.hubic.com/oauth/token/',
|
||||
'infos_url' => 'https://api.hubic.com/1.0/account',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Security\OAuthErrorHandler;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Jean-Christophe Cuvelier <jcc@atomseeds.com>
|
||||
* @author Fabiano Roberto <fabiano.roberto@ped.technology>
|
||||
*/
|
||||
final class InstagramResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'instagram';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'username',
|
||||
'realname' => 'username',
|
||||
'email' => 'id',
|
||||
'accounttype' => 'account_type',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = [])
|
||||
{
|
||||
OAuthErrorHandler::handleOAuthError($request);
|
||||
|
||||
$parameters = array_merge([
|
||||
'code' => $request->query->get('code'),
|
||||
'grant_type' => 'authorization_code',
|
||||
'client_id' => $this->options['client_id'],
|
||||
'client_secret' => $this->options['client_secret'],
|
||||
'redirect_uri' => $redirectUri,
|
||||
], $extraParameters);
|
||||
|
||||
$response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
|
||||
$response = $this->getResponseContent($response);
|
||||
|
||||
$this->validateResponseContent($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetUserInformationRequest($url, array $parameters = [])
|
||||
{
|
||||
return $this->httpRequest($this->normalizeUrl($url, $parameters), null, [], 'GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://api.instagram.com/oauth/authorize',
|
||||
'access_token_url' => 'https://api.instagram.com/oauth/access_token',
|
||||
'infos_url' => 'https://api.instagram.com/v1/users/self',
|
||||
'use_bearer_authorization' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Thomas Bretzke <tb@itembase.biz>
|
||||
*/
|
||||
final class ItembaseResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'itembase';
|
||||
|
||||
public const ITEMBASE_AUTH_URL = 'https://accounts.itembase.com/oauth/v2/auth';
|
||||
public const ITEMBASE_TOKEN_URL = 'https://accounts.itembase.com/oauth/v2/token';
|
||||
public const ITEMBASE_INFOS_URL = 'https://users.itembase.com/v1/me';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'uuid',
|
||||
'nickname' => 'username',
|
||||
'firstname' => 'first_name',
|
||||
'lastname' => 'last_name',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => self::ITEMBASE_AUTH_URL,
|
||||
'access_token_url' => self::ITEMBASE_TOKEN_URL,
|
||||
'infos_url' => self::ITEMBASE_INFOS_URL,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Dmitry Matora <dmitry.matora@gmail.com>
|
||||
*/
|
||||
final class JawboneResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'jawbone';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'xid' => 'data.id',
|
||||
'firstname' => 'data.first',
|
||||
'lastname' => 'data.last',
|
||||
'profilepicture' => 'data.image',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokeToken($accessToken)
|
||||
{
|
||||
$response = $this->getInformation($accessToken, 'PartnerAppMembership');
|
||||
|
||||
return 200 === $response->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInformation($accessToken, $type, array $extraParameters = [])
|
||||
{
|
||||
$url = $this->normalizeUrl($this->options['infos_url'].'/'.$type, $extraParameters);
|
||||
|
||||
$headers = [
|
||||
'Authorization' => 'Bearer '.$accessToken['access_token'],
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
|
||||
return $this->httpRequest($url, null, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://jawbone.com/auth/oauth2/auth',
|
||||
'access_token_url' => 'https://jawbone.com/auth/oauth2/token',
|
||||
'infos_url' => 'https://jawbone.com/nudge/api/v.1.0/users/@me',
|
||||
'use_commas_in_scope' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use HWI\Bundle\OAuthBundle\Security\Helper\NonceGenerator;
|
||||
use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
final class JiraResourceOwner extends GenericOAuth1ResourceOwner
|
||||
{
|
||||
public const TYPE = 'jira';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'name',
|
||||
'nickname' => 'name',
|
||||
'realname' => 'displayName',
|
||||
'email' => 'emailAddress',
|
||||
'profilepicture' => 'avatarUrls.48x48',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$parameters = array_merge([
|
||||
'oauth_consumer_key' => $this->options['client_id'],
|
||||
'oauth_timestamp' => time(),
|
||||
'oauth_nonce' => NonceGenerator::generate(),
|
||||
'oauth_version' => '1.0',
|
||||
'oauth_signature_method' => $this->options['signature_method'],
|
||||
'oauth_token' => $accessToken['oauth_token'],
|
||||
], $extraParameters);
|
||||
|
||||
$parameters['oauth_signature'] = OAuthUtils::signRequest(
|
||||
'GET',
|
||||
$this->options['infos_session_url'],
|
||||
$parameters,
|
||||
$this->options['client_secret'],
|
||||
$accessToken['oauth_token_secret'],
|
||||
$this->options['signature_method']
|
||||
);
|
||||
|
||||
$content = $this->getResponseContent($this->doGetUserInformationRequest($this->options['infos_session_url'], $parameters));
|
||||
$url = $this->normalizeUrl($this->options['infos_url'], ['username' => $content['name']]);
|
||||
|
||||
// Regenerate nonce & signature as URL was changed
|
||||
$parameters['oauth_nonce'] = NonceGenerator::generate();
|
||||
$parameters['oauth_signature'] = OAuthUtils::signRequest(
|
||||
'GET',
|
||||
$url,
|
||||
$parameters,
|
||||
$this->options['client_secret'],
|
||||
$accessToken['oauth_token_secret'],
|
||||
$this->options['signature_method']
|
||||
);
|
||||
|
||||
try {
|
||||
$content = $this->doGetUserInformationRequest($url, $parameters);
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->getContent(false));
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => '{base_url}/plugins/servlet/oauth/authorize',
|
||||
'request_token_url' => '{base_url}/plugins/servlet/oauth/request-token',
|
||||
'access_token_url' => '{base_url}/plugins/servlet/oauth/access-token',
|
||||
|
||||
// JIRA API requires to first know the username to be able to ask for user details
|
||||
'infos_session_url' => '{base_url}/rest/auth/1/session',
|
||||
'infos_url' => '{base_url}/rest/api/2/user',
|
||||
|
||||
'signature_method' => 'RSA-SHA1',
|
||||
]);
|
||||
|
||||
$resolver->setRequired([
|
||||
'base_url',
|
||||
]);
|
||||
|
||||
$normalizer = function (Options $options, $value) {
|
||||
return str_replace('{base_url}', $options['base_url'], $value);
|
||||
};
|
||||
|
||||
$resolver
|
||||
->setNormalizer('authorization_url', $normalizer)
|
||||
->setNormalizer('request_token_url', $normalizer)
|
||||
->setNormalizer('access_token_url', $normalizer)
|
||||
->setNormalizer('infos_session_url', $normalizer)
|
||||
->setNormalizer('infos_url', $normalizer)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Andrea Quintino <andreaquin1990@gmail.com>
|
||||
*/
|
||||
final class KeycloakResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'keycloak';
|
||||
|
||||
protected array $paths = [
|
||||
'identifier' => 'sub',
|
||||
'nickname' => 'preferred_username',
|
||||
'firstname' => 'given_name',
|
||||
'lastname' => 'family_name',
|
||||
'realname' => 'name',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'picture',
|
||||
];
|
||||
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
return parent::getAuthorizationUrl($redirectUri, array_merge([
|
||||
'approval_prompt' => $this->getOption('approval_prompt'),
|
||||
'kc_idp_hint' => $this->getOption('idp_hint'),
|
||||
], $extraParameters));
|
||||
}
|
||||
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'protocol' => 'openid-connect',
|
||||
'scope' => 'openid email',
|
||||
'response_type' => 'code',
|
||||
'approval_prompt' => 'auto',
|
||||
'authorization_url' => '{keycloak_url}/auth',
|
||||
'access_token_url' => '{keycloak_url}/token',
|
||||
'infos_url' => '{keycloak_url}/userinfo',
|
||||
'idp_hint' => null,
|
||||
]);
|
||||
|
||||
$resolver->setRequired([
|
||||
'realm',
|
||||
'base_url',
|
||||
]);
|
||||
|
||||
$normalizer = function (Options $options, $value) {
|
||||
return str_replace(
|
||||
'{keycloak_url}',
|
||||
$options['base_url'].'/realms/'.$options['realm'].'/protocol/'.$options['protocol'],
|
||||
$value
|
||||
);
|
||||
};
|
||||
|
||||
$resolver->setNormalizer('authorization_url', $normalizer);
|
||||
$resolver->setNormalizer('access_token_url', $normalizer);
|
||||
$resolver->setNormalizer('infos_url', $normalizer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Response\LinkedinUserResponse;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Francisco Facioni <fran6co@gmail.com>
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class LinkedinResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'linkedin';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'emailAddress',
|
||||
'firstname' => 'firstName',
|
||||
'lastname' => 'lastName',
|
||||
'email' => 'emailAddress',
|
||||
'profilepicture' => 'profilePicture',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$response = parent::getUserInformation($accessToken, $extraParameters);
|
||||
|
||||
$responseData = $response->getData();
|
||||
// The user info returned by /me doesn't contain the email so we make an extra request to fetch it
|
||||
$content = $this->httpRequest(
|
||||
$this->normalizeUrl($this->options['email_url'], $extraParameters),
|
||||
null,
|
||||
['Authorization' => 'Bearer '.$accessToken['access_token']]
|
||||
);
|
||||
|
||||
$emailResponse = $this->getResponseContent($content);
|
||||
if (isset($emailResponse['elements']) && \count($emailResponse['elements']) > 0) {
|
||||
$responseData['emailAddress'] = $emailResponse['elements'][0]['handle~']['emailAddress'];
|
||||
}
|
||||
// errors not handled because I don't see any relevant thing to do with them
|
||||
|
||||
$response->setData($responseData);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetTokenRequest($url, array $parameters = [])
|
||||
{
|
||||
$parameters['client_id'] = $this->options['client_id'];
|
||||
$parameters['client_secret'] = $this->options['client_secret'];
|
||||
|
||||
return $this->httpRequest($this->normalizeUrl($url, $parameters), null, [], 'POST');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function httpRequest($url, $content = null, array $headers = [], $method = null)
|
||||
{
|
||||
// LinkedIn v2 API is supposed to require Content-Type: application/json but it works without
|
||||
// and request to get the access token doesn't seems to work with Content-Type: application/json
|
||||
// so we don't put any Content-Type header.
|
||||
// Skip the Content-Type header in GenericOAuth2ResourceOwner::httpRequest
|
||||
//
|
||||
// LinkedIn API requires to always set Content-Length in POST requests
|
||||
if ('POST' === $method) {
|
||||
$headers['Content-Length'] = \is_string($content) ? (string) \strlen($content) : '0';
|
||||
}
|
||||
|
||||
return AbstractResourceOwner::httpRequest($url, $content, $headers, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'scope' => 'r_liteprofile r_emailaddress',
|
||||
'authorization_url' => 'https://www.linkedin.com/oauth/v2/authorization',
|
||||
'access_token_url' => 'https://www.linkedin.com/oauth/v2/accessToken',
|
||||
'infos_url' => 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))',
|
||||
'email_url' => 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))',
|
||||
|
||||
'user_response_class' => LinkedinUserResponse::class,
|
||||
|
||||
'csrf' => true,
|
||||
|
||||
'use_bearer_authorization' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Gaponov Igor <jiminy96@gmail.com>
|
||||
*/
|
||||
final class MailRuResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'mailru';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'uid',
|
||||
'nickname' => 'nick',
|
||||
'realname' => 'nick',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$params = [
|
||||
'app_id' => $this->options['client_id'],
|
||||
'method' => 'users.getInfo',
|
||||
'secure' => '1',
|
||||
'session_key' => $accessToken['access_token'],
|
||||
];
|
||||
|
||||
$params['sig'] = md5(vsprintf('app_id=%smethod=%ssecure=%ssession_key=%s', $params).$this->options['client_secret']);
|
||||
|
||||
$url = $this->normalizeUrl($this->options['infos_url'], $params);
|
||||
|
||||
try {
|
||||
$content = $this->doGetUserInformationRequest($url)->toArray(false);
|
||||
if (isset($content[0])) {
|
||||
$content = (array) $content[0];
|
||||
}
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content);
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://connect.mail.ru/oauth/authorize',
|
||||
'access_token_url' => 'https://connect.mail.ru/oauth/token',
|
||||
'infos_url' => 'http://www.appsmail.ru/platform/api',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
final class OAuth1ResourceOwner extends GenericOAuth1ResourceOwner
|
||||
{
|
||||
public const TYPE = 'oauth1';
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
final class OAuth2ResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'oauth2';
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Sergey Polischook <spolischook@gmail.com>
|
||||
*/
|
||||
final class OdnoklassnikiResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'odnoklassniki';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'uid',
|
||||
'nickname' => 'username',
|
||||
'realname' => 'name',
|
||||
'email' => 'email',
|
||||
'firstname' => 'first_name',
|
||||
'lastname' => 'last_name',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$parameters = [
|
||||
'access_token' => $accessToken['access_token'],
|
||||
'application_key' => $this->options['application_key'],
|
||||
];
|
||||
|
||||
if ($this->options['fields']) {
|
||||
$parameters['fields'] = $this->options['fields'];
|
||||
$parameters['sig'] = md5(sprintf(
|
||||
'application_key=%sfields=%smethod=users.getCurrentUser%s',
|
||||
$this->options['application_key'],
|
||||
$this->options['fields'],
|
||||
md5($accessToken['access_token'].$this->options['client_secret'])
|
||||
));
|
||||
} else {
|
||||
$parameters['sig'] = md5(sprintf(
|
||||
'application_key=%smethod=users.getCurrentUser%s',
|
||||
$this->options['application_key'],
|
||||
md5($accessToken['access_token'].$this->options['client_secret'])
|
||||
));
|
||||
}
|
||||
|
||||
$url = $this->normalizeUrl($this->options['infos_url'], $parameters);
|
||||
|
||||
try {
|
||||
$content = $this->doGetUserInformationRequest($url)->toArray(false);
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content);
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://connect.ok.ru/oauth/authorize',
|
||||
'access_token_url' => 'https://api.ok.ru/oauth/token.do',
|
||||
'infos_url' => 'https://api.ok.ru/fb.do?method=users.getCurrentUser',
|
||||
|
||||
'application_key' => null,
|
||||
'fields' => null,
|
||||
]);
|
||||
|
||||
$fieldsNormalizer = function (Options $options, $value) {
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \is_array($value) ? implode(',', $value) : $value;
|
||||
};
|
||||
|
||||
$resolver->setNormalizer('fields', $fieldsNormalizer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class Office365ResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'office365';
|
||||
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'email' => 'mail',
|
||||
'realname' => 'displayName',
|
||||
'firstname' => 'givenName',
|
||||
'lastname' => 'surname',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$extraParameters = array_merge([
|
||||
'resource' => 'https://graph.microsoft.com',
|
||||
], $extraParameters);
|
||||
|
||||
return parent::getAccessToken($request, $redirectUri, $extraParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://login.microsoftonline.com/common/oauth2/authorize',
|
||||
'access_token_url' => 'https://login.microsoftonline.com/common/oauth2/token',
|
||||
'infos_url' => 'https://graph.microsoft.com/v1.0/me',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
final class PassageResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'passage';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'sub',
|
||||
'email' => 'email',
|
||||
'phone_number' => 'phone_number',
|
||||
'email_verified' => 'email_verified',
|
||||
'phone_number_verified' => 'phone_number_verified',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokeToken($token)
|
||||
{
|
||||
if (!isset($this->options['revoke_token_url'])) {
|
||||
throw new AuthenticationException('OAuth error: "Method unsupported."');
|
||||
}
|
||||
|
||||
$parameters = [
|
||||
'client_id' => $this->options['client_id'],
|
||||
'client_secret' => $this->options['client_secret'],
|
||||
'token' => $token,
|
||||
];
|
||||
|
||||
$response = $this->httpRequest($this->normalizeUrl($this->options['revoke_token_url']), $parameters, [], 'POST');
|
||||
|
||||
return 200 === $response->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://{sub_domain}.withpassage.com/authorize',
|
||||
'access_token_url' => 'https://{sub_domain}.withpassage.com/token',
|
||||
'revoke_token_url' => 'https://{sub_domain}.withpassage.com/revoke',
|
||||
'infos_url' => 'https://{sub_domain}.withpassage.com/userinfo',
|
||||
|
||||
'use_commas_in_scope' => false,
|
||||
'scope' => 'openid email',
|
||||
]);
|
||||
|
||||
$resolver->setRequired([
|
||||
'sub_domain',
|
||||
]);
|
||||
|
||||
$normalizer = function (Options $options, $value) {
|
||||
return str_replace('{sub_domain}', $options['sub_domain'], $value);
|
||||
};
|
||||
|
||||
$resolver
|
||||
->setNormalizer('authorization_url', $normalizer)
|
||||
->setNormalizer('access_token_url', $normalizer)
|
||||
->setNormalizer('revoke_token_url', $normalizer)
|
||||
->setNormalizer('infos_url', $normalizer)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Berny Cantos <be@rny.cc>
|
||||
*/
|
||||
final class PaypalResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'paypal';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'user_id',
|
||||
'nickname' => 'email',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'sandbox' => false,
|
||||
'scope' => 'openid email',
|
||||
'authorization_url' => 'https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize',
|
||||
'access_token_url' => 'https://api.paypal.com/v1/identity/openidconnect/tokenservice',
|
||||
'infos_url' => 'https://api.paypal.com/v1/identity/openidconnect/userinfo/?schema=openid',
|
||||
]);
|
||||
|
||||
$resolver->addAllowedTypes('sandbox', 'bool');
|
||||
|
||||
$sandboxTransformation = function (Options $options, $value) {
|
||||
if (!$options['sandbox']) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return preg_replace('~\.paypal\.~', '.sandbox.paypal.', $value, 1);
|
||||
};
|
||||
|
||||
$resolver
|
||||
->setNormalizer('authorization_url', $sandboxTransformation)
|
||||
->setNormalizer('access_token_url', $sandboxTransformation)
|
||||
->setNormalizer('infos_url', $sandboxTransformation)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
final class QQResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'qq';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'openid',
|
||||
'nickname' => 'nickname',
|
||||
'realname' => 'nickname',
|
||||
'profilepicture' => 'figureurl_qq_1',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getResponseContent(ResponseInterface $rawResponse): array
|
||||
{
|
||||
$content = $rawResponse->getContent(false);
|
||||
if (preg_match('/^callback\((.+)\);$/', $content, $matches)) {
|
||||
return json_decode(trim($matches[1]), true) ?: [];
|
||||
}
|
||||
|
||||
return parent::getResponseContent($rawResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(?array $accessToken = null, array $extraParameters = [])
|
||||
{
|
||||
$openid = $extraParameters['openid'] ?? $this->requestUserIdentifier($accessToken);
|
||||
|
||||
$url = $this->normalizeUrl($this->options['infos_url'], [
|
||||
'oauth_consumer_key' => $this->options['client_id'],
|
||||
'access_token' => $accessToken['access_token'],
|
||||
'openid' => $openid,
|
||||
'format' => 'json',
|
||||
]);
|
||||
|
||||
$response = $this->doGetUserInformationRequest($url);
|
||||
$content = $this->getResponseContent($response);
|
||||
|
||||
// Custom errors:
|
||||
if (isset($content['ret']) && 0 === $content['ret']) {
|
||||
$content['openid'] = $openid;
|
||||
} else {
|
||||
throw new AuthenticationException(sprintf('OAuth error: %s', isset($content['ret']) ? $content['msg'] : 'invalid response'));
|
||||
}
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content);
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://graph.qq.com/oauth2.0/authorize?format=json',
|
||||
'access_token_url' => 'https://graph.qq.com/oauth2.0/token',
|
||||
'infos_url' => 'https://graph.qq.com/user/get_user_info',
|
||||
'me_url' => 'https://graph.qq.com/oauth2.0/me',
|
||||
]);
|
||||
}
|
||||
|
||||
private function requestUserIdentifier(?array $accessToken = null)
|
||||
{
|
||||
$url = $this->normalizeUrl($this->options['me_url'], [
|
||||
'access_token' => $accessToken['access_token'],
|
||||
]);
|
||||
|
||||
$response = $this->httpRequest($url);
|
||||
$content = $this->getResponseContent($response);
|
||||
|
||||
if (!isset($content['openid'])) {
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
|
||||
return $content['openid'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Martin Aarhof <martin.aarhof@gmail.com>
|
||||
*/
|
||||
final class RedditResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'reddit';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'name',
|
||||
'realname' => null,
|
||||
'email' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetTokenRequest($url, array $parameters = [])
|
||||
{
|
||||
return $this->httpRequest(
|
||||
$url,
|
||||
http_build_query($parameters, '', '&'),
|
||||
[
|
||||
'Authorization' => 'Basic '.base64_encode(sprintf('%s:%s', $this->options['client_id'], $this->options['client_secret'])),
|
||||
],
|
||||
'POST'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://ssl.reddit.com/api/v1/authorize',
|
||||
'access_token_url' => 'https://ssl.reddit.com/api/v1/access_token',
|
||||
'infos_url' => 'https://oauth.reddit.com/api/v1/me.json',
|
||||
|
||||
'use_bearer_authorization' => true,
|
||||
'use_commas_in_scope' => true,
|
||||
'csrf' => true,
|
||||
'scope' => 'identity',
|
||||
|
||||
'duration' => 'permanent',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Artem Genvald <genvaldartem@gmail.com>
|
||||
*/
|
||||
final class RunKeeperResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'runkeeper';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'realname' => 'name',
|
||||
'profilepicture' => 'medium_picture',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserResource($accessToken)
|
||||
{
|
||||
$response = $this->httpRequest(
|
||||
$this->normalizeUrl($this->options['user_resource_url']),
|
||||
null,
|
||||
['Authorization' => 'Bearer '.$accessToken]
|
||||
);
|
||||
|
||||
return $this->getResponseContent($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://runkeeper.com/apps/authorize',
|
||||
'access_token_url' => 'https://runkeeper.com/apps/token',
|
||||
'infos_url' => 'https://api.runkeeper.com/profile',
|
||||
'user_resource_url' => 'https://api.runkeeper.com/user',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Tyler Pugh <tylerism@gmail.com>
|
||||
*/
|
||||
final class SalesforceResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'salesforce';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'user_id',
|
||||
'nickname' => 'nick_name',
|
||||
'realname' => 'nick_name',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'photos.picture',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
// SalesForce returns the infos_url in the OAuth Response Token
|
||||
$this->options['infos_url'] = $accessToken['id'];
|
||||
|
||||
return parent::getUserInformation($accessToken, $extraParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetUserInformationRequest($url, array $parameters = [])
|
||||
{
|
||||
// Salesforce requires format parameter in order for API to return json response
|
||||
$url = $this->normalizeUrl($url, [
|
||||
'format' => $this->options['format'],
|
||||
]);
|
||||
|
||||
// Salesforce require to pass the OAuth token as 'oauth_token' instead of 'access_token'
|
||||
$url = str_replace('access_token', 'oauth_token', $url);
|
||||
|
||||
return $this->httpRequest($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'sandbox' => false,
|
||||
'authorization_url' => 'https://login.salesforce.com/services/oauth2/authorize',
|
||||
'access_token_url' => 'https://login.salesforce.com/services/oauth2/token',
|
||||
|
||||
// @see SalesforceResourceOwner::getUserInformation()
|
||||
'infos_url' => null,
|
||||
|
||||
// @see SalesforceResourceOwner::doGetUserInformationRequest()
|
||||
'format' => 'json',
|
||||
]);
|
||||
|
||||
$sandboxTransformation = function (Options $options, $value) {
|
||||
if (!$options['sandbox']) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return preg_replace('~login\.~', 'test.', $value, 1);
|
||||
};
|
||||
|
||||
$resolver
|
||||
->setNormalizer('authorization_url', $sandboxTransformation)
|
||||
->setNormalizer('access_token_url', $sandboxTransformation)
|
||||
;
|
||||
|
||||
$resolver->addAllowedTypes('sandbox', 'bool');
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Response\SensioConnectUserResponse;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class SensioConnectResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'sensio_connect';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$content = $this->doGetUserInformationRequest(
|
||||
$this->normalizeUrl(
|
||||
$this->options['infos_url'],
|
||||
array_merge([$this->options['attr_name'] => $accessToken['access_token']], $extraParameters)
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->getContent(false));
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetUserInformationRequest($url, array $parameters = [])
|
||||
{
|
||||
return $this->httpRequest($url, null, ['Accept' => 'application/vnd.com.sensiolabs.connect+xml']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://connect.symfony.com/oauth/authorize',
|
||||
'access_token_url' => 'https://connect.symfony.com/oauth/access_token',
|
||||
'infos_url' => 'https://connect.symfony.com/api',
|
||||
|
||||
'user_response_class' => SensioConnectUserResponse::class,
|
||||
|
||||
'response_type' => 'code',
|
||||
|
||||
'use_bearer_authorization' => false,
|
||||
'csrf' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
final class SinaWeiboResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'sina_weibo';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'screen_name',
|
||||
'realname' => 'screen_name',
|
||||
'profilepicture' => 'profile_image_url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(?array $accessToken = null, array $extraParameters = [])
|
||||
{
|
||||
$url = $this->normalizeUrl($this->options['infos_url'], [
|
||||
'access_token' => $accessToken['access_token'],
|
||||
'uid' => $accessToken['uid'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$content = $this->doGetUserInformationRequest($url);
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->toArray(false));
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://api.weibo.com/oauth2/authorize',
|
||||
'access_token_url' => 'https://api.weibo.com/oauth2/access_token',
|
||||
'infos_url' => 'https://api.weibo.com/2/users/show.json',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Baptiste Clavié <clavie.b@gmail.com>
|
||||
*/
|
||||
final class SlackResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'slack';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'user.id',
|
||||
'nickname' => 'user.name',
|
||||
'email' => 'user.email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://slack.com/oauth/authorize',
|
||||
'access_token_url' => 'https://slack.com/api/oauth.access',
|
||||
'infos_url' => 'https://slack.com/api/users.identity',
|
||||
|
||||
'scope' => 'identify',
|
||||
|
||||
'use_bearer_authorization' => false,
|
||||
'attr_name' => 'token',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Anthony AHMED <antho.ahmed@gmail.com>
|
||||
*/
|
||||
final class SoundcloudResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'soundcloud';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'username',
|
||||
'realname' => 'full_name',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'access_token_url' => 'https://api.soundcloud.com/oauth2/token',
|
||||
'attr_name' => 'oauth_token',
|
||||
'authorization_url' => 'https://soundcloud.com/connect',
|
||||
'infos_url' => 'https://api.soundcloud.com/me.json',
|
||||
'scope' => 'non-expiring',
|
||||
'use_bearer_authorization' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Janne Savolainen <janne.savolainen@sempre.fi>
|
||||
*/
|
||||
final class SpotifyResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'spotify';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'id',
|
||||
'realname' => 'display_name',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'images.0.url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(?array $accessToken = null, array $extraParameters = [])
|
||||
{
|
||||
$url = $this->normalizeUrl($this->options['infos_url'], [
|
||||
'access_token' => $accessToken['access_token'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$content = $this->doGetUserInformationRequest($url);
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->toArray(false));
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://accounts.spotify.com/authorize',
|
||||
'access_token_url' => 'https://accounts.spotify.com/api/token',
|
||||
'infos_url' => 'https://api.spotify.com/v1/me',
|
||||
]);
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class StackExchangeResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'stack_exchange';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'items.0.user_id',
|
||||
'nickname' => 'items.0.display_name',
|
||||
'realname' => 'items.0.display_name',
|
||||
'profilepicture' => 'items.0.profile_image',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$parameters = array_merge(
|
||||
[$this->options['attr_name'] => $accessToken['access_token']],
|
||||
['site' => $this->options['site'], 'key' => $this->options['key']],
|
||||
$extraParameters
|
||||
);
|
||||
|
||||
try {
|
||||
$content = $this->doGetUserInformationRequest($this->normalizeUrl($this->options['infos_url'], $parameters));
|
||||
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->toArray(false));
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setRequired([
|
||||
'key',
|
||||
]);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://stackexchange.com/oauth',
|
||||
'access_token_url' => 'https://stackexchange.com/oauth/access_token',
|
||||
'infos_url' => 'https://api.stackexchange.com/2.0/me',
|
||||
|
||||
'scope' => 'no_expiry',
|
||||
'site' => 'stackoverflow',
|
||||
'use_bearer_authorization' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Vincenzo Di Biaggio <aniceweb@gmail.com>
|
||||
*/
|
||||
final class StereomoodResourceOwner extends GenericOAuth1ResourceOwner
|
||||
{
|
||||
public const TYPE = 'stereomood';
|
||||
|
||||
protected array $paths = [
|
||||
'identifier' => 'oauth_token',
|
||||
'nickname' => 'oauth_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($accessToken);
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'http://www.stereomood.com/api/oauth/authenticate',
|
||||
'request_token_url' => 'http://www.stereomood.com/api/oauth/request_token',
|
||||
'access_token_url' => 'http://www.stereomood.com/api/oauth/access_token',
|
||||
|
||||
// Stereomood don't use `infos_url`
|
||||
'infos_url' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Artem Genvald <genvaldartem@gmail.com>
|
||||
*/
|
||||
final class StravaResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'strava';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'realname' => ['firstname', 'lastname'],
|
||||
'profilepicture' => 'profile_medium',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://www.strava.com/oauth/authorize',
|
||||
'access_token_url' => 'https://www.strava.com/oauth/token',
|
||||
'infos_url' => 'https://www.strava.com/api/v3/athlete',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Response\PathUserResponse;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\LazyResponseException;
|
||||
|
||||
/**
|
||||
* @author zorn-v
|
||||
*/
|
||||
final class TelegramResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'telegram';
|
||||
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'username',
|
||||
'realname' => 'first_name',
|
||||
'firstname' => 'first_name',
|
||||
'lastname' => 'last_name',
|
||||
'profilepicture' => 'photo_url',
|
||||
];
|
||||
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
[$botId] = explode(':', $this->options['client_secret']);
|
||||
$parameters = array_merge([
|
||||
'bot_id' => $botId,
|
||||
'origin' => $redirectUri,
|
||||
'return_to' => $redirectUri,
|
||||
], $extraParameters);
|
||||
|
||||
return $this->normalizeUrl($this->options['authorization_url'], $parameters);
|
||||
}
|
||||
|
||||
public function handles(Request $request)
|
||||
{
|
||||
if (!$request->query->has('code')) {
|
||||
$js = '<script>location.href = "?code=" + new URLSearchParams(location.hash.substring(1)).get("tgAuthResult")</script>';
|
||||
throw new LazyResponseException(new Response($js));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$token = $request->query->get('code', '');
|
||||
$token = str_pad(strtr($token, '-_', '+/'), \strlen($token) % 4, '=', \STR_PAD_RIGHT);
|
||||
$authData = json_decode(base64_decode($token), true);
|
||||
if (empty($authData['hash'])) {
|
||||
throw new AuthenticationException('Invalid Telegram auth data');
|
||||
}
|
||||
if (empty($authData['auth_date']) || (time() - $authData['auth_date']) > 300) {
|
||||
throw new AuthenticationException('Telegram auth data expired');
|
||||
}
|
||||
$botToken = $this->options['client_secret'];
|
||||
$checkHash = $authData['hash'];
|
||||
unset($authData['hash']);
|
||||
ksort($authData);
|
||||
$dataCheckStr = '';
|
||||
foreach ($authData as $k => $v) {
|
||||
$dataCheckStr .= sprintf("\n%s=%s", $k, $v);
|
||||
}
|
||||
$dataCheckStr = substr($dataCheckStr, 1);
|
||||
$secretKey = hash('sha256', $botToken, true);
|
||||
$hash = hash_hmac('sha256', $dataCheckStr, $secretKey);
|
||||
if ($hash !== $checkHash) {
|
||||
throw new AuthenticationException('Telegram auth data check failed');
|
||||
}
|
||||
|
||||
return ['access_token' => $token];
|
||||
}
|
||||
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$data = base64_decode($accessToken['access_token']);
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($data);
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setRequired([
|
||||
'client_id',
|
||||
'client_secret',
|
||||
'authorization_url',
|
||||
]);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://oauth.telegram.org/auth',
|
||||
'auth_with_one_url' => true,
|
||||
'state' => null,
|
||||
'csrf' => false,
|
||||
'user_response_class' => PathUserResponse::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Richard van den Brand <richard@vandenbrand.org>
|
||||
*/
|
||||
final class ThirtySevenSignalsResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = '37signals';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'identity.id',
|
||||
'nickname' => 'identity.email_address',
|
||||
'firstname' => 'identity.first_name',
|
||||
'lastname' => 'identity.last_name',
|
||||
'realname' => ['identity.last_name', 'identity.first_name'],
|
||||
'email' => 'identity.email_address',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
return parent::getAuthorizationUrl($redirectUri, array_merge(['type' => 'web_server'], $extraParameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessToken(Request $request, $redirectUri, array $extraParameters = [])
|
||||
{
|
||||
return parent::getAccessToken($request, $redirectUri, array_merge(['type' => 'web_server'], $extraParameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://launchpad.37signals.com/authorization/new',
|
||||
'access_token_url' => 'https://launchpad.37signals.com/authorization/token',
|
||||
'infos_url' => 'https://launchpad.37signals.com/authorization.json',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Davide Bellettini <davide@bellettini.me>
|
||||
*/
|
||||
final class ToshlResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'toshl';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'email',
|
||||
'firstname' => 'first_name',
|
||||
'lastname' => 'last_name',
|
||||
'realname' => ['first_name', 'last_name'],
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokeToken($token)
|
||||
{
|
||||
$response = $this->httpRequest(
|
||||
$this->options['revoke_token_url'],
|
||||
null,
|
||||
['Authorization' => 'Basic '.base64_encode($this->options['client_id'].':'.$this->options['client_secret'])],
|
||||
'DELETE'
|
||||
);
|
||||
|
||||
return 204 === $response->getStatusCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://toshl.com/oauth2/authorize',
|
||||
'access_token_url' => 'https://toshl.com/oauth2/token',
|
||||
'revoke_token_url' => 'https://toshl.com/oauth2/revoke',
|
||||
'infos_url' => 'https://api.toshl.com/me',
|
||||
'csrf' => true,
|
||||
'use_commas_in_scope' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Julien DIDIER <julien@didier.io>
|
||||
*/
|
||||
final class TraktResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'trakt';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'username',
|
||||
'nickname' => 'username',
|
||||
'realname' => 'name',
|
||||
'profilepicture' => 'images.avatar.full',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$content = $this->httpRequest($this->normalizeUrl($this->options['infos_url']), null, [
|
||||
'Authorization' => 'Bearer '.$accessToken['access_token'],
|
||||
'Content-Type' => 'application/json',
|
||||
'trakt-api-key' => $this->options['client_id'],
|
||||
'trakt-api-version' => 2,
|
||||
]);
|
||||
|
||||
try {
|
||||
$response = $this->getUserResponse();
|
||||
$response->setData($content->toArray(false));
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://api-v2launch.trakt.tv/oauth/authorize',
|
||||
'access_token_url' => 'https://api-v2launch.trakt.tv/oauth/token',
|
||||
'infos_url' => 'https://api-v2launch.trakt.tv/users/me?extended=images',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class TrelloResourceOwner extends GenericOAuth1ResourceOwner
|
||||
{
|
||||
public const TYPE = 'trello';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'username',
|
||||
'realname' => 'fullName',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'avatarSource',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
$token = $this->getRequestToken($redirectUri, $extraParameters);
|
||||
|
||||
return $this->normalizeUrl($this->options['authorization_url'], [
|
||||
'scope' => $this->options['scopes'],
|
||||
'name' => $this->options['application'],
|
||||
'expiration' => $this->options['expiration'],
|
||||
'oauth_token' => $token['oauth_token'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://trello.com/1/OAuthAuthorizeToken',
|
||||
'request_token_url' => 'https://trello.com/1/OAuthGetRequestToken',
|
||||
'access_token_url' => 'https://trello.com/1/OAuthGetAccessToken',
|
||||
'infos_url' => 'https://api.trello.com/1/members/me?fields=username,fullName,avatarSource,email',
|
||||
'realm' => 'trello.com',
|
||||
'application' => null,
|
||||
'scopes' => 'read',
|
||||
'expiration' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Simon Bräuer <redshark1802>
|
||||
*/
|
||||
final class TwitchResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'twitch';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'data.0.id',
|
||||
'nickname' => 'data.0.login',
|
||||
'realname' => 'data.0.display_name',
|
||||
'email' => 'data.0.email', // Require scope "user:read:email"
|
||||
'profilepicture' => 'data.0.profile_image_url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function httpRequest($url, $content = null, array $headers = [], $method = null)
|
||||
{
|
||||
// Twitch also require that you provide the client id as a header
|
||||
$headers += ['Client-ID' => $this->options['client_id']];
|
||||
|
||||
return parent::httpRequest($url, $content, $headers, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://id.twitch.tv/oauth2/authorize',
|
||||
'access_token_url' => 'https://id.twitch.tv/oauth2/token',
|
||||
'infos_url' => 'https://api.twitch.tv/helix/users',
|
||||
'use_bearer_authorization' => true,
|
||||
'use_authorization_to_get_token' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
final class TwitterResourceOwner extends GenericOAuth1ResourceOwner
|
||||
{
|
||||
public const TYPE = 'twitter';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id_str',
|
||||
'nickname' => 'screen_name',
|
||||
'realname' => 'name',
|
||||
'profilepicture' => 'profile_image_url_https',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
if ($this->options['include_email']) {
|
||||
$this->options['infos_url'] = $this->normalizeUrl($this->options['infos_url'], ['include_email' => 'true']);
|
||||
}
|
||||
|
||||
return parent::getUserInformation($accessToken, $extraParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://api.twitter.com/oauth/authenticate',
|
||||
'request_token_url' => 'https://api.twitter.com/oauth/request_token',
|
||||
'access_token_url' => 'https://api.twitter.com/oauth/access_token',
|
||||
'infos_url' => 'https://api.twitter.com/1.1/account/verify_credentials.json',
|
||||
'include_email' => false,
|
||||
]);
|
||||
|
||||
$resolver->setDefined('x_auth_access_type');
|
||||
// @link https://dev.twitter.com/oauth/reference/post/oauth/request_token
|
||||
$resolver->setAllowedValues('x_auth_access_type', ['read', 'write']);
|
||||
// @link https://dev.twitter.com/rest/reference/get/account/verify_credentials
|
||||
$resolver->setAllowedTypes('include_email', 'bool');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Adrov Igor <nucleartux@gmail.com>
|
||||
* @author Vladislav Vlastovskiy <me@vlastv.ru>
|
||||
* @author Alexander Latushkin <alex@skazo4neg.ru>
|
||||
*/
|
||||
final class VkontakteResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'vkontakte';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'response.0.id',
|
||||
'nickname' => 'response.0.nickname',
|
||||
'firstname' => 'response.0.first_name',
|
||||
'lastname' => 'response.0.last_name',
|
||||
'realname' => ['response.0.last_name', 'response.0.first_name'],
|
||||
'profilepicture' => 'response.0.photo_medium',
|
||||
'email' => 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = [])
|
||||
{
|
||||
$url = $this->normalizeUrl($this->options['infos_url'], [
|
||||
'access_token' => $accessToken['access_token'],
|
||||
'fields' => $this->options['fields'],
|
||||
'name_case' => $this->options['name_case'],
|
||||
'v' => $this->options['api_version'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$response = $this->getUserResponse();
|
||||
$response->setResourceOwner($this);
|
||||
$response->setOAuthToken(new OAuthToken($accessToken));
|
||||
|
||||
$content = $this->doGetUserInformationRequest($url)->toArray(false);
|
||||
$content['email'] = $accessToken['email'] ?? null;
|
||||
|
||||
if (isset($content['response'][0]['screen_name'])) {
|
||||
$content['response'][0]['nickname'] = $content['response'][0]['screen_name'];
|
||||
}
|
||||
|
||||
$response->setData($content);
|
||||
|
||||
return $response;
|
||||
} catch (TransportExceptionInterface|JsonException $e) {
|
||||
throw new HttpTransportException('Error while sending HTTP request', $this->getName(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://oauth.vk.com/authorize',
|
||||
'access_token_url' => 'https://oauth.vk.com/access_token',
|
||||
'infos_url' => 'https://api.vk.com/method/users.get',
|
||||
'use_authorization_to_get_token' => false,
|
||||
|
||||
// Based on: https://vk.com/dev/constant_version_updates
|
||||
'api_version' => '5.131',
|
||||
|
||||
'scope' => 'email',
|
||||
|
||||
'use_commas_in_scope' => true,
|
||||
|
||||
'fields' => 'nickname,photo_medium,screen_name,email',
|
||||
'name_case' => null,
|
||||
]);
|
||||
|
||||
$fieldsNormalizer = function (Options $options, $value) {
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \is_array($value) ? implode(',', $value) : $value;
|
||||
};
|
||||
|
||||
$resolver->setNormalizer('fields', $fieldsNormalizer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
final class WindowsLiveResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'windows_live';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'name',
|
||||
'realname' => 'name',
|
||||
'firstname' => 'first_name',
|
||||
'lastname' => 'last_name',
|
||||
'email' => 'emails.account', // requires 'wl.emails' scope
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetTokenRequest($url, array $parameters = [])
|
||||
{
|
||||
return parent::httpRequest($url, http_build_query($parameters, '', '&'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function httpRequest($url, $content = null, array $headers = [], $method = null)
|
||||
{
|
||||
// Skip the Content-Type header in GenericOAuth2ResourceOwner::httpRequest
|
||||
return AbstractResourceOwner::httpRequest($url, $content, $headers, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://login.live.com/oauth20_authorize.srf',
|
||||
'access_token_url' => 'https://login.live.com/oauth20_token.srf',
|
||||
'infos_url' => 'https://apis.live.net/v5.0/me',
|
||||
|
||||
'scope' => 'wl.signin',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Joseph Bielawski <stloyd@gmail.com>
|
||||
*/
|
||||
final class WordpressResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'wordpress';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'ID',
|
||||
'nickname' => 'username',
|
||||
'realname' => 'display_name',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'avatar_URL',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://public-api.wordpress.com/oauth2/authorize',
|
||||
'access_token_url' => 'https://public-api.wordpress.com/oauth2/token',
|
||||
'infos_url' => 'https://public-api.wordpress.com/rest/v1/me',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author othillo <othillo@othillo.nl>
|
||||
*/
|
||||
final class XingResourceOwner extends GenericOAuth1ResourceOwner
|
||||
{
|
||||
public const TYPE = 'xing';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'users.0.id',
|
||||
'nickname' => 'users.0.display_name',
|
||||
'firstname' => 'users.0.first_name',
|
||||
'lastname' => 'users.0.last_name',
|
||||
'realname' => ['users.0.first_name', 'users.0.last_name'],
|
||||
'profilepicture' => 'users.0.photo_urls.large',
|
||||
'email' => 'users.0.active_email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://api.xing.com/v1/authorize',
|
||||
'request_token_url' => 'https://api.xing.com/v1/request_token',
|
||||
'access_token_url' => 'https://api.xing.com/v1/access_token',
|
||||
'infos_url' => 'https://api.xing.com/v1/users/me',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Tom <tomilett@gmail.com>
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
final class YahooResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'yahoo';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'sub',
|
||||
'nickname' => 'given_name',
|
||||
'realname' => 'name',
|
||||
'email' => 'email',
|
||||
'firstname' => 'given_name',
|
||||
'lastname' => 'family_name',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://api.login.yahoo.com/oauth2/request_auth',
|
||||
'request_token_url' => 'https://api.login.yahoo.com/oauth2/get_token',
|
||||
'access_token_url' => 'https://api.login.yahoo.com/oauth2/get_token',
|
||||
'infos_url' => 'https://api.login.yahoo.com/openid/v1/userinfo',
|
||||
|
||||
'realm' => 'yahooapis.com',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Anton Kamenschikov <wiistriker [at] gmail.com>
|
||||
*/
|
||||
final class YandexResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'yandex';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'id',
|
||||
'nickname' => 'display_name',
|
||||
'realname' => 'real_name',
|
||||
'email' => 'default_email',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doGetUserInformationRequest($url, array $parameters = [])
|
||||
{
|
||||
// Yandex require to pass the OAuth token as 'oauth_token' instead of 'access_token'
|
||||
return $this->httpRequest(str_replace('access_token', 'oauth_token', $url));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://oauth.yandex.ru/authorize',
|
||||
'access_token_url' => 'https://oauth.yandex.ru/token',
|
||||
'infos_url' => 'https://login.yandex.ru/info?format=json',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth\ResourceOwner;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @author Gennady Telegin <gtelegin@gmail.com>
|
||||
*/
|
||||
final class YoutubeResourceOwner extends GenericOAuth2ResourceOwner
|
||||
{
|
||||
public const TYPE = 'youtube';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected array $paths = [
|
||||
'identifier' => 'items.0.id',
|
||||
'nickname' => 'items.0.snippet.title',
|
||||
'realname' => 'items.0.snippet.title',
|
||||
'email' => 'email',
|
||||
'profilepicture' => 'items.0.snippet.thumbnails.high.url',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = [])
|
||||
{
|
||||
return parent::getAuthorizationUrl($redirectUri, array_merge([
|
||||
'access_type' => $this->options['access_type'],
|
||||
'approval_prompt' => $this->options['approval_prompt'],
|
||||
'request_visible_actions' => $this->options['request_visible_actions'],
|
||||
'hd' => $this->options['hd'],
|
||||
'prompt' => $this->options['prompt'],
|
||||
], $extraParameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'authorization_url' => 'https://accounts.google.com/o/oauth2/auth',
|
||||
'access_token_url' => 'https://accounts.google.com/o/oauth2/token',
|
||||
'revoke_token_url' => 'https://accounts.google.com/o/oauth2/revoke',
|
||||
'infos_url' => 'https://www.googleapis.com/youtube/v3/channels?part=id,snippet&mine=true',
|
||||
'scope' => 'https://www.googleapis.com/auth/youtube.readonly',
|
||||
|
||||
'access_type' => null,
|
||||
'approval_prompt' => null,
|
||||
'display' => null,
|
||||
// Identifying a particular hosted domain account to be accessed (for example, 'mycollege.edu')
|
||||
'hd' => null,
|
||||
'login_hint' => null,
|
||||
'prompt' => null,
|
||||
'request_visible_actions' => null,
|
||||
]);
|
||||
|
||||
$resolver
|
||||
// @link https://developers.google.com/accounts/docs/OAuth2WebServer#offline
|
||||
->setAllowedValues('access_type', ['online', 'offline', null])
|
||||
// sometimes we need to force for approval prompt (e.g. when we lost refresh token)
|
||||
->setAllowedValues('approval_prompt', ['force', 'auto', null])
|
||||
// @link https://developers.google.com/accounts/docs/OAuth2Login#authenticationuriparameters
|
||||
->setAllowedValues('display', ['page', 'popup', 'touch', 'wap', null])
|
||||
->setAllowedValues('login_hint', ['email address', 'sub', null])
|
||||
->setAllowedValues('prompt', [null, 'consent', 'select_account', null])
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the HWIOAuthBundle package.
|
||||
*
|
||||
* (c) Hardware Info <opensource@hardware.info>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace HWI\Bundle\OAuthBundle\OAuth;
|
||||
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Exception\HttpTransportException;
|
||||
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* ResourceOwnerInterface.
|
||||
*
|
||||
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
interface ResourceOwnerInterface
|
||||
{
|
||||
/**
|
||||
* Retrieves the user's information from an access_token.
|
||||
*
|
||||
* @param array $accessToken The access token
|
||||
* @param array $extraParameters An array of parameters to add to the url
|
||||
*
|
||||
* @return UserResponseInterface the wrapped response interface
|
||||
*
|
||||
* @throws HttpTransportException
|
||||
*/
|
||||
public function getUserInformation(array $accessToken, array $extraParameters = []);
|
||||
|
||||
/**
|
||||
* Returns the provider's authorization url.
|
||||
*
|
||||
* @param string $redirectUri The uri to redirect the client back to
|
||||
* @param array $extraParameters An array of parameters to add to the url
|
||||
*
|
||||
* @return string The authorization url
|
||||
*/
|
||||
public function getAuthorizationUrl($redirectUri, array $extraParameters = []);
|
||||
|
||||
/**
|
||||
* Retrieve an access token for a given code.
|
||||
*
|
||||
* @param HttpRequest $request The request object where is going to extract the code from
|
||||
* @param string $redirectUri The uri to redirect the client back to
|
||||
* @param array $extraParameters An array of parameters to add to the url
|
||||
*
|
||||
* @return array The access token
|
||||
*
|
||||
* @throws HttpTransportException
|
||||
*/
|
||||
public function getAccessToken(HttpRequest $request, $redirectUri, array $extraParameters = []);
|
||||
|
||||
/**
|
||||
* Check whatever CSRF token from request is valid or not.
|
||||
*
|
||||
* @param string|null $csrfToken
|
||||
*
|
||||
* @return bool True if CSRF token is valid
|
||||
*
|
||||
* @throws AuthenticationException When token is not valid
|
||||
*/
|
||||
public function isCsrfTokenValid($csrfToken);
|
||||
|
||||
/**
|
||||
* Return a name for the resource owner.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Retrieve an option by name.
|
||||
*
|
||||
* @param string $name The option name
|
||||
*
|
||||
* @return mixed The option value
|
||||
*
|
||||
* @throws \InvalidArgumentException When the option does not exist
|
||||
*/
|
||||
public function getOption($name);
|
||||
|
||||
/**
|
||||
* Checks whether the class can handle the request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handles(HttpRequest $request);
|
||||
|
||||
/**
|
||||
* Add extra paths to the configuration.
|
||||
*/
|
||||
public function addPaths(array $paths);
|
||||
|
||||
/**
|
||||
* @param string $refreshToken Refresh token
|
||||
* @param array $extraParameters An array of parameters to add to the url
|
||||
*/
|
||||
public function refreshAccessToken($refreshToken, array $extraParameters = []);
|
||||
|
||||
public function getState(): StateInterface;
|
||||
|
||||
public function storeState(?StateInterface $state = null);
|
||||
|
||||
public function addStateParameter(string $key, string $value): void;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user