From ee62df699a18d76b88133ec91c2e98bdda6927ef Mon Sep 17 00:00:00 2001 From: Noah Miller Date: Tue, 27 Apr 2021 00:04:55 -0700 Subject: [PATCH 1/3] Add test for existing Auth Code flow behavior --- .../tests/fixtures/DummyProvider.php | 37 +++++ .../phpunit/Civi/OAuth/AuthCodeFlowTest.php | 157 ++++++++++++++++++ .../phpunit/api/v4/OAuthClientGrantTest.php | 2 +- 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 ext/oauth-client/tests/fixtures/DummyProvider.php create mode 100644 ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php diff --git a/ext/oauth-client/tests/fixtures/DummyProvider.php b/ext/oauth-client/tests/fixtures/DummyProvider.php new file mode 100644 index 000000000000..39df19c908df --- /dev/null +++ b/ext/oauth-client/tests/fixtures/DummyProvider.php @@ -0,0 +1,37 @@ +setHttpClient($this->createHttpClient($paramsForCannedResponses)); + } + + } + + private function createHttpClient($paramsForResponses): \GuzzleHttp\Client + { + $handler = new MockHandler(); + + foreach ($paramsForResponses as $ps) { + $handler->append( + new Response($ps['status'], $ps['headers'], $ps['body']) + ); + } + + $handlerStack = HandlerStack::create($handler); + return new \GuzzleHttp\Client(['handler' => $handlerStack]); + } +} + + diff --git a/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php new file mode 100644 index 000000000000..9c7accbbf7ca --- /dev/null +++ b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php @@ -0,0 +1,157 @@ +install('oauth-client')->apply(); + } + + public function setUp(): void + { + parent::setUp(); + } + + public function tearDown(): void + { + parent::tearDown(); + } + + public function hook_civicrm_oauthProviders(&$providers) { + $providers = array_merge($providers, $this->providers); + } + + public function makeDummyProviderThatGetsAToken(): void + { + $idTokenHeader = ['alg' => 'RS256', 'kid' => '123456789', 'typ' => 'JWT']; + $idTokenPayload = [ + 'iss' => 'https://dummy', + 'azp' => 'something', + 'aud' => 'something', + 'sub' => '987654321', + 'email' => 'test@baz.biff', + 'email_verified' => true, + 'at_hash' => 'fake hash value', + 'nonce' => '111', + 'iat' => 1619151829, + 'exp' => 9999999999 + ]; + $idToken = base64_encode(json_encode($idTokenHeader)) + . '.' . base64_encode(json_encode($idTokenPayload)); + + $authServerResponse = [ + 'status' => 200, + 'headers' => ['Content-Type' => 'application/json; charset=utf-8'], + 'body' => json_encode( + [ + 'access_token' => 'example-access-token-value', + 'token_type' => 'Bearer', + 'scope' => 'foo', + 'refresh_token' => 'example-refresh-token-value', + 'created_at' => time(), + 'expires_in' => 3600, + 'id_token' => $idToken + + ] + ) + ]; + + $this->providers['dummy'] = [ + 'name' => 'dummy', + 'title' => 'Dummy Provider', + 'class' => 'Civi\OAuth\DummyProvider', + 'options' => [ + 'urlAuthorize' => 'https://dummy/authorize', + 'urlAccessToken' => 'https://dummy/token', + 'urlResourceOwnerDetails' => '{{use_id_token}}', + 'scopes' => ['foo'], + 'cannedResponses' => [$authServerResponse] + ], + 'contactTemplate' => [ + 'values' => [ + 'contact_type' => 'Individual', + ], + 'chain' => [ + 'email' => ['Email', 'create', ['values' => ['contact_id' => '$id', 'email' => '{{token.resource_owner.email}}']]], + ], + ] + ]; + + require_once 'tests/fixtures/DummyProvider.php'; + } + + public function makeDummyProviderClient(): array + { + $client = \Civi\Api4\OAuthClient::create(false)->setValues( + [ + 'provider' => 'dummy', + 'guid' => "example-client-guid", + 'secret' => "example-secret", + ] + )->execute()->single(); + return $client; + } + + public function testFetchAndStoreSysToken() + { + $this->makeDummyProviderThatGetsAToken(); + $client = $this->makeDummyProviderClient(); + + /** @var OAuthTokenFacade $tokenService */ + $tokenService = \Civi::service('oauth2.token'); + + // This is the call that \CRM_OAuth_Page_Return::run would make upon receiving an auth code. + $tokenRecord = $tokenService->init( + [ + 'client' => $client, + 'scope' => 'foo', + 'tag' => null, + 'storage' => 'OAuthSysToken', + 'grant_type' => 'authorization_code', + 'cred' => ['code' => 'example-auth-code'], + ] + ); + $this->assertTrue(is_numeric($tokenRecord['id'])); + $this->assertEquals($client['id'], $tokenRecord['client_id']); + $this->assertEquals(['foo'], $tokenRecord['scopes']); + $this->assertEquals('example-access-token-value', $tokenRecord['access_token']); + $this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']); + $this->assertEquals('Bearer', $tokenRecord['token_type']); + $this->assertEquals('test@baz.biff', $tokenRecord['resource_owner_name']); + $this->assertEquals( + [ + 'iss' => 'https://dummy', + 'azp' => 'something', + 'aud' => 'something', + 'sub' => '987654321', + 'email' => 'test@baz.biff', + 'email_verified' => true, + 'at_hash' => 'fake hash value', + 'nonce' => '111', + 'iat' => 1619151829, + 'exp' => 9999999999 + ], + $tokenRecord['resource_owner']); + } +} diff --git a/ext/oauth-client/tests/phpunit/api/v4/OAuthClientGrantTest.php b/ext/oauth-client/tests/phpunit/api/v4/OAuthClientGrantTest.php index a678726d491b..f33bf1cf0bec 100644 --- a/ext/oauth-client/tests/phpunit/api/v4/OAuthClientGrantTest.php +++ b/ext/oauth-client/tests/phpunit/api/v4/OAuthClientGrantTest.php @@ -28,7 +28,7 @@ public function tearDown(): void { } /** - * Basic sanity check - create, read, and delete a client. + * Generate the URL to request an authorization code from a provider. */ public function testAuthorizationCode(): void { $usePerms = function($ps) { From 8ef1d71744f40bd35555d67e5c6a2919fc87b116 Mon Sep 17 00:00:00 2001 From: Noah Miller Date: Tue, 27 Apr 2021 01:25:03 -0700 Subject: [PATCH 2/3] (NFC) put code into Drupal style --- .../tests/fixtures/DummyProvider.php | 20 +++---- .../phpunit/Civi/OAuth/AuthCodeFlowTest.php | 57 +++++++++---------- 2 files changed, 35 insertions(+), 42 deletions(-) diff --git a/ext/oauth-client/tests/fixtures/DummyProvider.php b/ext/oauth-client/tests/fixtures/DummyProvider.php index 39df19c908df..ac7549fd9786 100644 --- a/ext/oauth-client/tests/fixtures/DummyProvider.php +++ b/ext/oauth-client/tests/fixtures/DummyProvider.php @@ -1,26 +1,21 @@ setHttpClient($this->createHttpClient($paramsForCannedResponses)); } - } - private function createHttpClient($paramsForResponses): \GuzzleHttp\Client - { + private function createHttpClient($paramsForResponses): \GuzzleHttp\Client { $handler = new MockHandler(); foreach ($paramsForResponses as $ps) { @@ -32,6 +27,5 @@ private function createHttpClient($paramsForResponses): \GuzzleHttp\Client $handlerStack = HandlerStack::create($handler); return new \GuzzleHttp\Client(['handler' => $handlerStack]); } -} - +} diff --git a/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php index 9c7accbbf7ca..7cb80bd0a0f9 100644 --- a/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php +++ b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php @@ -2,9 +2,6 @@ namespace Civi\OAuth; -/** @noinspection ALL */ - -use CRM_OAuth_ExtensionUtil as E; use Civi\Test\HeadlessInterface; use Civi\Test\HookInterface; use Civi\Test\TransactionalInterface; @@ -13,28 +10,24 @@ * @group headless */ class AuthCodeFlowTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface, - TransactionalInterface -{ + TransactionalInterface { use \Civi\Test\ContactTestTrait; use \Civi\Test\Api3TestTrait; private $providers = []; - public function setUpHeadless() - { + public function setUpHeadless() { // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile(). // See: https://docs.civicrm.org/dev/en/latest/testing/phpunit/#civitest return \Civi\Test::headless()->install('oauth-client')->apply(); } - public function setUp(): void - { + public function setUp(): void { parent::setUp(); } - public function tearDown(): void - { + public function tearDown(): void { parent::tearDown(); } @@ -42,8 +35,7 @@ public function hook_civicrm_oauthProviders(&$providers) { $providers = array_merge($providers, $this->providers); } - public function makeDummyProviderThatGetsAToken(): void - { + public function makeDummyProviderThatGetsAToken(): void { $idTokenHeader = ['alg' => 'RS256', 'kid' => '123456789', 'typ' => 'JWT']; $idTokenPayload = [ 'iss' => 'https://dummy', @@ -51,11 +43,11 @@ public function makeDummyProviderThatGetsAToken(): void 'aud' => 'something', 'sub' => '987654321', 'email' => 'test@baz.biff', - 'email_verified' => true, + 'email_verified' => TRUE, 'at_hash' => 'fake hash value', 'nonce' => '111', 'iat' => 1619151829, - 'exp' => 9999999999 + 'exp' => 9999999999, ]; $idToken = base64_encode(json_encode($idTokenHeader)) . '.' . base64_encode(json_encode($idTokenPayload)); @@ -71,10 +63,10 @@ public function makeDummyProviderThatGetsAToken(): void 'refresh_token' => 'example-refresh-token-value', 'created_at' => time(), 'expires_in' => 3600, - 'id_token' => $idToken + 'id_token' => $idToken, ] - ) + ), ]; $this->providers['dummy'] = [ @@ -86,35 +78,41 @@ public function makeDummyProviderThatGetsAToken(): void 'urlAccessToken' => 'https://dummy/token', 'urlResourceOwnerDetails' => '{{use_id_token}}', 'scopes' => ['foo'], - 'cannedResponses' => [$authServerResponse] + 'cannedResponses' => [$authServerResponse], ], 'contactTemplate' => [ 'values' => [ 'contact_type' => 'Individual', ], 'chain' => [ - 'email' => ['Email', 'create', ['values' => ['contact_id' => '$id', 'email' => '{{token.resource_owner.email}}']]], + 'email' => [ + 'Email', + 'create', + [ + 'values' => [ + 'contact_id' => '$id', + 'email' => '{{token.resource_owner.email}}', + ], + ], + ], ], - ] + ], ]; require_once 'tests/fixtures/DummyProvider.php'; } - public function makeDummyProviderClient(): array - { - $client = \Civi\Api4\OAuthClient::create(false)->setValues( + public function makeDummyProviderClient(): array { + return \Civi\Api4\OAuthClient::create(FALSE)->setValues( [ 'provider' => 'dummy', 'guid' => "example-client-guid", 'secret' => "example-secret", ] )->execute()->single(); - return $client; } - public function testFetchAndStoreSysToken() - { + public function testFetchAndStoreSysToken() { $this->makeDummyProviderThatGetsAToken(); $client = $this->makeDummyProviderClient(); @@ -126,7 +124,7 @@ public function testFetchAndStoreSysToken() [ 'client' => $client, 'scope' => 'foo', - 'tag' => null, + 'tag' => NULL, 'storage' => 'OAuthSysToken', 'grant_type' => 'authorization_code', 'cred' => ['code' => 'example-auth-code'], @@ -146,12 +144,13 @@ public function testFetchAndStoreSysToken() 'aud' => 'something', 'sub' => '987654321', 'email' => 'test@baz.biff', - 'email_verified' => true, + 'email_verified' => TRUE, 'at_hash' => 'fake hash value', 'nonce' => '111', 'iat' => 1619151829, - 'exp' => 9999999999 + 'exp' => 9999999999, ], $tokenRecord['resource_owner']); } + } From 0af0f0c1b4c57000e7d8511b6f631c52bf695212 Mon Sep 17 00:00:00 2001 From: Noah Miller Date: Tue, 27 Apr 2021 01:33:35 -0700 Subject: [PATCH 3/3] (NFC) making the whitespace how Jenkins wants it --- .../tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php index 7cb80bd0a0f9..250d5c6ef80b 100644 --- a/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php +++ b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php @@ -9,8 +9,10 @@ /** * @group headless */ -class AuthCodeFlowTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface, - TransactionalInterface { +class AuthCodeFlowTest extends \PHPUnit\Framework\TestCase implements + HeadlessInterface, + HookInterface, + TransactionalInterface { use \Civi\Test\ContactTestTrait; use \Civi\Test\Api3TestTrait;