Add test for existing Auth Code flow behavior
authorNoah Miller <nm@lemnisc.us>
Tue, 27 Apr 2021 07:04:55 +0000 (00:04 -0700)
committerNoah Miller <nm@lemnisc.us>
Tue, 27 Apr 2021 07:04:55 +0000 (00:04 -0700)
ext/oauth-client/tests/fixtures/DummyProvider.php [new file with mode: 0644]
ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php [new file with mode: 0644]
ext/oauth-client/tests/phpunit/api/v4/OAuthClientGrantTest.php

diff --git a/ext/oauth-client/tests/fixtures/DummyProvider.php b/ext/oauth-client/tests/fixtures/DummyProvider.php
new file mode 100644 (file)
index 0000000..39df19c
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+namespace Civi\OAuth;
+
+use GuzzleHttp\Handler\MockHandler;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Psr7\Response;
+
+/**
+ *
+ */
+class DummyProvider extends CiviGenericProvider
+{
+  public function __construct(array $options = [], array $collaborators = [])
+  {
+    parent::__construct($options, $collaborators);
+    if ($paramsForCannedResponses = $options['cannedResponses'] ?? null) {
+      $this->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 (file)
index 0000000..9c7accb
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+
+namespace Civi\OAuth;
+
+/** @noinspection ALL */
+
+use CRM_OAuth_ExtensionUtil as E;
+use Civi\Test\HeadlessInterface;
+use Civi\Test\HookInterface;
+use Civi\Test\TransactionalInterface;
+
+/**
+ * @group headless
+ */
+class AuthCodeFlowTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface,
+                                                                      TransactionalInterface
+{
+
+  use \Civi\Test\ContactTestTrait;
+  use \Civi\Test\Api3TestTrait;
+
+  private $providers = [];
+
+  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
+  {
+    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']);
+  }
+}
index a678726d491b367455d645a9e93583d15acbbd5b..f33bf1cf0bec8ae9c880e77eb879bfb81774c367 100644 (file)
@@ -28,7 +28,7 @@ class api_v4_OAuthClientGrantTest extends \PHPUnit\Framework\TestCase implements
   }
 
   /**
-   * 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) {