1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706:
<?php
namespace League\OAuth2\Client\Test\Provider;
use Eloquent\Liberator\Liberator;
use Eloquent\Phony\Phpunit\Phony;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\ClientInterface;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Test\Provider\Fake as MockProvider;
use League\OAuth2\Client\Grant\AbstractGrant;
use League\OAuth2\Client\Grant\GrantFactory;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\RequestFactory;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
class AbstractProviderTest extends TestCase
{
protected $provider;
protected function setUp()
{
$this->provider = new MockProvider([
'clientId' => 'mock_client_id',
'clientSecret' => 'mock_secret',
'redirectUri' => 'none',
]);
}
public function testInvalidGrantString()
{
$this->provider->getAccessToken('invalid_grant', ['invalid_parameter' => 'none']);
}
public function testInvalidGrantObject()
{
$grant = new \StdClass();
$this->provider->getAccessToken($grant, ['invalid_parameter' => 'none']);
}
public function testAuthorizationUrlStateParam()
{
$this->assertContains('state=XXX', $this->provider->getAuthorizationUrl([
'state' => 'XXX'
]));
}
public function testCustomAuthorizationUrlOptions()
{
$url = $this->provider->getAuthorizationUrl([
'foo' => 'BAR'
]);
$query = parse_url($url, PHP_URL_QUERY);
$this->assertNotEmpty($query);
parse_str($query, $params);
$this->assertArrayHasKey('foo', $params);
$this->assertSame('BAR', $params['foo']);
}
public function testConstructorSetsProperties()
{
$options = [
'clientId' => '1234',
'clientSecret' => '4567',
'redirectUri' => 'http://example.org/redirect'
];
$mockProvider = new MockProvider($options);
foreach ($options as $key => $value) {
$this->assertAttributeEquals($value, $key, $mockProvider);
}
}
public function testConstructorSetsClientOptions()
{
$timeout = rand(100, 900);
$mockProvider = new MockProvider(compact('timeout'));
$config = $mockProvider->getHttpClient()->getConfig();
$this->assertContains('timeout', $config);
$this->assertEquals($timeout, $config['timeout']);
}
public function testCanSetAProxy()
{
$proxy = '192.168.0.1:8888';
$mockProvider = new MockProvider(['proxy' => $proxy]);
$config = $mockProvider->getHttpClient()->getConfig();
$this->assertContains('proxy', $config);
$this->assertEquals($proxy, $config['proxy']);
}
public function testCannotDisableVerifyIfNoProxy()
{
$mockProvider = new MockProvider(['verify' => false]);
$config = $mockProvider->getHttpClient()->getConfig();
$this->assertContains('verify', $config);
$this->assertTrue($config['verify']);
}
public function testCanDisableVerificationIfThereIsAProxy()
{
$mockProvider = new MockProvider(['proxy' => '192.168.0.1:8888', 'verify' => false]);
$config = $mockProvider->getHttpClient()->getConfig();
$this->assertContains('verify', $config);
$this->assertFalse($config['verify']);
}
public function testConstructorSetsGrantFactory()
{
$mockAdapter = Phony::mock(GrantFactory::class)->get();
$mockProvider = new MockProvider([], ['grantFactory' => $mockAdapter]);
$this->assertSame($mockAdapter, $mockProvider->getGrantFactory());
}
public function testConstructorSetsHttpAdapter()
{
$mockAdapter = Phony::mock(ClientInterface::class)->get();
$mockProvider = new MockProvider([], ['httpClient' => $mockAdapter]);
$this->assertSame($mockAdapter, $mockProvider->getHttpClient());
}
public function testConstructorSetsRequestFactory()
{
$mockAdapter = Phony::mock(RequestFactory::class)->get();
$mockProvider = new MockProvider([], ['requestFactory' => $mockAdapter]);
$this->assertSame($mockAdapter, $mockProvider->getRequestFactory());
}
public function testSetRedirectHandler()
{
$this->testFunction = false;
$this->state = false;
$callback = function ($url, $provider) {
$this->testFunction = $url;
$this->state = $provider->getState();
};
$this->provider->authorize([], $callback);
$this->assertNotFalse($this->testFunction);
$this->assertAttributeEquals($this->state, 'state', $this->provider);
}
public function testGetUserProperties($response, $name = null, $email = null, $id = null)
{
$provider = new MockProvider([
'clientId' => 'mock_client_id',
'clientSecret' => 'mock_secret',
'redirectUri' => 'none',
]);
$token = new AccessToken(['access_token' => 'abc', 'expires_in' => 3600]);
$stream = Phony::mock(StreamInterface::class);
$stream->__toString->returns(json_encode(compact('id', 'name', 'email')));
$response = Phony::mock(ResponseInterface::class);
$response->getBody->returns($stream->get());
$response->getHeader->with('content-type')->returns('application/json');
$client = Phony::mock(ClientInterface::class);
$client->send->returns($response->get());
$provider->setHttpClient($client->get());
$user = $provider->getResourceOwner($token);
$url = $provider->getResourceOwnerDetailsUrl($token);
$this->assertEquals($id, $user->getId());
$this->assertEquals($name, $user->getUserScreenName());
$this->assertEquals($email, $user->getUserEmail());
$this->assertArrayHasKey('name', $user->toArray());
$this->assertArrayHasKey('email', $user->toArray());
Phony::inOrder(
$client->send->calledWith(
$this->callback(function ($request) use ($url) {
return $request->getMethod() === 'GET'
&& $request->hasHeader('Authorization')
&& (string) $request->getUri() === $url;
})
),
$response->getBody->called(),
$stream->__toString->called(),
$response->getHeader->called()
);
}
public function testGetUserPropertiesThrowsExceptionWhenNonJsonResponseIsReceived()
{
$this->expectException(\UnexpectedValueException::class);
$provider = new MockProvider([
'clientId' => 'mock_client_id',
'clientSecret' => 'mock_secret',
'redirectUri' => 'none',
]);
$token = new AccessToken(['access_token' => 'abc', 'expires_in' => 3600]);
$stream = Phony::mock(StreamInterface::class);
$stream->__toString->returns("<html><body>some unexpected response.</body></html>");
$response = Phony::mock(ResponseInterface::class);
$response->getBody->returns($stream->get());
$response->getHeader->with('content-type')->returns('text/html');
$client = Phony::mock(ClientInterface::class);
$client->send->returns($response->get());
$provider->setHttpClient($client->get());
$user = $provider->getResourceOwner($token);
}
public function userPropertyProvider()
{
$response = [
'id' => 1,
'email' => 'test@example.com',
'name' => 'test',
];
$response2 = [
'id' => null,
'email' => null,
'name' => null,
];
$response3 = [];
return [
'full response' => [$response, 'test', 'test@example.com', 1],
'empty response' => [$response2],
'no response' => [$response3],
];
}
public function getHeadersTest()
{
$provider = $this->getMockForAbstractClass(
'\League\OAuth2\Client\Provider\AbstractProvider',
[
[
'clientId' => 'mock_client_id',
'clientSecret' => 'mock_secret',
'redirectUri' => 'none',
]
]
);
$this->assertEquals([], $provider->getHeaders());
$this->assertEquals([], $provider->getHeaders('mock_token'));
$provider->authorizationHeader = 'Bearer';
$this->assertEquals(['Authorization' => 'Bearer abc'], $provider->getHeaders('abc'));
$token = new AccessToken(['access_token' => 'xyz', 'expires_in' => 3600]);
$this->assertEquals(['Authorization' => 'Bearer xyz'], $provider->getHeaders($token));
}
public function testScopesOverloadedDuringAuthorize()
{
$url = $this->provider->getAuthorizationUrl();
parse_str(parse_url($url, PHP_URL_QUERY), $qs);
$this->assertArrayHasKey('scope', $qs);
$this->assertSame('test', $qs['scope']);
$url = $this->provider->getAuthorizationUrl(['scope' => ['foo', 'bar']]);
parse_str(parse_url($url, PHP_URL_QUERY), $qs);
$this->assertArrayHasKey('scope', $qs);
$this->assertSame('foo,bar', $qs['scope']);
}
public function testAuthorizationStateIsRandom()
{
$last = null;
for ($i = 0; $i < 100; $i++) {
$url = $this->provider->getAuthorizationUrl();
parse_str(parse_url($url, PHP_URL_QUERY), $qs);
$this->assertRegExp('/^[a-zA-Z0-9\/+]{32}$/', $qs['state']);
$this->assertNotSame($qs['state'], $last);
$last = $qs['state'];
}
}
public function testErrorResponsesCanBeCustomizedAtTheProvider()
{
$provider = new MockProvider([
'clientId' => 'mock_client_id',
'clientSecret' => 'mock_secret',
'redirectUri' => 'none',
]);
$error = ["error" => "Foo error", "code" => 1337];
$errorJson = json_encode($error);
$stream = Phony::mock(StreamInterface::class);
$stream->__toString->returns($errorJson);
$response = Phony::mock(ResponseInterface::class);
$response->getBody->returns($stream->get());
$response->getHeader->with('content-type')->returns('application/json');
$client = Phony::mock(ClientInterface::class);
$client->send->returns($response->get());
$provider->setHttpClient($client->get());
$errorMessage = '';
$errorCode = 0;
try {
$provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
} catch (IdentityProviderException $e) {
$errorMessage = $e->getMessage();
$errorCode = $e->getCode();
$errorBody = $e->getResponseBody();
}
$method = $provider->getAccessTokenMethod();
$url = $provider->getBaseAccessTokenUrl([]);
$this->assertEquals($error['error'], $errorMessage);
$this->assertEquals($error['code'], $errorCode);
$this->assertEquals($error, $errorBody);
Phony::inOrder(
$client->send->calledWith(
$this->callback(function ($request) use ($method, $url) {
return $request->getMethod() === $method
&& (string) $request->getUri() === $url;
})
),
$response->getBody->called(),
$stream->__toString->called(),
$response->getHeader->called()
);
}
public function testClientErrorTriggersProviderException()
{
$provider = new MockProvider([
'clientId' => 'mock_client_id',
'clientSecret' => 'mock_secret',
'redirectUri' => 'none',
]);
$stream = Phony::mock(StreamInterface::class);
$stream->__toString->returns('{"error":"Foo error","code":1337}');
$request = Phony::mock(RequestInterface::class);
$response = Phony::mock(ResponseInterface::class);
$response->getStatusCode->returns(400);
$response->getBody->returns($stream->get());
$response->getHeader->with('content-type')->returns('application/json');
$client = Phony::mock(ClientInterface::class);
$client->send->throws(new BadResponseException(
'test exception',
$request->get(),
$response->get()
));
$provider->setHttpClient($client->get());
$provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
}
public function testGetResponse()
{
$provider = new MockProvider();
$request = Phony::mock(RequestInterface::class)->get();
$response = Phony::mock(ResponseInterface::class)->get();
$client = Phony::mock(ClientInterface::class);
$client->send->with($request)->returns($response);
$provider->setHttpClient($client->get());
$output = $provider->getResponse($request);
$this->assertSame($output, $response);
}
public function testAuthenticatedRequestAndResponse()
{
$provider = new MockProvider();
$token = new AccessToken(['access_token' => 'abc', 'expires_in' => 3600]);
$request = $provider->getAuthenticatedRequest('get', 'https://api.example.com/v1/test', $token);
$stream = Phony::mock(StreamInterface::class);
$stream->__toString->returns('{"example":"response"}');
$response = Phony::mock(ResponseInterface::class);
$response->getBody->returns($stream->get());
$response->getHeader->with('content-type')->returns('application/json');
$client = Phony::mock(ClientInterface::class);
$client->send->with($request)->returns($response->get());
$provider->setHttpClient($client->get());
$result = $provider->getParsedResponse($request);
$this->assertSame(['example' => 'response'], $result);
$this->assertInstanceOf(RequestInterface::class, $request);
$header = $request->getHeader('Authorization');
$this->assertContains('Bearer abc', $header);
Phony::inOrder(
$client->send->called(),
$response->getBody->called(),
$stream->__toString->called(),
$response->getHeader->called()
);
}
public function getAccessTokenMethodProvider()
{
return [
['GET'],
['POST'],
];
}
public function testGetAccessToken($method)
{
$provider = new MockProvider([
'clientId' => 'mock_client_id',
'clientSecret' => 'mock_secret',
'redirectUri' => 'none',
]);
$provider->setAccessTokenMethod($method);
$grant_name = 'mock';
$raw_response = ['access_token' => 'okay', 'expires' => time() + 3600, 'resource_owner_id' => 3];
$grant = Phony::mock(AbstractGrant::class);
$grant->prepareRequestParameters->returns([]);
$stream = Phony::mock(StreamInterface::class);
$stream->__toString->returns(json_encode($raw_response));
$response = Phony::mock(ResponseInterface::class);
$response->getBody->returns($stream->get());
$response->getHeader->with('content-type')->returns('application/json');
$client = Phony::mock(ClientInterface::class);
$client->send->returns($response->get());
$provider->setHttpClient($client->get());
$token = $provider->getAccessToken($grant->get(), ['code' => 'mock_authorization_code']);
$this->assertInstanceOf(AccessToken::class, $token);
$this->assertSame($raw_response['resource_owner_id'], $token->getResourceOwnerId());
$this->assertSame($raw_response['access_token'], $token->getToken());
$this->assertSame($raw_response['expires'], $token->getExpires());
Phony::inOrder(
$grant->prepareRequestParameters->calledWith('~', '~'),
$client->send->calledWith(
$this->callback(function ($request) use ($provider) {
return $request->getMethod() === $provider->getAccessTokenMethod()
&& (string) $request->getUri() === $provider->getBaseAccessTokenUrl([]);
})
),
$response->getBody->called(),
$stream->__toString->called(),
$response->getHeader->called()
);
}
private function getMethod($class, $name)
{
$class = new \ReflectionClass($class);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method;
}
public function parseResponseProvider()
{
return [
[
'body' => '{"a": 1}',
'type' => 'application/json',
'parsed' => ['a' => 1]
],
[
'body' => 'string',
'type' => 'unknown',
'parsed' => 'string'
],
[
'body' => 'a=1&b=2',
'type' => 'application/x-www-form-urlencoded',
'parsed' => ['a' => 1, 'b' => 2]
],
];
}
public function testParseResponse($body, $type, $parsed)
{
$stream = Phony::mock(StreamInterface::class);
$stream->__toString->returns($body);
$response = Phony::mock(ResponseInterface::class);
$response->getBody->returns($stream->get());
$response->getHeader->with('content-type')->returns($type);
$method = $this->getMethod(AbstractProvider::class, 'parseResponse');
$result = $method->invoke($this->provider, $response->get());
$this->assertEquals($parsed, $result);
}
public function testParseResponseJsonFailure()
{
$this->testParseResponse('{a: 1}', 'application/json', null);
}
public function getAppendQueryProvider()
{
return [
['test.com/?a=1', 'test.com/', '?a=1'],
['test.com/?a=1', 'test.com/', '&a=1'],
['test.com/?a=1', 'test.com/', 'a=1'],
['test.com/?a=1', 'test.com/?a=1', '?'],
['test.com/?a=1', 'test.com/?a=1', '&'],
['test.com/?a=1&b=2', 'test.com/?a=1', '&b=2'],
['test.com/?a=1&b=2', 'test.com/?a=1', 'b=2'],
['test.com/?a=1&b=2', 'test.com/?a=1', '?b=2'],
['test.com/?a=1&b=1&b=2', 'test.com/?a=1&b=1', 'b=2'],
['test.com/?a=1&b=2&b=2', 'test.com/?a=1&b=2', 'b=2'],
];
}
public function testAppendQuery($expected, $url, $query)
{
$method = $this->getMethod(AbstractProvider::class, 'appendQuery');
$this->assertEquals($expected, $method->invoke($this->provider, $url, $query));
}
protected function getAbstractProviderMock()
{
$mock = Phony::partialMock(AbstractProvider::class);
return Liberator::liberate($mock->get());
}
public function testDefaultAccessTokenMethod()
{
$provider = $this->getAbstractProviderMock();
$method = $provider->getAccessTokenMethod();
$expectedMethod = 'POST';
$this->assertEquals($expectedMethod, $method);
}
public function testDefaultPrepareAccessTokenResponse()
{
$provider = Phony::partialMock(Fake\ProviderWithAccessTokenResourceOwnerId::class);
$provider = Liberator::liberate($provider->get());
$result = ['user_id' => uniqid()];
$newResult = $provider->prepareAccessTokenResponse($result);
$this->assertArrayHasKey('resource_owner_id', $newResult);
$this->assertEquals($result['user_id'], $newResult['resource_owner_id']);
}
public function testPrepareAccessTokenResponseWithDotNotation()
{
$provider = Phony::partialMock(Fake\ProviderWithAccessTokenResourceOwnerId::class);
$provider->getAccessTokenResourceOwnerId->returns('user.id');
$provider = Liberator::liberate($provider->get());
$result = ['user' => ['id' => uniqid()]];
$newResult = $provider->prepareAccessTokenResponse($result);
$this->assertArrayHasKey('resource_owner_id', $newResult);
$this->assertEquals($result['user']['id'], $newResult['resource_owner_id']);
}
public function testPrepareAccessTokenResponseWithInvalidKeyType()
{
$provider = Phony::mock(Fake\ProviderWithAccessTokenResourceOwnerId::class);
$provider->getAccessTokenResourceOwnerId->returns(new \stdClass);
$provider = Liberator::liberate($provider->get());
$result = ['user_id' => uniqid()];
$newResult = $provider->prepareAccessTokenResponse($result);
$this->assertFalse(isset($newResult['resource_owner_id']));
}
public function testPrepareAccessTokenResponseWithInvalidKeyPath()
{
$provider = Phony::mock(Fake\ProviderWithAccessTokenResourceOwnerId::class);
$provider->getAccessTokenResourceOwnerId->returns('user.name');
$provider = Liberator::liberate($provider->get());
$result = ['user' => ['id' => uniqid()]];
$newResult = $provider->prepareAccessTokenResponse($result);
$this->assertFalse(isset($newResult['resource_owner_id']));
}
public function testDefaultAuthorizationHeaders()
{
$provider = $this->getAbstractProviderMock();
$headers = $provider->getAuthorizationHeaders();
$this->assertEquals([], $headers);
}
}