implement OAuthSessionToken, with tests
authorNoah Miller <nm@lemnisc.us>
Tue, 5 Sep 2023 17:55:15 +0000 (13:55 -0400)
committerNoah Miller <nm@lemnisc.us>
Fri, 22 Sep 2023 04:01:38 +0000 (00:01 -0400)
ext/oauth-client/Civi/Api4/OAuthSessionToken.php [new file with mode: 0644]
ext/oauth-client/Civi/OAuth/OAuthTokenFacade.php
ext/oauth-client/tests/phpunit/api/v4/OAuthSessionTokenTest.php [new file with mode: 0644]

diff --git a/ext/oauth-client/Civi/Api4/OAuthSessionToken.php b/ext/oauth-client/Civi/Api4/OAuthSessionToken.php
new file mode 100644 (file)
index 0000000..5c96d01
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+namespace Civi\Api4;
+
+use Civi\Api4\Generic\AbstractAction;
+use Civi\Api4\Generic\Result;
+
+class OAuthSessionToken extends Generic\AbstractEntity {
+
+  const ENTITY = 'OAuthSessionToken';
+
+  public static function deleteAll($checkPermissions = TRUE): AbstractAction {
+    return (new class(self::ENTITY, __FUNCTION__) extends AbstractAction {
+
+      public function _run(Result $result) {
+        $result->exchangeArray(OAuthSessionToken::get());
+        \CRM_Core_Session::singleton()->set('OAuthSessionTokens');
+      }
+
+    })->setCheckPermissions($checkPermissions);
+  }
+
+  /**
+   * @param bool $checkPermissions
+   * @return Generic\BasicGetAction
+   */
+  public static function get($checkPermissions = TRUE): Generic\BasicGetAction {
+    $action = new Generic\BasicGetAction(self::ENTITY, __FUNCTION__, function () {
+      $session = \CRM_Core_Session::singleton();
+      return $session->get('OAuthSessionTokens') ?? [];
+    });
+    return $action->setCheckPermissions($checkPermissions);
+  }
+
+  public static function create($checkPermissions = TRUE): Generic\BasicCreateAction {
+    $action = new Generic\BasicCreateAction(
+      self::ENTITY,
+      __FUNCTION__,
+      function ($item, $createAction) {
+        $session = \CRM_Core_Session::singleton();
+        $all = $session->get('OAuthSessionTokens') ?? [];
+        $all[] = &$item;
+        $item['cardinal'] = array_key_last($all);
+        $session->set('OAuthSessionTokens', $all);
+        return $item;
+      });
+    return $action->setCheckPermissions($checkPermissions);
+  }
+
+  /**
+   * @param bool $checkPermissions
+   * @return Generic\BasicGetFieldsAction
+   */
+  public static function getFields($checkPermissions = TRUE) {
+    $action = new Generic\BasicGetFieldsAction(self::ENTITY, __FUNCTION__, function () {
+      return [
+        [
+          'name' => 'client_id',
+          'required' => TRUE,
+        ],
+        ['name' => 'grant_type'],
+        ['name' => 'tag'],
+        ['name' => 'scopes'],
+        ['name' => 'token_type'],
+        ['name' => 'access_token'],
+        ['name' => 'refresh_token'],
+        ['name' => 'expires'],
+        ['name' => 'raw'],
+        ['name' => 'storage'],
+        ['name' => 'resource_owner_name'],
+        ['name' => 'resource_owner'],
+      ];
+    });
+    return $action->setCheckPermissions($checkPermissions);
+  }
+
+  /**
+   * @return array
+   */
+  public static function permissions() {
+    return [
+      "meta" => ["access CiviCRM"],
+      "default" => ["administer CiviCRM data"],
+    ];
+  }
+
+}
index 679599c0e37877829e4c099b26d076fb5f6cac41..45718390e974d09a673dd2614482123cf059353a 100644 (file)
@@ -6,7 +6,7 @@ use League\OAuth2\Client\Provider\ResourceOwnerInterface;
 
 class OAuthTokenFacade {
 
-  const STORAGE_TYPES = ';^OAuth(Sys|Contact)Token$;';
+  const STORAGE_TYPES = ';^OAuth(Sys|Contact|Session)Token$;';
 
   /**
    * Request and store a token.
diff --git a/ext/oauth-client/tests/phpunit/api/v4/OAuthSessionTokenTest.php b/ext/oauth-client/tests/phpunit/api/v4/OAuthSessionTokenTest.php
new file mode 100644 (file)
index 0000000..82431d0
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+
+use Civi\Test\HeadlessInterface;
+use Civi\Test\HookInterface;
+use Civi\Test\TransactionalInterface;
+
+/**
+ * Create and read session-specific OAuth tokens
+ *
+ * @group headless
+ */
+class api_v4_OAuthSessionTokenTest extends \PHPUnit\Framework\TestCase implements
+    HeadlessInterface,
+    HookInterface,
+    TransactionalInterface {
+
+  // these two traits together give us createLoggedInUser()
+  use Civi\Test\ContactTestTrait;
+  use \Civi\Test\Api3TestTrait;
+
+  public function setUpHeadless(): \Civi\Test\CiviEnvBuilder {
+    return \Civi\Test::headless()->install('oauth-client')->apply();
+  }
+
+  public function setUp(): void {
+    parent::setUp();
+    $this->assertEquals(0, CRM_Core_DAO::singleValueQuery('SELECT count(*) FROM civicrm_oauth_client'));
+    $this->assertNull(CRM_Core_Session::singleton()->get('OAuthSessionTokens') ?? NULL);
+  }
+
+  protected function tearDown(): void {
+    CRM_Core_Session::singleton()->reset();
+    parent::tearDown();
+  }
+
+  private function createClient(): ?array {
+    $createClient = Civi\Api4\OAuthClient::create(FALSE)->setValues(
+      [
+        'provider' => 'test_example_1',
+        'guid' => "example-client-guid",
+        'secret' => "example-secret",
+      ]
+    )->execute();
+    $client = $createClient->first();
+    $this->assertTrue(is_numeric($client['id']));
+    return $client;
+  }
+
+  private function getTestTokenCreateValues($client, $secretPrefix) {
+    return [
+      'client_id' => $client['id'],
+      'access_token' => "$secretPrefix-user-access-token",
+      'refresh_token' => "$secretPrefix-user-refresh-token",
+    ];
+  }
+
+  public function testAnonymousSessionCanHoldToken() {
+    self::assertNull(CRM_Core_Session::getLoggedInContactID());
+
+    $client = $this->createClient();
+    $tokenCreateValues = $this->getTestTokenCreateValues($client, 'anon');
+
+    Civi\Api4\OAuthSessionToken::create(FALSE)
+      ->setValues($tokenCreateValues)
+      ->execute();
+
+    $retrievedToken = \Civi\Api4\OAuthSessionToken::get(FALSE)
+      ->addWhere('client_id', '=', $client['id'])
+      ->execute()
+      ->first();
+
+    $this->assertEquals($client['id'], $retrievedToken['client_id']);
+    $this->assertEquals($tokenCreateValues['access_token'], $retrievedToken['access_token']);
+    $this->assertEquals($tokenCreateValues['refresh_token'], $retrievedToken['refresh_token']);
+  }
+
+  public function testAnonymousSessionTokensCanBeDeleted() {
+    self::assertNull(CRM_Core_Session::getLoggedInContactID());
+
+    $client = $this->createClient();
+    $tokenCreateValues = $this->getTestTokenCreateValues($client, 'anon');
+
+    Civi\Api4\OAuthSessionToken::create(FALSE)
+      ->setValues($tokenCreateValues)
+      ->execute();
+
+    \Civi\Api4\OAuthSessionToken::deleteAll(FALSE)
+      ->execute();
+
+    $retrievedTokens = \Civi\Api4\OAuthSessionToken::get(FALSE)
+      ->execute()
+      ->first();
+
+    $this->assertEmpty($retrievedTokens);
+  }
+
+  public function testLoggedInSessionCanHoldToken() {
+    $this->createLoggedInUser();
+    self::assertIsNumeric(CRM_Core_Session::getLoggedInContactID());
+
+    $client = $this->createClient();
+    $tokenCreateValues = $this->getTestTokenCreateValues($client, 'loggedIn');
+
+    Civi\Api4\OAuthSessionToken::create(FALSE)
+      ->setValues($tokenCreateValues)
+      ->execute();
+
+    $retrievedToken = \Civi\Api4\OAuthSessionToken::get(FALSE)
+      ->addWhere('client_id', '=', $client['id'])
+      ->execute()
+      ->first();
+
+    $this->assertEquals($client['id'], $retrievedToken['client_id']);
+    $this->assertEquals($tokenCreateValues['access_token'], $retrievedToken['access_token']);
+    $this->assertEquals($tokenCreateValues['refresh_token'], $retrievedToken['refresh_token']);
+  }
+
+  public function testLoggingOutDeletesTokens() {
+    $this->createLoggedInUser();
+    self::assertIsNumeric(CRM_Core_Session::getLoggedInContactID());
+
+    $client = $this->createClient();
+    $tokenCreateValues = $this->getTestTokenCreateValues($client, 'loggedIn');
+
+    Civi\Api4\OAuthSessionToken::create(FALSE)
+      ->setValues($tokenCreateValues)
+      ->execute();
+
+    // log out
+    CRM_Core_Session::singleton()->reset();
+    self::assertNull(CRM_Core_Session::getLoggedInContactID());
+
+    $retrievedTokens = \Civi\Api4\OAuthSessionToken::get(FALSE)
+      ->execute()
+      ->first();
+
+    $this->assertEmpty($retrievedTokens);
+  }
+
+}