├── .github
└── workflows
│ └── phpunit.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src
├── DependencyInjection
│ └── GuzzleBundleOAuth2Extension.php
├── GuzzleBundleOAuth2Plugin.php
├── Middleware
│ ├── CachedOAuthMiddleware.php
│ └── PersistentOAuthMiddleware.php
└── Resources
│ └── config
│ └── services.xml
└── tests
├── DependencyInjection
└── GuzzleBundleOAuth2ExtensionTest.php
└── GuzzleBundleOAuth2PluginTest.php
/.github/workflows/phpunit.yml:
--------------------------------------------------------------------------------
1 | name: PHPUnit
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches: [ master ]
7 |
8 | jobs:
9 | build:
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | os: [ubuntu-latest]
14 | php:
15 | - '7.2'
16 | - '8.3'
17 | symfony:
18 | - '5.0.*'
19 | - '5.4.*' # LTS
20 | - '6.0.*'
21 | - '7.0.*'
22 | exclude:
23 | - php: '7.2'
24 | symfony: '6.0.*' # requires PHP >=8.1
25 | - php: '7.2'
26 | symfony: '7.0.*' # requires PHP >=8.2
27 |
28 | runs-on: ${{ matrix.os }}
29 |
30 | env:
31 | SYMFONY: ${{ matrix.symfony }}
32 |
33 | steps:
34 | - uses: actions/checkout@v4
35 |
36 | - name: Install PHP
37 | uses: shivammathur/setup-php@v2
38 | with:
39 | php-version: ${{ matrix.php }}
40 | ini-values: date.timezone='UTC'
41 | tools: composer:v2
42 |
43 | - name: Require symfony
44 | run: composer --no-update require symfony/symfony:"${SYMFONY}"
45 |
46 | - name: Install dependencies
47 | run: |
48 | composer update
49 | vendor/bin/simple-phpunit install
50 | - name: Test
51 | run: |
52 | composer validate --strict --no-check-lock
53 | vendor/bin/simple-phpunit --coverage-text --verbose
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore potentially sensitive phpunit file
2 | /phpunit.xml
3 |
4 | # Ignore composer generated files
5 | /composer.lock
6 | /vendor/
7 |
8 | # Ignore generated files
9 | /build/
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Gregurco
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Guzzle Bundle OAuth2 Plugin
2 |
3 | [](https://travis-ci.org/gregurco/GuzzleBundleOAuth2Plugin)
4 | [](https://coveralls.io/r/gregurco/GuzzleBundleOAuth2Plugin)
5 | [](https://insight.sensiolabs.com/projects/eba4f2e6-2c2a-4e92-85b6-c32ab3ac3aa7)
6 |
7 | This plugin integrates [OAuth2][1] functionality into [Guzzle Bundle][2], a bundle for building RESTful web service clients.
8 |
9 | ----
10 |
11 | ## Prerequisites
12 | - PHP 7.2 or above
13 | - [Guzzle Bundle][2]
14 | - [guzzle-oauth2-plugin][3]
15 |
16 | ## Installation
17 |
18 | To install this bundle, run the command below on the command line and you will get the latest stable version from [Packagist][4].
19 |
20 | ``` bash
21 | composer require gregurco/guzzle-bundle-oauth2-plugin
22 | ```
23 |
24 | ## Usage
25 |
26 | ### Enable bundle
27 |
28 | Find next lines in `src/Kernel.php`:
29 |
30 | ```php
31 | foreach ($contents as $class => $envs) {
32 | if (isset($envs['all']) || isset($envs[$this->environment])) {
33 | yield new $class();
34 | }
35 | }
36 | ```
37 |
38 | and replace them by:
39 |
40 | ```php
41 | foreach ($contents as $class => $envs) {
42 | if (isset($envs['all']) || isset($envs[$this->environment])) {
43 | if ($class === \EightPoints\Bundle\GuzzleBundle\EightPointsGuzzleBundle::class) {
44 | yield new $class([
45 | new \Gregurco\Bundle\GuzzleBundleOAuth2Plugin\GuzzleBundleOAuth2Plugin(),
46 | ]);
47 | } else {
48 | yield new $class();
49 | }
50 | }
51 | }
52 | ```
53 |
54 | ### Basic configuration
55 |
56 | #### With default grant type (client)
57 |
58 | ``` yaml
59 | # app/config/config.yml
60 |
61 | eight_points_guzzle:
62 | clients:
63 | api_payment:
64 | base_url: "http://api.domain.tld"
65 |
66 | options:
67 | auth: oauth2
68 |
69 | # plugin settings
70 | plugin:
71 | oauth2:
72 | base_uri: "https://example.com"
73 | token_url: "/oauth/token"
74 | client_id: "test-client-id"
75 | client_secret: "test-client-secret" # optional
76 | scope: "administration"
77 | ```
78 |
79 | #### With password grant type
80 |
81 | ``` yaml
82 | # app/config/config.yml
83 |
84 | eight_points_guzzle:
85 | clients:
86 | api_payment:
87 | base_url: "http://api.domain.tld"
88 |
89 | options:
90 | auth: oauth2
91 |
92 | # plugin settings
93 | plugin:
94 | oauth2:
95 | base_uri: "https://example.com"
96 | token_url: "/oauth/token"
97 | client_id: "test-client-id"
98 | username: "johndoe"
99 | password: "A3ddj3w"
100 | scope: "administration"
101 | grant_type: "Sainsburys\\Guzzle\\Oauth2\\GrantType\\PasswordCredentials"
102 | ```
103 |
104 | #### With client credentials in body
105 |
106 | ``` yaml
107 | # app/config/config.yml
108 |
109 | eight_points_guzzle:
110 | clients:
111 | api_payment:
112 | base_url: "http://api.domain.tld"
113 |
114 | options:
115 | auth: oauth2
116 |
117 | # plugin settings
118 | plugin:
119 | oauth2:
120 | base_uri: "https://example.com"
121 | token_url: "/oauth/token"
122 | client_id: "test-client-id"
123 | scope: "administration"
124 | auth_location: "body"
125 | ```
126 |
127 | ### Options
128 |
129 | | Key | Description | Required | Example |
130 | | --- | --- | --- | --- |
131 | | base_uri | URL of oAuth2 server.| yes | https://example.com |
132 | | token_url | The path that will be concatenated with base_uri.
Default: `/oauth2/token`| no | /oauth/token |
133 | | client_id | The client identifier issued to the client during the registration process | yes | s6BhdRkqt3 |
134 | | client_secret | The client secret | no | 7Fjfp0ZBr1KtDRbnfVdmIw |
135 | | username | The resource owner username | for PasswordCredentials grant type | johndoe |
136 | | password | The resource owner password | for PasswordCredentials grant type | A3ddj3w |
137 | | auth_location | The place where to put client_id and client_secret in auth request.
Default: headers. Allowed values: body, headers. | no | body |
138 | | resource | The App ID URI of the web API (secured resource) | no | https://service.contoso.com/ |
139 | | private_key | Path to private key | for JwtBearer grant type | `"%kernel.root_dir%/path/to/private.key"` |
140 | | scope | One or more scope values indicating which parts of the user's account you wish to access | no | administration |
141 | | audience | | no | |
142 | | grant_type | Grant type class path. Class should implement GrantTypeInterface.
Default: `Sainsburys\\Guzzle\\Oauth2\\GrantType\\ClientCredentials` | no | `Sainsburys\\Guzzle\\Oauth2\\GrantType\\PasswordCredentials`
`Sainsburys\\Guzzle\\Oauth2\\GrantType\\AuthorizationCode`
`Sainsburys\\Guzzle\\Oauth2\\GrantType\\JwtBearer` |
143 | | persistent | Token will be stored in session unless grant_type is client credentials; in which case it will be stored in the app cache.
Default: false | no | |
144 | | retry_limit | How many times request will be repeated on failure.
Default: 5 | no | |
145 |
146 | See more information about middleware [here][3].
147 |
148 | ## License
149 |
150 | This middleware is licensed under the MIT License - see the LICENSE file for details
151 |
152 | [1]: http://www.xml.com/pub/a/2003/12/17/dive.html
153 | [2]: https://github.com/8p/EightPointsGuzzleBundle
154 | [3]: https://github.com/Sainsburys/guzzle-oauth2-plugin
155 | [4]: https://packagist.org/packages/gregurco/guzzle-bundle-oauth2-plugin
156 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gregurco/guzzle-bundle-oauth2-plugin",
3 | "type": "library",
4 | "description": "OAuth2 Plugin for Guzzle Bundle, a PHP HTTP client library and framework for building RESTful web service clients",
5 | "keywords": ["oauth2", "middleware", "plugin", "framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
6 | "homepage": "https://github.com/gregurco/GuzzleBundleOAuth2Plugin",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Gregurco Vlad",
11 | "email": "gregurco.vlad@gmail.com",
12 | "homepage": "https://github.com/gregurco"
13 | },
14 | {
15 | "name": "Community",
16 | "homepage": "https://github.com/gregurco/GuzzleBundleOAuth2Plugin/contributors"
17 | }
18 | ],
19 |
20 | "require": {
21 | "php": ">=7.2",
22 | "guzzlehttp/guzzle": "^6.5.8|^7.4.5",
23 | "eightpoints/guzzle-bundle": "^8.0",
24 | "symfony/http-kernel": "~5.0|~6.0|~7.0",
25 | "symfony/config": "~5.0|~6.0|~7.0",
26 | "symfony/dependency-injection": "~5.0|~6.0|~7.0",
27 | "symfony/expression-language": "~5.0|~6.0|~7.0",
28 | "sainsburys/guzzle-oauth2-plugin": "^3.0"
29 | },
30 |
31 | "require-dev": {
32 | "symfony/phpunit-bridge": "~5.0|~6.0|~7.0",
33 | "php-coveralls/php-coveralls": "^2.2"
34 | },
35 |
36 | "autoload": {
37 | "psr-4": {
38 | "Gregurco\\Bundle\\GuzzleBundleOAuth2Plugin\\": "src"
39 | }
40 | },
41 |
42 | "autoload-dev": {
43 | "psr-4": {
44 | "Gregurco\\Bundle\\GuzzleBundleOAuth2Plugin\\Tests\\": "tests"
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ./tests
15 |
16 |
17 |
18 |
19 |
20 | ./src
21 |
22 | ./src/Resources/
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/DependencyInjection/GuzzleBundleOAuth2Extension.php:
--------------------------------------------------------------------------------
1 | load('services.xml');
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/GuzzleBundleOAuth2Plugin.php:
--------------------------------------------------------------------------------
1 | load($configs, $container);
33 | }
34 |
35 | /**
36 | * @param array $config
37 | * @param ContainerBuilder $container
38 | * @param string $clientName
39 | * @param Definition $handler
40 | */
41 | public function loadForClient(array $config, ContainerBuilder $container, string $clientName, Definition $handler) : void
42 | {
43 | if ($config['enabled']) {
44 | $middlewareConfig = [
45 | PasswordCredentials::CONFIG_USERNAME => $config['username'],
46 | PasswordCredentials::CONFIG_PASSWORD => $config['password'],
47 | GrantTypeBase::CONFIG_CLIENT_ID => $config['client_id'],
48 | GrantTypeBase::CONFIG_CLIENT_SECRET => $config['client_secret'],
49 | GrantTypeBase::CONFIG_TOKEN_URL => $config['token_url'],
50 | GrantTypeBase::CONFIG_AUTH_LOCATION => $config['auth_location'],
51 | GrantTypeBase::CONFIG_RESOURCE => $config['resource'],
52 | JwtBearer::CONFIG_PRIVATE_KEY => null,
53 | 'scope' => $config['scope'],
54 | 'audience' => $config['audience'],
55 | ];
56 |
57 | if ($config['private_key']) {
58 | // Define Client
59 | $privateKeyDefinitionName = sprintf('guzzle_bundle_oauth2_plugin.private_key.%s', $clientName);
60 | $privateKeyDefinition = new Definition(\SplFileObject::class);
61 | $privateKeyDefinition->addArgument($config['private_key']);
62 | $privateKeyDefinition->setPublic(true);
63 | $container->setDefinition($privateKeyDefinitionName, $privateKeyDefinition);
64 |
65 | $middlewareConfig[JwtBearer::CONFIG_PRIVATE_KEY] = new Reference($privateKeyDefinitionName);
66 | }
67 |
68 | // Define Client
69 | $oauthClientDefinitionName = sprintf('guzzle_bundle_oauth2_plugin.client.%s', $clientName);
70 | $oauthClientDefinition = new Definition(Client::class);
71 | $oauthClientDefinition->addArgument(['base_uri' => $config['base_uri']]);
72 | $oauthClientDefinition->setPublic(true);
73 | $container->setDefinition($oauthClientDefinitionName, $oauthClientDefinition);
74 |
75 | // Define password credentials
76 | $passwordCredentialsDefinitionName = sprintf('guzzle_bundle_oauth2_plugin.password_credentials.%s', $clientName);
77 | $passwordCredentialsDefinition = new Definition($config['grant_type']);
78 | $passwordCredentialsDefinition->addArgument(new Reference($oauthClientDefinitionName));
79 | $passwordCredentialsDefinition->addArgument($middlewareConfig);
80 | $passwordCredentialsDefinition->setPublic(true);
81 | $container->setDefinition($passwordCredentialsDefinitionName, $passwordCredentialsDefinition);
82 |
83 | // Define refresh token
84 | $refreshTokenDefinitionName = sprintf('guzzle_bundle_oauth2_plugin.refresh_token.%s', $clientName);
85 | $refreshTokenDefinition = new Definition(RefreshToken::class);
86 | $refreshTokenDefinition->addArgument(new Reference($oauthClientDefinitionName));
87 | $refreshTokenDefinition->addArgument($middlewareConfig);
88 | $refreshTokenDefinition->setPublic(true);
89 | $container->setDefinition($refreshTokenDefinitionName, $refreshTokenDefinition);
90 |
91 | //Define middleware
92 | $oAuth2MiddlewareDefinitionName = sprintf('guzzle_bundle_oauth2_plugin.middleware.%s', $clientName);
93 | if ($config['persistent']) {
94 | if ($config['grant_type'] === ClientCredentials::class) {
95 | $oAuth2MiddlewareDefinition = new Definition('%guzzle_bundle_oauth2_plugin.cached_middleware.class%');
96 | $oAuth2MiddlewareDefinition->setArguments(
97 | [
98 | new Reference($oauthClientDefinitionName),
99 | new Reference($passwordCredentialsDefinitionName),
100 | new Reference($refreshTokenDefinitionName),
101 | new Reference(AdapterInterface::class),
102 | $clientName
103 | ]
104 | );
105 | } else {
106 | $oAuth2MiddlewareDefinition = new Definition('%guzzle_bundle_oauth2_plugin.persistent_middleware.class%');
107 | $oAuth2MiddlewareDefinition->setArguments(
108 | [
109 | new Reference($oauthClientDefinitionName),
110 | new Reference($passwordCredentialsDefinitionName),
111 | new Reference($refreshTokenDefinitionName),
112 | new Reference('session'),
113 | $clientName
114 | ]
115 | );
116 | }
117 | } else {
118 | $oAuth2MiddlewareDefinition = new Definition('%guzzle_bundle_oauth2_plugin.middleware.class%');
119 | $oAuth2MiddlewareDefinition->setArguments([
120 | new Reference($oauthClientDefinitionName),
121 | new Reference($passwordCredentialsDefinitionName),
122 | new Reference($refreshTokenDefinitionName)
123 | ]);
124 | }
125 |
126 | $oAuth2MiddlewareDefinition->setPublic(true);
127 | $container->setDefinition($oAuth2MiddlewareDefinitionName, $oAuth2MiddlewareDefinition);
128 |
129 | $onBeforeExpression = new Expression(sprintf('service("%s").onBefore()', $oAuth2MiddlewareDefinitionName));
130 | $onFailureExpression = new Expression(sprintf(
131 | 'service("%s").onFailure(%d)',
132 | $oAuth2MiddlewareDefinitionName,
133 | $config['retry_limit']
134 | ));
135 |
136 | $handler->addMethodCall('push', [$onBeforeExpression]);
137 | $handler->addMethodCall('push', [$onFailureExpression]);
138 | }
139 | }
140 |
141 | /**
142 | * @param ArrayNodeDefinition $pluginNode
143 | */
144 | public function addConfiguration(ArrayNodeDefinition $pluginNode) : void
145 | {
146 | $pluginNode
147 | ->canBeEnabled()
148 | ->validate()
149 | ->ifTrue(function (array $config) {
150 | return $config['enabled'] === true && empty($config['base_uri']);
151 | })
152 | ->thenInvalid('base_uri is required')
153 | ->end()
154 | ->validate()
155 | ->ifTrue(function (array $config) {
156 | return $config['enabled'] === true && empty($config['client_id']);
157 | })
158 | ->thenInvalid('client_id is required')
159 | ->end()
160 | ->validate()
161 | ->ifTrue(function (array $config) {
162 | return $config['enabled'] === true &&
163 | $config['grant_type'] === PasswordCredentials::class &&
164 | (empty($config['username']) || empty($config['password']));
165 | })
166 | ->thenInvalid('username and password are required')
167 | ->end()
168 | ->validate()
169 | ->ifTrue(function (array $config) {
170 | return $config['enabled'] === true &&
171 | $config['grant_type'] === JwtBearer::class &&
172 | empty($config['private_key']);
173 | })
174 | ->thenInvalid('private_key is required')
175 | ->end()
176 | ->children()
177 | ->scalarNode('base_uri')->defaultNull()->end()
178 | ->scalarNode('username')->defaultNull()->end()
179 | ->scalarNode('password')->defaultNull()->end()
180 | ->scalarNode('client_id')->defaultNull()->end()
181 | ->scalarNode('client_secret')->defaultNull()->end()
182 | ->scalarNode('token_url')->defaultNull()->end()
183 | ->scalarNode('scope')->defaultNull()->end()
184 | ->scalarNode('audience')->defaultNull()->end()
185 | ->scalarNode('resource')->defaultNull()->end()
186 | ->scalarNode('private_key')->defaultNull()->end()
187 | ->scalarNode('auth_location')
188 | ->defaultValue('headers')
189 | ->validate()
190 | ->ifNotInArray(['headers', 'body'])
191 | ->thenInvalid('Invalid auth_location %s. Allowed values: headers, body.')
192 | ->end()
193 | ->end()
194 | ->scalarNode('grant_type')
195 | ->defaultValue(ClientCredentials::class)
196 | ->validate()
197 | ->ifTrue(function ($v) {
198 | return !is_subclass_of($v, GrantTypeInterface::class);
199 | })
200 | ->thenInvalid(sprintf('Use instance of %s in grant_type', GrantTypeInterface::class))
201 | ->end()
202 | ->end()
203 | ->booleanNode('persistent')->defaultFalse()->end()
204 | ->booleanNode('retry_limit')->defaultValue(5)->end()
205 | ->end();
206 | }
207 |
208 | /**
209 | * @return string
210 | */
211 | public function getPluginName() : string
212 | {
213 | return 'oauth2';
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/Middleware/CachedOAuthMiddleware.php:
--------------------------------------------------------------------------------
1 | cacheClient = $cacheClient;
44 | $this->clientName = $clientName;
45 | }
46 |
47 | /**
48 | * Get a new access token.
49 | *
50 | * @throws InvalidArgumentException
51 | *
52 | * @return AccessToken|null
53 | */
54 | protected function acquireAccessToken()
55 | {
56 | $token = parent::acquireAccessToken();
57 |
58 | $this->cacheToken($token);
59 |
60 | return $token;
61 | }
62 |
63 | /**
64 | * cacheToken sets the token in the cache adapter
65 | *
66 | * @param AccessToken $token
67 | *
68 | * @throws InvalidArgumentException
69 | */
70 | protected function cacheToken(AccessToken $token)
71 | {
72 | $item = $this->cacheClient->getItem(sprintf('oauth.token.%s', $this->clientName));
73 |
74 | $expires = $token->getExpires();
75 |
76 | $item->set(
77 | [
78 | 'token' => $token->getToken(),
79 | 'type' => $token->getType(),
80 | 'data' => array_merge($token->getData(), ['expires' => $expires->getTimestamp()]),
81 | ]
82 | );
83 |
84 | if ($expires) {
85 | $item->expiresAt($expires->sub(\DateInterval::createFromDateString('10 seconds')));
86 | }
87 |
88 | $this->cacheClient->saveDeferred($item);
89 | }
90 |
91 | /**
92 | * getAccessToken will get the oauth token from the cache if available
93 | *
94 | * @throws \Exception
95 | * @throws InvalidArgumentException
96 | *
97 | * @return null|AccessToken
98 | */
99 | public function getAccessToken()
100 | {
101 | if ($this->accessToken === null) {
102 | $this->restoreTokenFromCache();
103 | }
104 |
105 | return parent::getAccessToken();
106 | }
107 |
108 | /**
109 | * restoreTokenFromCache
110 | *
111 | * @throws \Exception
112 | * @throws InvalidArgumentException
113 | */
114 | protected function restoreTokenFromCache()
115 | {
116 | $item = $this->cacheClient->getItem(sprintf('oauth.token.%s', $this->clientName));
117 |
118 | if ($item->isHit()) {
119 | $tokenData = $item->get();
120 |
121 | $this->setAccessToken(
122 | new AccessToken(
123 | $tokenData['token'],
124 | $tokenData['type'],
125 | $tokenData['data']
126 | )
127 | );
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/Middleware/PersistentOAuthMiddleware.php:
--------------------------------------------------------------------------------
1 | session = $session;
39 | $this->clientName = $clientName;
40 | }
41 |
42 | /**
43 | * Get a new access token.
44 | *
45 | * @return AccessToken|null
46 | */
47 | protected function acquireAccessToken()
48 | {
49 | $token = parent::acquireAccessToken();
50 |
51 | $this->storeTokenInSession($token);
52 |
53 | return $token;
54 | }
55 |
56 | /**
57 | * @param AccessToken $token
58 | */
59 | protected function storeTokenInSession(AccessToken $token)
60 | {
61 | $expires = $token->getExpires();
62 |
63 | $this->session->start();
64 | $this->session->set($this->clientName . '_token', [
65 | 'token' => $token->getToken(),
66 | 'type' => $token->getType(),
67 | 'data' => array_merge($token->getData(), ['expires' => $expires->getTimestamp()]),
68 | ]);
69 | $this->session->save();
70 | }
71 |
72 | /**
73 | * @return null|AccessToken
74 | */
75 | public function getAccessToken()
76 | {
77 | if ($this->accessToken === null) {
78 | $this->restoreTokenFromSession();
79 | }
80 |
81 | return parent::getAccessToken();
82 | }
83 |
84 | protected function restoreTokenFromSession()
85 | {
86 | if ($this->session->has($this->clientName . '_token')) {
87 | $sessionTokenData = $this->session->get($this->clientName . '_token');
88 |
89 | $this->setAccessToken(new AccessToken(
90 | $sessionTokenData['token'],
91 | $sessionTokenData['type'],
92 | $sessionTokenData['data']
93 | ));
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Resources/config/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Sainsburys\Guzzle\Oauth2\Middleware\OAuthMiddleware
8 | Gregurco\Bundle\GuzzleBundleOAuth2Plugin\Middleware\PersistentOAuthMiddleware
9 | Gregurco\Bundle\GuzzleBundleOAuth2Plugin\Middleware\CachedOAuthMiddleware
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/DependencyInjection/GuzzleBundleOAuth2ExtensionTest.php:
--------------------------------------------------------------------------------
1 | load([], $container);
18 |
19 | $this->assertTrue($container->hasParameter('guzzle_bundle_oauth2_plugin.middleware.class'));
20 | $this->assertEquals(
21 | OAuthMiddleware::class,
22 | $container->getParameter('guzzle_bundle_oauth2_plugin.middleware.class')
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/GuzzleBundleOAuth2PluginTest.php:
--------------------------------------------------------------------------------
1 | plugin = new GuzzleBundleOAuth2Plugin();
32 | }
33 |
34 | public function testSubClassesOfPlugin() : void
35 | {
36 | $this->assertInstanceOf(PluginInterface::class, $this->plugin);
37 | $this->assertInstanceOf(Bundle::class, $this->plugin);
38 | }
39 |
40 | public function testAddConfiguration() : void
41 | {
42 | $arrayNode = new ArrayNodeDefinition('node');
43 |
44 | $this->plugin->addConfiguration($arrayNode);
45 |
46 | $node = $arrayNode->getNode();
47 |
48 | $this->assertFalse($node->isRequired());
49 | $this->assertTrue($node->hasDefaultValue());
50 | $this->assertSame(
51 | [
52 | 'enabled' => false,
53 | 'base_uri' => null,
54 | 'username' => null,
55 | 'password' => null,
56 | 'client_id' => null,
57 | 'client_secret' => null,
58 | 'token_url' => null,
59 | 'scope' => null,
60 | 'audience' => null,
61 | 'resource' => null,
62 | 'private_key' => null,
63 | 'auth_location' => 'headers',
64 | 'grant_type' => ClientCredentials::class,
65 | 'persistent' => false,
66 | 'retry_limit' => 5,
67 | ],
68 | $node->getDefaultValue()
69 | );
70 | }
71 |
72 | public function testGetPluginName() : void
73 | {
74 | $this->assertEquals('oauth2', $this->plugin->getPluginName());
75 | }
76 |
77 | public function testLoad() : void
78 | {
79 | $container = new ContainerBuilder();
80 |
81 | $this->plugin->load([], $container);
82 |
83 | $this->assertTrue($container->hasParameter('guzzle_bundle_oauth2_plugin.middleware.class'));
84 | $this->assertEquals(
85 | OAuthMiddleware::class,
86 | $container->getParameter('guzzle_bundle_oauth2_plugin.middleware.class')
87 | );
88 | }
89 |
90 | public function testLoadForClient() : void
91 | {
92 | $handler = new Definition();
93 | $container = new ContainerBuilder();
94 |
95 | $this->plugin->loadForClient(
96 | [
97 | 'enabled' => true,
98 | 'base_uri' => 'https://example.com',
99 | 'token_url' => '/oauth/token',
100 | 'username' => null,
101 | 'password' => null,
102 | 'client_id' => 'test-client-id',
103 | 'client_secret' => '',
104 | 'scope' => 'administration',
105 | 'audience' => null,
106 | 'resource' => null,
107 | 'private_key' => null,
108 | 'auth_location' => 'headers',
109 | 'grant_type' => ClientCredentials::class,
110 | 'persistent' => false,
111 | 'retry_limit' => 5,
112 | ],
113 | $container, 'api_payment', $handler
114 | );
115 |
116 | $this->assertTrue($container->hasDefinition('guzzle_bundle_oauth2_plugin.middleware.api_payment'));
117 | $this->assertCount(2, $handler->getMethodCalls());
118 |
119 | $clientMiddlewareDefinition = $container->getDefinition('guzzle_bundle_oauth2_plugin.middleware.api_payment');
120 | $this->assertCount(3, $clientMiddlewareDefinition->getArguments());
121 | }
122 |
123 | public function testLoadForClientWithPrivateKey() : void
124 | {
125 | $handler = new Definition();
126 | $container = new ContainerBuilder();
127 |
128 | $this->plugin->loadForClient(
129 | [
130 | 'enabled' => true,
131 | 'base_uri' => 'https://example.com',
132 | 'token_url' => '/oauth/token',
133 | 'username' => null,
134 | 'password' => null,
135 | 'client_id' => 'test-client-id',
136 | 'client_secret' => '',
137 | 'scope' => 'administration',
138 | 'audience' => null,
139 | 'resource' => null,
140 | 'private_key' => '/path/to/private.key',
141 | 'auth_location' => 'headers',
142 | 'grant_type' => JwtBearer::class,
143 | 'persistent' => false,
144 | 'retry_limit' => 5,
145 | ],
146 | $container, 'api_payment', $handler
147 | );
148 |
149 | $this->assertTrue($container->hasDefinition('guzzle_bundle_oauth2_plugin.middleware.api_payment'));
150 | $this->assertCount(2, $handler->getMethodCalls());
151 |
152 | $clientMiddlewareDefinition = $container->getDefinition('guzzle_bundle_oauth2_plugin.middleware.api_payment');
153 | $this->assertCount(3, $clientMiddlewareDefinition->getArguments());
154 |
155 | $this->assertTrue($container->hasDefinition('guzzle_bundle_oauth2_plugin.private_key.api_payment'));
156 | $clientMiddlewareDefinition = $container->getDefinition('guzzle_bundle_oauth2_plugin.private_key.api_payment');
157 | $this->assertCount(1, $clientMiddlewareDefinition->getArguments());
158 | $this->assertEquals('/path/to/private.key', $clientMiddlewareDefinition->getArgument(0));
159 | }
160 |
161 | /**
162 | * @dataProvider provideValidConfigurationData
163 | *
164 | * @param array $pluginConfiguration
165 | */
166 | public function testAddConfigurationWithData(array $pluginConfiguration) : void
167 | {
168 | $config = [
169 | 'eight_points_guzzle' => [
170 | 'clients' => [
171 | 'test_client' => [
172 | 'plugin' => [
173 | 'oauth2' => $pluginConfiguration,
174 | ]
175 | ]
176 | ]
177 | ]
178 | ];
179 |
180 | $processor = new Processor();
181 | $processedConfig = $processor->processConfiguration(new Configuration('eight_points_guzzle', false, [new GuzzleBundleOAuth2Plugin()]), $config);
182 |
183 | $this->assertIsArray($processedConfig);
184 | }
185 |
186 | /**
187 | * @return array
188 | */
189 | public function provideValidConfigurationData() : array
190 | {
191 | return [
192 | 'plugin is disabled' => [[
193 | 'enabled' => false,
194 | ]],
195 | 'plugin is enabled' => [[
196 | 'enabled' => true,
197 | 'base_uri' => 'https://example.com',
198 | 'client_id' => 's6BhdRkqt3',
199 | ]],
200 | 'PasswordCredentials in grant_type' => [[
201 | 'base_uri' => 'https://example.com',
202 | 'client_id' => 's6BhdRkqt3',
203 | 'username' => 'johndoe',
204 | 'password' => 'A3ddj3w',
205 | 'grant_type' => PasswordCredentials::class,
206 | ]],
207 | 'ClientCredentials in grant_type' => [[
208 | 'base_uri' => 'https://example.com',
209 | 'client_id' => 's6BhdRkqt3',
210 | 'grant_type' => ClientCredentials::class,
211 | ]],
212 | 'RefreshToken in grant_type' => [[
213 | 'base_uri' => 'https://example.com',
214 | 'client_id' => 's6BhdRkqt3',
215 | 'grant_type' => RefreshToken::class,
216 | ]],
217 | 'JwtBearer in grant_type' => [[
218 | 'base_uri' => 'https://example.com',
219 | 'client_id' => 's6BhdRkqt3',
220 | 'private_key' => '/path/to/private/key',
221 | 'grant_type' => JwtBearer::class,
222 | ]],
223 | 'headers in auth_location' => [[
224 | 'base_uri' => 'https://example.com',
225 | 'client_id' => 's6BhdRkqt3',
226 | 'auth_location' => 'headers',
227 | ]],
228 | 'body in auth_location' => [[
229 | 'base_uri' => 'https://example.com',
230 | 'client_id' => 's6BhdRkqt3',
231 | 'auth_location' => 'body',
232 | ]],
233 | ];
234 | }
235 |
236 | /**
237 | * @dataProvider provideInvalidConfigurationData
238 | *
239 | * @param array $pluginConfiguration
240 | * @param string $message
241 | */
242 | public function testAddConfigurationWithInvalidData(array $pluginConfiguration, string $message) : void
243 | {
244 | $this->expectException(InvalidConfigurationException::class);
245 | $this->expectExceptionMessage($message);
246 |
247 | $config = [
248 | 'eight_points_guzzle' => [
249 | 'clients' => [
250 | 'test_client' => [
251 | 'plugin' => [
252 | 'oauth2' => $pluginConfiguration,
253 | ]
254 | ]
255 | ]
256 | ]
257 | ];
258 |
259 | $processor = new Processor();
260 | $processor->processConfiguration(new Configuration('eight_points_guzzle', false, [new GuzzleBundleOAuth2Plugin()]), $config);
261 | }
262 |
263 | /**
264 | * @return array
265 | */
266 | public function provideInvalidConfigurationData() : array
267 | {
268 | return [
269 | 'without base_uri' => [
270 | 'config' => [
271 | 'enabled' => true,
272 | 'client_id' => 's6BhdRkqt3',
273 | ],
274 | 'exception message' => 'base_uri is required',
275 | ],
276 | 'without client_id' => [
277 | 'config' => [
278 | 'enabled' => true,
279 | 'base_uri' => 'https://example.com',
280 | ],
281 | 'exception message' => 'client_id is required',
282 | ],
283 | 'invalid type in grant_type' => [
284 | 'config' => [
285 | 'base_uri' => 'https://example.com',
286 | 'client_id' => 's6BhdRkqt3',
287 | 'grant_type' => true,
288 | ],
289 | 'exception message' => sprintf('Use instance of %s in grant_type', GrantTypeInterface::class),
290 | ],
291 | 'invalid class in grant_type' => [
292 | 'config' => [
293 | 'base_uri' => 'https://example.com',
294 | 'client_id' => 's6BhdRkqt3',
295 | 'grant_type' => \stdClass::class,
296 | ],
297 | 'exception message' => sprintf('Use instance of %s in grant_type', GrantTypeInterface::class),
298 | ],
299 | 'invalid auth_location' => [
300 | 'config' => [
301 | 'base_uri' => 'https://example.com',
302 | 'client_id' => 's6BhdRkqt3',
303 | 'auth_location' => 'somewhere',
304 | ],
305 | 'exception message' => 'Invalid auth_location "somewhere". Allowed values: headers, body.',
306 | ],
307 | 'PasswordCredentials grant type without username' => [
308 | 'config' => [
309 | 'base_uri' => 'https://example.com',
310 | 'client_id' => 's6BhdRkqt3',
311 | 'password' => 'A3ddj3w',
312 | 'grant_type' => PasswordCredentials::class,
313 | ],
314 | 'exception message' => 'username and password are required',
315 | ],
316 | 'PasswordCredentials grant type without password' => [
317 | 'config' => [
318 | 'base_uri' => 'https://example.com',
319 | 'client_id' => 's6BhdRkqt3',
320 | 'username' => 'johndoe',
321 | 'grant_type' => PasswordCredentials::class,
322 | ],
323 | 'exception message' => 'username and password are required',
324 | ],
325 | 'JwtBearer grant type without private_key' => [
326 | 'config' => [
327 | 'base_uri' => 'https://example.com',
328 | 'client_id' => 's6BhdRkqt3',
329 | 'grant_type' => JwtBearer::class,
330 | ],
331 | 'exception message' => 'private_key is required',
332 | ],
333 | ];
334 | }
335 | }
336 |
--------------------------------------------------------------------------------