├── .gitmodules
├── tests
├── bootstrap.php
└── OAuth2
│ └── Tests
│ ├── Strategy
│ ├── BaseTest.php
│ ├── PasswordTest.php
│ └── AuthCodeTest.php
│ ├── TestCase.php
│ ├── ResponseTest.php
│ ├── AccessTokenTest.php
│ └── ClientTest.php
├── .travis.yml
├── .gitignore
├── src
└── OAuth2
│ ├── Strategy
│ ├── Base.php
│ ├── Password.php
│ └── AuthCode.php
│ ├── Error.php
│ ├── Response.php
│ ├── Client.php
│ └── AccessToken.php
├── phpunit.xml.dist
├── composer.json
└── README.md
/.gitmodules:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | setExpectedException('\InvalidArgumentException');
14 | new \OAuth2\Strategy\Base;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/OAuth2/Strategy/Base.php:
--------------------------------------------------------------------------------
1 | client = $client;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 | tests/OAuth2/
17 |
18 |
19 |
20 |
21 |
22 | src/OAuth2/
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/OAuth2/Tests/TestCase.php:
--------------------------------------------------------------------------------
1 | getMock('\OAuth2\Client', ['getResponse'], [$client_id, $client_secret, $opts]);
19 |
20 | // configure client stub
21 | $client->expects($this->any())
22 | ->method('getResponse')
23 | ->will($this->returnCallback([$this, 'mockGetResponse']));
24 |
25 | return $client;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/OAuth2/Error.php:
--------------------------------------------------------------------------------
1 | error = $this;
19 | $this->response = $response;
20 |
21 | $parsedResponse = $response->parse();
22 | if (is_array($parsedResponse)) {
23 | $this->code = isset($parsedResponse['error']) ? $parsedResponse['error'] : 0;
24 | $this->message = isset($parsedResponse['error_description']) ? $parsedResponse['error_description'] : null;
25 | }
26 | }
27 |
28 | /**
29 | * response getter
30 | *
31 | * @return OAuth2\Response
32 | */
33 | public function getResponse()
34 | {
35 | return $this->response;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/OAuth2/Strategy/Password.php:
--------------------------------------------------------------------------------
1 | 'password'
27 | , 'username' => $username
28 | , 'password' => $password
29 | ), $params);
30 | return $this->client->getToken($params, $opts);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "keeguon/oauth2-php"
3 | , "type": "library"
4 | , "description": "A library to consume services using the OAuth 2 security scheme."
5 | , "keywords": ["oauth2"]
6 | , "homepage": "https://github.com/Keeguon/oauth2-php"
7 | , "version": "1.3.6"
8 | , "license": "MIT"
9 | , "authors": [
10 | {
11 | "name": "Félix Bellanger"
12 | , "email": "felix.bellanger@gmail.com"
13 | , "homepage": "https://github.com/Keeguon"
14 | },
15 | {
16 | "name": "Martin Jantošovič"
17 | , "homepage": "https://github.com/Mordred"
18 | },
19 | {
20 | "name": "Cyrus David"
21 | , "email": "david@jcyr.us"
22 | , "homepage": "https://github.com/vohof"
23 | }
24 | ]
25 | , "require": {
26 | "php": ">=5.4"
27 | , "guzzlehttp/guzzle": "~5.0"
28 | }
29 | , "autoload": {
30 | "psr-0": {
31 | "OAuth2\\Tests": "tests/"
32 | , "OAuth2": "src/"
33 | }
34 | }
35 | , "require-dev": {
36 | "symfony/class-loader": "*"
37 | , "phpunit/phpunit": "*"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/OAuth2/Strategy/AuthCode.php:
--------------------------------------------------------------------------------
1 | 'code'
17 | , 'client_id' => $this->client->getId()
18 | ), $params);
19 | }
20 |
21 | /**
22 | * The authorization URL endpoint of the provider
23 | *
24 | * @param array $params Additional query parameters for the URL
25 | * @return string
26 | */
27 | public function authorizeUrl($params = array())
28 | {
29 | $params = array_merge($this->authorizeParams(), $params);
30 | return $this->client->authorizeUrl($params);
31 | }
32 |
33 | /**
34 | * Retrieve an access token given the specified validation code.
35 | *
36 | * @param string $code The Authorization Code value
37 | * @param array $params Additional params
38 | * @param array $opts Options
39 | */
40 | public function getToken($code, $params = array(), $opts = array())
41 | {
42 | $params = array_merge(array(
43 | 'grant_type' => 'authorization_code'
44 | , 'code' => $code
45 | ), $params);
46 | return $this->client->getToken($params, $opts);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/OAuth2/Response.php:
--------------------------------------------------------------------------------
1 | response = $response;
15 | $this->parseMode = $parseMode;
16 | }
17 |
18 | /**
19 | * response getter
20 | *
21 | * @return Guzzle\Http\Message\Response
22 | */
23 | public function getResponse()
24 | {
25 | return $this->response;
26 | }
27 |
28 | public function headers()
29 | {
30 | return $this->response->getHeaders();
31 | }
32 |
33 | public function status()
34 | {
35 | return $this->response->getStatusCode();
36 | }
37 |
38 | public function body()
39 | {
40 | return (string) $this->response->getBody();
41 | }
42 |
43 | public function parse()
44 | {
45 | $parsed = null;
46 |
47 | switch ($this->parseMode) {
48 | case 'json':
49 | $parsed = json_decode($this->body(), true);
50 | break;
51 |
52 | case 'query':
53 | parse_str($this->body(), $parsed);
54 | break;
55 |
56 | case 'automatic':
57 | default:
58 | $types = array('application/json', 'text/javascript');
59 | $content_type = $this->content_type();
60 |
61 | foreach ($types as $type) {
62 | if (stripos($content_type, $type) !== false) {
63 | $parsed = json_decode($this->body(), true);
64 | break;
65 | }
66 | }
67 |
68 | if (stripos($content_type, "application/x-www-form-urlencoded") !== false) {
69 | parse_str($this->body(), $parsed);
70 | }
71 | break;
72 | }
73 |
74 | return $parsed;
75 | }
76 |
77 | public function content_type()
78 | {
79 | return $this->response->getHeader('Content-Type');
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OAuth2-PHP [](http://travis-ci.org/Keeguon/oauth2-php)
2 |
3 |
4 | A PHP library aimed to consume services using OAuth 2 as a security scheme.
5 |
6 |
7 | ## Dependencies
8 |
9 | * PHP >=5.3.2
10 | * Guzzle
11 |
12 |
13 | ## Installation
14 |
15 | ### composer
16 |
17 | To install OAuth2-PHP with composer you simply need to create a composer.json in your project root and add:
18 |
19 | ```json
20 | {
21 | "require": {
22 | "keeguon/oauth2-php": ">=1.0.0"
23 | }
24 | }
25 | ```
26 |
27 | Then run
28 |
29 | ```bash
30 | $ wget -nc http://getcomposer.org/composer.phar
31 | $ php composer.phar install
32 | ```
33 |
34 | You have now OAuth2-PHP installed in vendor/keeguon/oauth2-php
35 |
36 | And an handy autoload file to include in you project in vendor/.composer/autoload.php
37 |
38 |
39 | ## Testing
40 |
41 | The library is fully tested with PHPUnit for unit tests. To run tests you need PHPUnit which can be installed using the project dependencies as follows:
42 |
43 | ```bash
44 | $ php composer.phar install --dev
45 | ```
46 |
47 | Then to run the test suites
48 |
49 | ```bash
50 | $ vendor/bin/phpunit
51 | ```
52 |
53 |
54 | ## License
55 |
56 | Copyright (c) 2012 Félix Bellanger
57 |
58 | 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:
59 |
60 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
61 |
62 | 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.
63 |
--------------------------------------------------------------------------------
/tests/OAuth2/Tests/Strategy/PasswordTest.php:
--------------------------------------------------------------------------------
1 | client = $this->getClientStub('abc', 'def', array('site' => 'https://api.example.com'));
19 |
20 | // create password
21 | $this->password = new \OAuth2\Strategy\Password($this->client);
22 | }
23 |
24 | protected function tearDown()
25 | {
26 | unset($this->access);
27 | unset($this->client);
28 | unset($this->mode);
29 | unset($this->password);
30 | }
31 |
32 | /**
33 | * @covers OAuth2\Strategy\Password::authorize_url()
34 | */
35 | public function testAuthorizeUrl()
36 | {
37 | $this->setExpectedException('\ErrorException', 'The authorization endpoint is not used in this strategy.');
38 | $this->password->authorizeUrl();
39 | }
40 |
41 | /**
42 | * @covers OAuth2\Strategy\Password::get_token()
43 | */
44 | public function testGetToken()
45 | {
46 | foreach(array('json', 'formencoded') as $mode) {
47 | // get_token (mode)
48 | $this->mode = $mode;
49 | $this->access = $this->password->getToken('username', 'password');
50 |
51 | // returns AccessToken with same Client
52 | $this->assertEquals($this->client, $this->access->getClient());
53 |
54 | // returns AccessToken with $token
55 | $this->assertEquals('salmon', $this->access->getToken());
56 |
57 | // returns AccessToken with $refresh_token
58 | $this->assertEquals('trout', $this->access->getRefreshToken());
59 |
60 | // returns AccessToken with $expires_in
61 | $this->assertEquals(600, $this->access->getExpiresIn());
62 | }
63 | }
64 |
65 | /**
66 | * Intercept all OAuth2\Client::getResponse() calls and mock their responses
67 | */
68 | public function mockGetResponse()
69 | {
70 | // retrieve args
71 | $args = func_get_args();
72 |
73 | // map responses
74 | $map = array(
75 | 'formencoded' => new \GuzzleHttp\Message\Response(200, array('Content-Type' => 'application/x-www-form-urlencoded'), \GuzzleHttp\Stream\Stream::factory('expires_in=600&access_token=salmon&refresh_token=trout'))
76 | , 'json' => new \GuzzleHttp\Message\Response(200, array('Content-Type' => 'application/json'), \GuzzleHttp\Stream\Stream::factory('{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}'))
77 | );
78 |
79 | return new \OAuth2\Response($map[$this->mode]);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/OAuth2/Tests/ResponseTest.php:
--------------------------------------------------------------------------------
1 | array('bar'));
22 | $body = 'foo';
23 |
24 | // returns the status, headers and body
25 | $this->response = new \OAuth2\Response(new \GuzzleHttp\Message\Response($status, $headers, \GuzzleHttp\Stream\Stream::factory($body)));
26 | $responseHeaders = $this->response->headers();
27 | $this->assertCount(1, $responseHeaders);
28 | $this->assertArrayHasKey('foo', $responseHeaders);
29 | $this->assertEquals($headers['foo'], $responseHeaders['foo']);
30 | $this->assertEquals($status, $this->response->status());
31 | $this->assertEquals($body, $this->response->body());
32 | }
33 |
34 | /**
35 | * @covers OAuth2\Response::content_type()
36 | * @covers OAuth2\Response::parse()
37 | */
38 | public function testParseResponse()
39 | {
40 | // parses application/x-www-form-urlencoded body
41 | $headers = array('Content-Type' => 'application/x-www-form-urlencoded');
42 | $body = 'foo=bar&answer=42';
43 | $this->response = new \OAuth2\Response(new \GuzzleHttp\Message\Response(200, $headers, \GuzzleHttp\Stream\Stream::factory($body)));
44 | $parsedResponse = $this->response->parse();
45 | $this->assertEquals(2, count(array_keys($parsedResponse)));
46 | $this->assertEquals('bar', $parsedResponse['foo']);
47 | $this->assertEquals(42, $parsedResponse['answer']);
48 |
49 | // parses application/json body
50 | $headers = array('Content-Type' => 'application/json');
51 | $body = json_encode(array('foo' => 'bar', 'answer' => 42));
52 | $this->response = new \OAuth2\Response(new \GuzzleHttp\Message\Response(200, $headers, \GuzzleHttp\Stream\Stream::factory($body)));
53 | $parsedResponse = $this->response->parse();
54 | $this->assertEquals(2, count(array_keys($parsedResponse)));
55 | $this->assertEquals('bar', $parsedResponse['foo']);
56 | $this->assertEquals(42, $parsedResponse['answer']);
57 |
58 | // doesn't try to parse other content-types
59 | $headers = array('Content-Type' => 'text/html');
60 | $body = '';
61 | $this->response = new \OAuth2\Response(new \GuzzleHttp\Message\Response(200, $headers, \GuzzleHttp\Stream\Stream::factory($body)));
62 | $this->assertNull($this->response->parse());
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/OAuth2/Tests/Strategy/AuthCodeTest.php:
--------------------------------------------------------------------------------
1 | kvformToken = 'expires_in=600&access_token=salmon&refresh_token=trout&extra_param=steve';
22 | $this->jsonToken = json_encode(array('expires_in' => 600, 'access_token' => 'salmon', 'refresh_token' => 'trout', 'extra_param' => 'steve'));
23 |
24 | // get client stub
25 | $this->client = $this->getClientStub('abc', 'def', array('site' => 'https://api.example.com'));
26 |
27 | // create authCode
28 | $this->authCode = new \OAuth2\Strategy\AuthCode($this->client);
29 | }
30 |
31 | protected function tearDown()
32 | {
33 | unset($this->authCode);
34 | unset($this->access);
35 | unset($this->client);
36 | unset($this->code);
37 | unset($this->jsonToken);
38 | unset($this->kvformToken);
39 | unset($this->mode);
40 | }
41 |
42 | /**
43 | * @covers OAuth2\Strategy\AuthCode::authorize_url()
44 | */
45 | public function testAuthorizeUrl()
46 | {
47 | // should include the client_id
48 | $this->assertContains('client_id=abc', $this->authCode->authorizeUrl());
49 |
50 | // should include the type
51 | $this->assertContains('response_type=code', $this->authCode->authorizeUrl());
52 |
53 | // should include passed in options
54 | $cb = 'http://myserver.local/oauth/callback';
55 | $this->assertContains('redirect_uri='.urlencode($cb), $this->authCode->authorizeUrl(array('redirect_uri' => $cb)));
56 | }
57 |
58 | /**
59 | * @covers OAuth2\Strategy\AuthCode::get_token()
60 | */
61 | public function testGetToken()
62 | {
63 | foreach(array('json', 'formencoded') as $mode) {
64 | // set mode
65 | $this->mode = $mode;
66 |
67 | foreach (array('GET', 'POST') as $verb) {
68 | // set token_method and get token
69 | $this->client->options['token_method'] = $verb;
70 | $this->access = $this->authCode->getToken($this->code);
71 | }
72 |
73 | // returns AccessToken with same Client
74 | $this->assertEquals($this->client, $this->access->getClient());
75 |
76 | // returns AccessToken with $token
77 | $this->assertEquals('salmon', $this->access->getToken());
78 |
79 | // returns AccessToken with $refresh_token
80 | $this->assertEquals('trout', $this->access->getRefreshToken());
81 |
82 | // returns AccessToken with $expires_in
83 | $this->assertEquals(600, $this->access->getExpiresIn());
84 |
85 | // returns AccessToken with params accessible via the params array
86 | $this->assertEquals('steve', $this->access->getParam('extra_param'));
87 | }
88 | }
89 |
90 | /**
91 | * Intercept all OAuth2\Client::getResponse() calls and mock their responses
92 | */
93 | public function mockGetResponse()
94 | {
95 | // retrieve args
96 | $args = func_get_args();
97 |
98 | // map responses
99 | $map = array(
100 | 'formencoded' => new \GuzzleHttp\Message\Response(200, array('Content-Type' => 'application/x-www-form-urlencoded'), \GuzzleHttp\Stream\Stream::factory($this->kvformToken))
101 | , 'json' => new \GuzzleHttp\Message\Response(200, array('Content-Type' => 'application/json'), \GuzzleHttp\Stream\Stream::factory($this->jsonToken))
102 | );
103 |
104 | return new \OAuth2\Response($map[$this->mode]);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/OAuth2/Client.php:
--------------------------------------------------------------------------------
1 | id = $clientId;
17 | $this->secret = $clientSecret;
18 | if (isset($opts['site'])) {
19 | $this->site = ['base_url' => $opts['site']];
20 | unset($opts['site']);
21 | }
22 |
23 | // Default options
24 | $this->options = array_merge([
25 | 'authorize_url' => '/oauth/authorize'
26 | , 'token_url' => '/oauth/token'
27 | , 'token_method' => 'POST'
28 | , 'client_auth' => 'header'
29 | , 'request_opts' => [ 'exceptions' => true ]
30 | ], $opts);
31 |
32 | // Connection object using Guzzle
33 | $this->connection = new \GuzzleHttp\Client($this->site);
34 | }
35 |
36 | /**
37 | * id getter
38 | *
39 | * @return string
40 | */
41 | public function getId()
42 | {
43 | return $this->id;
44 | }
45 |
46 | /**
47 | * secret getter
48 | *
49 | * @return string
50 | */
51 | public function getSecret()
52 | {
53 | return $this->secret;
54 | }
55 |
56 | /**
57 | * The authorize endpoint URL of the OAuth2 provider
58 | *
59 | * @param array $params Additional query parameters
60 | * @return string
61 | */
62 | public function authorizeUrl($params = array())
63 | {
64 | $authorizeUrl = (strpos($this->options['authorize_url'], 'http') === 0) ? $this->options['authorize_url'] : $this->site['base_url'].$this->options['authorize_url'];
65 | return (count($params)) ? $authorizeUrl.'?'.http_build_query($params) : $authorizeUrl;
66 | }
67 |
68 | /**
69 | * The token endpoint URL of the OAuth2 provider
70 | *
71 | * @param array $params Additional query parameters
72 | * @return string
73 | */
74 | public function tokenUrl($params = array())
75 | {
76 | $tokenUrl = (strpos($this->options['token_url'], 'http') === 0) ? $this->options['token_url'] : $this->site['base_url'].$this->options['token_url'];
77 | return (count($params)) ? $tokenUrl.'?'.http_build_query($params) : $tokenUrl;
78 | }
79 |
80 | /**
81 | * Makes a request relative to the specified site root.
82 | *
83 | * @param string $verb One of the following http method: GET, POST, PUT, DELETE
84 | * @param string $url URL path of the request
85 | * @param array $opts The options to make the request with (possible options: params (array), body (string), headers (array), exceptions (boolean), parse ('automatic', 'query' or 'json')
86 | * @return \GuzzleHttp\Message\Request
87 | */
88 | public function createRequest($verb, $url, $opts = array())
89 | {
90 | // Set some default options
91 | $opts = array_merge(array(
92 | 'body' => ''
93 | , 'query' => array()
94 | , 'headers' => array()
95 | ), $this->options['request_opts'], $opts);
96 |
97 | // Create the request
98 | $verb = (in_array($verb, ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'PATCH']) ? $verb : 'GET');
99 | $request = $this->connection->createRequest($verb, $url, $opts);
100 |
101 | return $request;
102 | }
103 |
104 | /**
105 | * Initializes an AccessToken by making a request to the token endpoint
106 | *
107 | * @param \GuzzleHttp\Message\Request $request The request object
108 | * @param string $parseMode The mode of parsing for the response
109 | * @return \OAuth2\Response
110 | */
111 | public function getResponse($request, $parseMode = 'automatic')
112 | {
113 | return new \OAuth2\Response($this->connection->send($request), $parseMode);
114 | }
115 |
116 | /**
117 | * Initializes an AccessToken by making a request to the token endpoint
118 | *
119 | * @param array $params An array of params for the token endpoint
120 | * @param array $access Token options, to pass to the AccessToken object
121 | * @return \OAuth2\AccessToken
122 | */
123 | public function getToken($params = array(), $tokenOpts = array())
124 | {
125 | // Get parse mode for the response
126 | $parseMode = isset($params['parse']) ? $params['parse'] : 'automatic';
127 | unset($params['parse']);
128 |
129 | if ($this->options['token_method'] === 'POST') {
130 | $opts['headers'] = array('Content-Type' => 'x-www-form-urlencoded');
131 | $opts['body'] = $params;
132 | } else {
133 | $opts['query'] = $params;
134 | }
135 |
136 | // Create request
137 | $request = $this->createRequest($this->options['token_method'], $this->tokenUrl(), $opts);
138 |
139 | // Set auth
140 | if (isset($this->options['client_auth'])) {
141 | if ($this->options['client_auth'] === 'header') {
142 | $request->setHeader('Authorization', 'Basic ' . base64_encode("$this->id:$this->secret"));
143 | } else if ($this->options['client_auth'] === 'query') {
144 | $request->getQuery()->merge(['client_id' => $this->id, 'client_secret' => $this->secret]);
145 | } else if ($this->options['client_auth'] === 'body') {
146 | // Retrieve current body as a \Guzzle\Query object since we'll have to add client auth
147 | $body = \GuzzleHttp\Query::fromString((string) $request->getBody());
148 |
149 | // Add client auth
150 | $body->merge(['client_id' => $this->id, 'client_secret' => $this->secret]);
151 |
152 | // Replace body
153 | $request->setBody(\GuzzleHttp\Stream\Stream::factory((string) $body));
154 | } else {
155 | throw new \Exception("Unknown client authentication method.");
156 | }
157 | } else {
158 | throw new \Exception("Missing client authentication method.");
159 | }
160 |
161 | // Get response
162 | $response = $this->getResponse($request, $parseMode);
163 |
164 | // Handle response
165 | $parsedResponse = $response->parse();
166 | if (!is_array($parsedResponse) && !isset($parsedResponse['access_token'])) {
167 | throw new \OAuth2\Error($response);
168 | }
169 |
170 | // Return access token
171 | return \OAuth2\AccessToken::fromHash($this, array_merge($parsedResponse, $tokenOpts));
172 | }
173 |
174 | /**
175 | * The Authorization Code strategy
176 | *
177 | * @return \OAuth2\Strategy\AuthCode
178 | */
179 | public function authCode()
180 | {
181 | $this->authCode = isset($this->authCode) ? $this->authCode : new \OAuth2\Strategy\AuthCode($this);
182 | return $this->authCode;
183 | }
184 |
185 | /**
186 | * The Resource Owner Password Credentials strategy
187 | *
188 | * @return \OAuth2\Strategy\Password
189 | */
190 | public function password()
191 | {
192 | $this->password = isset($this->password) ? $this->password : new \OAuth2\Strategy\Password($this);
193 | return $this->password;
194 | }
195 | }
196 |
197 |
--------------------------------------------------------------------------------
/tests/OAuth2/Tests/AccessTokenTest.php:
--------------------------------------------------------------------------------
1 | tokenBody = json_encode(array('access_token' => 'foo', 'expires_in' => 600, 'refresh_token' => 'bar'));
28 | $this->refreshBody = json_encode(array('access_token' => 'refreshed_foo', 'expires_in' => 600, 'refresh_token' => 'refresh_bar'));
29 |
30 | // get client stub
31 | $this->client = $this->getClientStub('abc', 'def', array('site' => 'https://api.example.com'));
32 |
33 | // instantiate access_token
34 | $this->accessToken = new \OAuth2\AccessToken($this->client, $this->token);
35 | }
36 |
37 | protected function tearDown()
38 | {
39 | unset($this->accessToken);
40 | unset($this->client);
41 | unset($this->refreshBody);
42 | unset($this->token);
43 | unset($this->tokenBody);
44 | }
45 |
46 | /**
47 | * @covers OAuth2\AccessToken::__construct()
48 | */
49 | public function testConstructorBuildsAccessToken()
50 | {
51 | // assigns client and token
52 | $this->assertEquals($this->client, $this->accessToken->getClient());
53 | $this->assertEquals($this->token, $this->accessToken->getToken());
54 |
55 | // assigns extra params
56 | $target = new \OAuth2\AccessToken($this->client, $this->token, array('foo' => 'bar'));
57 | $this->assertArrayHasKey('foo', $target->getParams());
58 | $this->assertEquals('bar', $target->getParam('foo'));
59 |
60 | // initialize with a Hash
61 | $hash = array('access_token' => $this->token, 'expires_in' => time() + 200, 'foo' => 'bar');
62 | $target = \OAuth2\AccessToken::fromHash($this->client, $hash);
63 | $this->assertInitializeToken($target);
64 |
65 | // initalizes with a form-urlencoded key/value string
66 | $kvform = "access_token={$this->token}&expires_in={time() + 200}&foo=bar";
67 | $target = \OAuth2\AccessToken::fromKvform($this->client, $kvform);
68 | $this->assertInitializeToken($target);
69 |
70 | // sets options
71 | $target = new \OAuth2\AccessToken($this->client, $this->token, array('param_name' => 'foo', 'header_format' => 'Bearer %', 'mode' => 'body'));
72 | $this->assertEquals('foo', $target->options['param_name']);
73 | $this->assertEquals('Bearer %', $target->options['header_format']);
74 | $this->assertEquals('body', $target->options['mode']);
75 | }
76 |
77 | /**
78 | * @covers OAuth2\AccessToken::request()
79 | * @covers OAuth2\AccessToken::get()
80 | * @covers OAuth2\AccessToken::post()
81 | * @covers OAuth2\AccessToken::put()
82 | * @covers OAuth2\AccessToken::delete()
83 | */
84 | public function testRequest()
85 | {
86 | // header mode
87 | $this->accessToken->options['mode'] = 'header';
88 | foreach (array('GET', 'POST', 'PUT', 'DELETE') as $verb) {
89 | // sends the token in the Authorization header for a {$verb} request
90 | $this->assertContains($this->token, $this->accessToken->request($verb, '/token/header')->body());
91 | }
92 |
93 | // query mode
94 | $this->accessToken->options['mode'] = 'query';
95 | foreach (array('GET', 'POST', 'PUT', 'DELETE') as $verb) {
96 | // sends the token in the query params for a {$verb} request
97 | $this->assertEquals($this->token, $this->accessToken->request($verb, '/token/query')->body());
98 | }
99 |
100 | // body mode
101 | $this->accessToken->options['mode'] = 'body';
102 | foreach (array('GET', 'POST', 'PUT', 'DELETE') as $verb) {
103 | // sends the token in the body for a {$verb} request
104 | $data = array_reverse(explode('=', $this->accessToken->request($verb, '/token/body')->body()));
105 | $this->assertEquals($this->token, $data[0]);
106 | }
107 | }
108 |
109 | /**
110 | * @covers OAuth2\AccessToken::expires()
111 | */
112 | public function testExpires()
113 | {
114 | // should be false if expires_in is null
115 | $target = new \OAuth2\AccessToken($this->client, $this->token);
116 | $this->assertFalse($target->expires());
117 |
118 | // should be true if there is an expires_in
119 | $target = new \OAuth2\AccessToken($this->client, $this->token, array('refresh_token' => 'abaca', 'expires_in' => 600));
120 | $this->assertTrue($target->expires());
121 | }
122 |
123 | /**
124 | * @covers OAuth2\AccessToken::isExpired()
125 | */
126 | public function testIsExpired()
127 | {
128 | // should be false if there is no expires_in or expires_at
129 | $target = new \OAuth2\AccessToken($this->client, $this->token);
130 | $this->assertFalse($target->isExpired());
131 |
132 | // should be false if expires_in is in the future
133 | $target = new \OAuth2\AccessToken($this->client, $this->token, array('refresh_token' => 'abaca', 'expires_in' => 10800));
134 | $this->assertFalse($target->isExpired());
135 | }
136 |
137 | /**
138 | * @covers OAuth2\AccessToken::refresh()
139 | */
140 | public function testRefresh()
141 | {
142 | // returns a refresh token with appropriate values carried over
143 | $target = new \OAuth2\AccessToken($this->client, $this->token, array('refresh_token' => 'abaca', 'expires_in' => 600, 'param_name' => 'o_param'));
144 | $refreshed = $target->refresh();
145 | $this->assertEquals($refreshed->getClient(), $target->getClient());
146 | $this->assertEquals($refreshed->options['param_name'], $target->options['param_name']);
147 | }
148 |
149 | /**
150 | * Intercept all OAuth2\Client::getResponse() calls and mock their responses
151 | */
152 | public function mockGetResponse()
153 | {
154 | // retrieve args
155 | $args = func_get_args();
156 |
157 | // create response based on mode
158 | switch ($args[0]->getPath()) {
159 | case '/token/header':
160 | $body = sprintf($this->accessToken->options['header_format'], $this->accessToken->getToken());
161 | return new \OAuth2\Response(new \GuzzleHttp\Message\Response(200, array(), \GuzzleHttp\Stream\Stream::factory($body)));
162 | break;
163 |
164 | case '/token/query':
165 | return new \OAuth2\Response(new \GuzzleHttp\Message\Response(200, array(), \GuzzleHttp\Stream\Stream::factory($this->accessToken->getToken())));
166 | break;
167 |
168 | case '/token/body':
169 | $body = $this->accessToken->options['param_name'] . '=' . $this->accessToken->getToken();
170 | return new \OAuth2\Response(new \GuzzleHttp\Message\Response(200, array(), \GuzzleHttp\Stream\Stream::factory($body)));
171 | break;
172 |
173 | case '/oauth/token':
174 | return new \OAuth2\Response(new \GuzzleHttp\Message\Response(200, ['Content-Type' => 'application/json'], \GuzzleHttp\Stream\Stream::factory($this->refreshBody)));
175 | break;
176 | }
177 | }
178 |
179 | /**
180 | * Assert for token initialization
181 | */
182 | private function assertInitializeToken($target) {
183 | $this->assertEquals($this->token, $target->getToken());
184 | $this->assertTrue($target->expires());
185 | $this->assertArrayHasKey('foo', $target->getParams());
186 | $this->assertEquals('bar', $target->getParam('foo'));
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/OAuth2/AccessToken.php:
--------------------------------------------------------------------------------
1 | null // string The refresh_token value
58 | , 'expires_in' => null // integer The number of seconds in which the AccessToken will expire
59 | , 'mode' => 'header' // string The transmission mode of the Access Token parameter value one of 'header', 'body' or 'query'
60 | , 'header_format' => 'Bearer %s' // string The string format to use for the Authorization header
61 | , 'param_name' => 'bearer_token' // string he parameter name to use for transmission of the Access Token value in 'body' or 'query' transmission mode
62 | ), $opts);
63 |
64 | // Setting class attributes
65 | $this->client = $client;
66 | $this->token = $token;
67 | foreach (array('refresh_token', 'expires_in') as $arg) {
68 | // camelize arg
69 | $camelizedArg = lcfirst(str_replace(" ", "", ucwords(strtr($arg, "_-", " "))));
70 |
71 | // set property
72 | $this->$camelizedArg = $opts[$arg];
73 | unset($opts[$arg]);
74 | }
75 |
76 | $this->options = array(
77 | 'mode' => $opts['mode']
78 | , 'header_format' => $opts['header_format']
79 | , 'param_name' => $opts['param_name']
80 | );
81 | unset($opts['mode'], $opts['header_format'], $opts['param_name']);
82 |
83 | $this->params = $opts;
84 | }
85 |
86 | /**
87 | * client getter
88 | *
89 | * @return OAuth2\Client
90 | */
91 | public function getClient()
92 | {
93 | return $this->client;
94 | }
95 |
96 | /**
97 | * expiresIn getter
98 | *
99 | * @return mixed
100 | */
101 | public function getExpiresIn()
102 | {
103 | return $this->expiresIn;
104 | }
105 |
106 | /**
107 | * params getter
108 | *
109 | * @return array
110 | */
111 | public function getParams()
112 | {
113 | return $this->params;
114 | }
115 |
116 | /**
117 | * param getter
118 | *
119 | * @return mixed
120 | */
121 | public function getParam($key)
122 | {
123 | return isset($this->params[$key]) ? $this->params[$key] : null;
124 | }
125 |
126 | /**
127 | * refreshToken getter
128 | *
129 | * @return mixed
130 | */
131 | public function getRefreshToken()
132 | {
133 | return $this->refreshToken;
134 | }
135 |
136 | /**
137 | * token getter
138 | *
139 | * @return mixed
140 | */
141 | public function getToken()
142 | {
143 | return $this->token;
144 | }
145 |
146 | /**
147 | * Whether or not the token expires
148 | *
149 | * @return boolean
150 | */
151 | public function expires()
152 | {
153 | return !is_null($this->expiresIn);
154 | }
155 |
156 | /**
157 | * Whether or not the token is expired
158 | *
159 | * @return boolean
160 | */
161 | public function isExpired()
162 | {
163 | return $this->expires() && ($this->expiresIn === 0);
164 | }
165 |
166 | /**
167 | * Make a request with the Access Token
168 | *
169 | * @param string $verb The HTTP request method
170 | * @param string $path The HTTP URL path of the request
171 | * @param array $opts The options to make the request with
172 | * @see Client::sendRequest
173 | */
174 | public function request($verb, $path, $opts = array())
175 | {
176 | // Set parse mode
177 | $parseMode = 'automatic';
178 | if (isset($opts['parse'])) {
179 | $parseMode = $opts['parse'];
180 | unset($opts['parse']);
181 | }
182 |
183 | // Set token
184 | $opts = $this->setToken($opts);
185 |
186 | // Make request and return response
187 | $request = $this->client->createRequest($verb, $path, $opts);
188 | return $this->client->getResponse($request, $parseMode);
189 | }
190 |
191 | /**
192 | * Make a GET request with the Access Token
193 | *
194 | * @see request
195 | */
196 | public function get($path, $opts = array())
197 | {
198 | return $this->request('GET', $path, $opts);
199 | }
200 |
201 | /**
202 | * Make a POST request with the Access Token
203 | *
204 | * @see request
205 | */
206 | public function post($path, $opts = array())
207 | {
208 | return $this->request('POST', $path, $opts);
209 | }
210 |
211 | /**
212 | * Make a PUT request with the Access Token
213 | *
214 | * @see request
215 | */
216 | public function put($path, $opts = array())
217 | {
218 | return $this->request('PUT', $path, $opts);
219 | }
220 |
221 | /**
222 | * Make a DELETE request with the Access Token
223 | *
224 | * @see request
225 | */
226 | public function delete($path, $opts = array())
227 | {
228 | return $this->request('DELETE', $path, $opts);
229 | }
230 |
231 | /**
232 | * Refreshes the current Access Token
233 | *
234 | * @param array $params
235 | * @return \OAuth2\AccessToken $new_token
236 | */
237 | public function refresh($params = array())
238 | {
239 | if (!$this->refreshToken) {
240 | throw new \ErrorException("A refresh_token is not available");
241 | }
242 |
243 | $params = array_merge($params, array(
244 | 'grant_type' => 'refresh_token'
245 | , 'refresh_token' => $this->refreshToken
246 | ));
247 |
248 | $newToken = $this->client->getToken($params);
249 | $newToken->options = $this->options;
250 | return $newToken;
251 | }
252 |
253 |
254 | private function setToken($opts)
255 | {
256 | switch ($this->options['mode']) {
257 | case 'header':
258 | $opts['headers'] = isset($opts['headers']) ? $opts['headers'] : array();
259 | $opts['headers']['Authorization'] = sprintf($this->options['header_format'], $this->token);
260 | break;
261 |
262 | case 'query':
263 | $opts['query'] = isset($opts['query']) ? $opts['query'] : array();
264 | $opts['query'][$this->options['param_name']] = $this->token;
265 | break;
266 |
267 | case 'body':
268 | $opts['body'] = isset($opts['body']) ? $opts['body'] : '';
269 | $opts['body'] .= "{$this->options['param_name']}={$this->token}";
270 | break;
271 |
272 | default:
273 | throw new \ErrorException("invalid 'mode' option of {$this->options['mode']}");
274 | break;
275 | }
276 |
277 | return $opts;
278 | }
279 | }
280 |
281 |
--------------------------------------------------------------------------------
/tests/OAuth2/Tests/ClientTest.php:
--------------------------------------------------------------------------------
1 | client = $this->getClientStub('abc', 'def', array('site' => 'https://api.example.com'));
24 | }
25 |
26 | protected function tearDown()
27 | {
28 | unset($this->client);
29 | }
30 |
31 | /**
32 | * @covers OAuth2\Client::__construct()
33 | */
34 | public function testConstructorBuildsClient()
35 | {
36 | // client id and secret should be assigned
37 | $this->assertEquals('abc', $this->client->getId());
38 | $this->assertEquals('def', $this->client->getSecret());
39 |
40 | // client site should be assigned
41 | $this->assertEquals(['base_url' => 'https://api.example.com'], $this->client->site);
42 |
43 | // connection baseUrl should be assigned
44 | $this->assertEquals('https://api.example.com', $this->client->connection->getBaseUrl());
45 |
46 | // exceptions in request_opts should be true
47 | $this->assertTrue($this->client->options['request_opts']['exceptions']);
48 |
49 | // allows true/false exceptions in request_opts
50 | $client = new \OAuth2\Client('abc', 'def', [
51 | 'site' => 'https://api.example.com'
52 | , 'request_opts' => [ 'exceptions' => false ]
53 | ]);
54 | $this->assertFalse($client->options['request_opts']['exceptions']);
55 | $client = new \OAuth2\Client('abc', 'def', [
56 | 'site' => 'https://api.example.com'
57 | , 'request_opts' => [ 'exceptions' => true ]
58 | ]);
59 | $this->assertTrue($client->options['request_opts']['exceptions']);
60 |
61 | // allow GET/POST for token_method option
62 | $client = new \OAuth2\Client('abc', 'def', array('site' => 'https://api.example.com', 'token_method' => 'GET'));
63 | $this->assertEquals('GET', $client->options['token_method']);
64 | $client = new \OAuth2\Client('abc', 'def', array('site' => 'https://api.example.com', 'token_method' => 'POST'));
65 | $this->assertEquals('POST', $client->options['token_method']);
66 | }
67 |
68 | /**
69 | * @covers OAuth2\Client::authorize_url()
70 | * @covers OAuth2\Client::token_url()
71 | */
72 | public function testUrlsEnpoints()
73 | {
74 | foreach (array('authorize', 'token') as $urlType) {
75 | // {$url_type}_url should default to /oauth/{$url_type}
76 | $this->assertEquals("https://api.example.com/oauth/{$urlType}", call_user_func(array($this->client, "{$urlType}Url")));
77 |
78 | // {$url_type}_url should be settable via the {$url_type}_url option
79 | $this->client->options["{$urlType}_url"] = '/oauth/custom';
80 | $this->assertEquals("https://api.example.com/oauth/custom", call_user_func(array($this->client, "{$urlType}Url")));
81 |
82 | // allows a different host than the site
83 | $this->client->options["{$urlType}_url"] = 'https://api.foo.com/oauth/custom';
84 | $this->assertEquals("https://api.foo.com/oauth/custom", call_user_func(array($this->client, "{$urlType}Url")));
85 | }
86 | }
87 |
88 | /**
89 | * @covers OAuth2\Client::getResponse()
90 | */
91 | public function testGetResponse()
92 | {
93 | // works with a null response body
94 | $request = $this->client->createRequest('GET', '/empty_get');
95 | $this->assertEmpty((string) $this->client->getResponse($request)->body());
96 |
97 | // returns on a successful response body
98 | $request = $this->client->createRequest('GET', '/success');
99 | $response = $this->client->getResponse($request);
100 | $this->assertEquals('yay', $response->body());
101 | $this->assertEquals(200, $response->status());
102 | $headers = $response->headers();
103 | $this->assertCount(1, $headers);
104 | $this->assertArrayHasKey('Content-Type', $headers);
105 | $this->assertEquals(array('text/awesome'), $headers['Content-Type']);
106 |
107 | // posts a body
108 | $request = $this->client->createRequest('POST', '/reflect', ['body' => 'foo=bar']);
109 | $response = $this->client->getResponse($request);
110 | $this->assertEquals('foo=bar', $response->body());
111 |
112 | // follows redirect properly
113 | $request = $this->client->createRequest('GET', '/redirect');
114 | $response = $this->client->getResponse($request);
115 | $this->assertEquals('yay', $response->body());
116 | $this->assertEquals(200, $response->status());
117 | $headers = $response->headers();
118 | $this->assertCount(1, $headers);
119 | $this->assertArrayHasKey('Content-Type', $headers);
120 | $this->assertEquals(array('text/awesome'), $headers['Content-Type']);
121 |
122 | // redirects using GET on a 303
123 | $request = $this->client->createRequest('POST', '/redirect', ['body' => 'foo=bar']);
124 | $response = $this->client->getResponse($request);
125 | $this->assertEquals('', $response->body());
126 | $this->assertEquals(200, $response->status());
127 |
128 | // obeys the max_redirects option
129 | $request = $this->client->createRequest('GET', '/redirect', [ 'allow_redirects' => false ]);
130 | $response = $this->client->getResponse($request);
131 | $this->assertEquals(302, $response->status());
132 |
133 | // returns if raise_errors is false
134 | $this->client->options['request_opts']['exceptions'] = false;
135 | $request = $this->client->createRequest('GET', '/unauthorized');
136 | $response = $this->client->getResponse($request);
137 | $this->assertEquals(401, $response->status());
138 | $headers = $response->headers();
139 | $this->assertCount(1, $headers);
140 | $this->assertArrayHasKey('Content-Type', $headers);
141 | $this->assertEquals(array('application/json'), $headers['Content-Type']);
142 | $this->assertNotNull($response->error);
143 |
144 | // test if exception are thrown when raise_errors is true
145 | $this->client->options['request_opts']['exceptions'] = true;
146 | foreach (array('/unauthorized', '/conflict', '/error') as $errorPath) {
147 | $request = $this->client->createRequest('GET', $errorPath);
148 |
149 | // throw OAuth\Error on error response to path {$errorPath}
150 | $this->setExpectedException('\OAuth2\Error');
151 | $this->client->getResponse($request);
152 | }
153 |
154 | // parses OAuth2 standard error response
155 | try {
156 | $request = $this->client->createRequest('GET', '/error');
157 | $this->client->getResponse($request);
158 | } catch (\OAuth2\Error $e) {
159 | $this->assertEquals($this->errorValue, $e->getCode());
160 | $this->assertEquals($this->errorDescriptionValue, $e->getDescription());
161 | }
162 |
163 | // provides the response in the Exception
164 | try {
165 | $request = $this->client->createRequest('GET', '/error');
166 | $this->client->getResponse($request);
167 | } catch (\OAuth2\Error $e) {
168 | $this->assertNotNull($e->getResponse());
169 | }
170 | }
171 |
172 | /**
173 | * @covers OAuth2\Client::auth_code()
174 | */
175 | public function testAuthCodeInstatiation()
176 | {
177 | // auth_code() should instantiate a AuthCode strategy with this client
178 | $this->assertInstanceOf("\OAuth2\Strategy\AuthCode", $this->client->authCode());
179 | }
180 |
181 | /**
182 | * Intercept all OAuth2\Client::getResponse() calls and mock their responses
183 | */
184 | public function mockGetResponse()
185 | {
186 | // retrieve arguments
187 | $args = func_get_args();
188 |
189 | // map routes
190 | $map = array();
191 | $map['GET']['/success'] = array('status' => 200, 'headers' => array('Content-Type' => 'text/awesome'), 'body' => 'yay');
192 | $map['GET']['/reflect'] = array('status' => 200, 'headers' => array(), 'body' => $args[0]->getBody());
193 | $map['POST']['/reflect'] = array('status' => 200, 'headers' => array(), 'body' => $args[0]->getBody());
194 | $map['GET']['/unauthorized'] = array('status' => 401, 'headers' => array('Content-Type' => 'application/json'), 'body' => json_encode(array('error' => $this->errorValue, 'error_description' => $this->errorDescriptionValue)));
195 | $map['GET']['/conflict'] = array('status' => 409, 'headers' => array('Content-Type' => 'text/plain'), 'body' => 'not authorized');
196 | $map['GET']['/redirect'] = array('status' => 302, 'headers' => array('Content-Type' => 'text/plain', 'location' => '/success'), 'body' => '');
197 | $map['POST']['/redirect'] = array('status' => 303, 'headers' => array('Content-Type' => 'text/plain', 'location' => '/reflect'), 'body' => '');
198 | $map['GET']['/error'] = array('status' => 500, 'headers' => array(), 'body' => '');
199 | $map['GET']['/empty_get'] = array('status' => 200, 'headers' => array(), 'body' => '');
200 |
201 | // match response
202 | $response = $map[$args[0]->getMethod()][$args[0]->getPath()];
203 |
204 | // wrap response in an OAuth2\Response object
205 | $response = new \OAuth2\Response(new \GuzzleHttp\Message\Response($response['status'], $response['headers'], \GuzzleHttp\Stream\Stream::factory($response['body'])), $args[1]);
206 |
207 | // handle response
208 | if (in_array($response->status(), range(200, 299))) {
209 | return $response;
210 | } else if (in_array($response->status(), range(300, 399))) {
211 | // Increment redirect count
212 | $this->client->options['redirect_count'] = isset($this->client->options['redirect_count']) ? $this->client->options['redirect_count'] : 0;
213 | $this->client->options['redirect_count'] += 1;
214 | if ($this->client->options['redirect_count'] > $args[0]->getConfig()['redirect']['max']) {
215 | return $response;
216 | }
217 |
218 | // Retrieve data
219 | $method = ($response->status() === 303) ? 'GET' : $args[0]->getMethod();
220 | $headers = $response->headers();
221 | $location = $headers['location'];
222 |
223 | // Redirect request
224 | $request = $this->client->createRequest($method, $location[0], [ 'body' => $response->body() ]);
225 | return $this->client->getResponse($request);
226 | } else if (in_array($response->status(), range(400, 599))) {
227 | $e = new \OAuth2\Error($response);
228 | if ($args[0]->getConfig()['exceptions'] || $this->client->options['request_opts']['exceptions']) {
229 | throw $e;
230 | }
231 | $response->error = $e;
232 | return $response;
233 | } else {
234 | throw new \OAuth2\Error($response);
235 | }
236 | }
237 | }
238 |
--------------------------------------------------------------------------------