From b30f1c8f3804be09dd1ace927431dafa58e5488d Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 26 Oct 2020 17:34:12 -0700 Subject: [PATCH] dev/core#2141 - APIv4 - Add OAuthClient & OAuthSysToken with unit-test --- ext/oauth-client/Civi/Api4/OAuthClient.php | 20 +++ ext/oauth-client/Civi/Api4/OAuthSysToken.php | 27 +++ .../tests/phpunit/api/v4/OAuthClientTest.php | 64 +++++++ .../phpunit/api/v4/OAuthSysTokenTest.php | 160 ++++++++++++++++++ 4 files changed, 271 insertions(+) create mode 100644 ext/oauth-client/Civi/Api4/OAuthClient.php create mode 100644 ext/oauth-client/Civi/Api4/OAuthSysToken.php create mode 100644 ext/oauth-client/tests/phpunit/api/v4/OAuthClientTest.php create mode 100644 ext/oauth-client/tests/phpunit/api/v4/OAuthSysTokenTest.php diff --git a/ext/oauth-client/Civi/Api4/OAuthClient.php b/ext/oauth-client/Civi/Api4/OAuthClient.php new file mode 100644 index 0000000000..b79d1b9b9c --- /dev/null +++ b/ext/oauth-client/Civi/Api4/OAuthClient.php @@ -0,0 +1,20 @@ + ['access CiviCRM'], + 'default' => ['manage OAuth client'], + ]; + } + +} diff --git a/ext/oauth-client/Civi/Api4/OAuthSysToken.php b/ext/oauth-client/Civi/Api4/OAuthSysToken.php new file mode 100644 index 0000000000..f4bc08a847 --- /dev/null +++ b/ext/oauth-client/Civi/Api4/OAuthSysToken.php @@ -0,0 +1,27 @@ + ['access CiviCRM'], + 'default' => ['manage OAuth client'], + 'delete' => ['manage OAuth client'], + 'get' => ['manage OAuth client'], + 'create' => ['manage OAuth client secrets'], + 'update' => ['manage OAuth client secrets'], + // In theory, there might be cases to 'create' or 'update' an OAuthSysToken + // without access to its secrets, but you should think through the + // lifecycle/errors/permissions. For now, easier to limit 'create'/update'. + ]; + } + +} diff --git a/ext/oauth-client/tests/phpunit/api/v4/OAuthClientTest.php b/ext/oauth-client/tests/phpunit/api/v4/OAuthClientTest.php new file mode 100644 index 0000000000..d9edf4041c --- /dev/null +++ b/ext/oauth-client/tests/phpunit/api/v4/OAuthClientTest.php @@ -0,0 +1,64 @@ +install('oauth-client')->apply(); + } + + public function setUp() { + parent::setUp(); + $this->assertEquals(0, CRM_Core_DAO::singleValueQuery('SELECT count(*) FROM civicrm_oauth_client')); + } + + public function tearDown() { + parent::tearDown(); + } + + /** + * Basic sanity check - create, read, and delete a client. + */ + public function testBasic() { + $random = CRM_Utils_String::createRandom(16, CRM_Utils_String::ALPHANUMERIC); + $usePerms = function($ps) { + $base = ['access CiviCRM']; + \CRM_Core_Config::singleton()->userPermissionClass->permissions = array_merge($base, $ps); + }; + + $usePerms(['manage OAuth client']); + $create = Civi\Api4\OAuthClient::create()->setValues([ + 'guid' => "example-id-$random" , + 'secret' => "example-secret-$random", + ])->execute(); + $this->assertEquals(1, $create->count()); + $client = $create->first(); + $this->assertEquals("example-id-$random", $client['guid']); + $this->assertEquals("example-secret-$random", $client['secret']); + + $usePerms(['manage OAuth client']); + // If we can tighten perm model: $usePerms(['manage OAuth client', 'manage OAuth client secrets']); + $get = Civi\Api4\OAuthClient::get(0)->addWhere('guid', '=', "example-id-$random")->execute(); + $this->assertEquals(1, $get->count()); + $client = $get->first(); + $this->assertEquals("example-id-$random", $client['guid']); + $this->assertEquals("example-secret-$random", $client['secret']); + + $usePerms(['manage OAuth client']); + Civi\Api4\OAuthClient::delete(0)->addWhere('guid', '=', "example-id-$random")->execute(); + $get = Civi\Api4\OAuthClient::get(0)->addWhere('guid', '=', "example-id-$random")->execute(); + $this->assertEquals(0, $get->count()); + } + +} diff --git a/ext/oauth-client/tests/phpunit/api/v4/OAuthSysTokenTest.php b/ext/oauth-client/tests/phpunit/api/v4/OAuthSysTokenTest.php new file mode 100644 index 0000000000..7bb058a11b --- /dev/null +++ b/ext/oauth-client/tests/phpunit/api/v4/OAuthSysTokenTest.php @@ -0,0 +1,160 @@ +install('oauth-client')->apply(); + } + + public function setUp() { + parent::setUp(); + $this->assertEquals(0, CRM_Core_DAO::singleValueQuery('SELECT count(*) FROM civicrm_oauth_client')); + $this->assertEquals(0, CRM_Core_DAO::singleValueQuery('SELECT count(*) FROM civicrm_oauth_systoken')); + } + + public function tearDown() { + parent::tearDown(); + } + + /** + * Create, read, and destroy token - with full access to secrets. + */ + public function testFullApiAccess() { + $random = CRM_Utils_String::createRandom(16, CRM_Utils_String::ALPHANUMERIC); + $usePerms = function($ps) { + $base = ['access CiviCRM']; + \CRM_Core_Config::singleton()->userPermissionClass->permissions = array_merge($base, $ps); + }; + + $usePerms(['manage OAuth client', 'manage OAuth client secrets']); + $createClient = Civi\Api4\OAuthClient::create()->setValues([ + 'guid' => "example-id-$random" , + 'secret' => "example-secret-$random", + ])->execute(); + $client = $createClient->first(); + $this->assertTrue(is_numeric($client['id'])); + + $usePerms(['manage OAuth client', 'manage OAuth client secrets']); + $createToken = Civi\Api4\OAuthSysToken::create()->setValues([ + 'client_id' => $client['id'], + 'access_token' => "example-access-token-$random", + 'refresh_token' => "example-refresh-token-$random", + ])->execute(); + $token = $createToken->first(); + $this->assertTrue(is_numeric($token['id'])); + $this->assertEquals($client['id'], $token['client_id']); + $this->assertEquals("example-access-token-$random", $token['access_token']); + $this->assertEquals("example-refresh-token-$random", $token['refresh_token']); + + $usePerms(['manage OAuth client', 'manage OAuth client secrets']); + $getTokens = Civi\Api4\OAuthSysToken::get()->execute(); + $this->assertEquals(1, count($getTokens)); + // ^^ Started at 0, added 1. + $token = $getTokens->first(); + $this->assertEquals($client['id'], $token['client_id']); + $this->assertEquals("example-access-token-$random", $token['access_token']); + $this->assertEquals("example-refresh-token-$random", $token['refresh_token']); + + $usePerms(['manage OAuth client', 'manage OAuth client secrets']); + $updateToken = Civi\Api4\OAuthSysToken::update() + ->setWhere([['client.guid', '=', "example-id-$random"]]) + ->setValues(['access_token' => "revised-access-token-$random"]) + ->execute(); + + $usePerms(['manage OAuth client', 'manage OAuth client secrets']); + $getTokens = Civi\Api4\OAuthSysToken::get()->execute(); + $this->assertEquals(1, count($getTokens)); + $token = $getTokens->first(); + $this->assertEquals($client['id'], $token['client_id']); + $this->assertEquals("revised-access-token-$random", $token['access_token']); + $this->assertEquals("example-refresh-token-$random", $token['refresh_token']); + } + + /** + * Create, read, and destroy a token - with limited API access (cannot access token secrets). + */ + public function testLimitedApiAccess() { + $random = CRM_Utils_String::createRandom(16, CRM_Utils_String::ALPHANUMERIC); + $usePerms = function($ps) { + $base = ['access CiviCRM']; + \CRM_Core_Config::singleton()->userPermissionClass->permissions = array_merge($base, $ps); + }; + + $usePerms(['manage OAuth client']); + $createClient = Civi\Api4\OAuthClient::create()->setValues([ + 'guid' => "example-id-$random" , + 'secret' => "example-secret-$random", + ])->execute(); + $client = $createClient->first(); + $this->assertTrue(is_numeric($client['id'])); + + // User has some access to tokens -- but secret fields are off limits. + try { + $usePerms(['manage OAuth client']); + Civi\Api4\OAuthSysToken::create()->setValues([ + 'client_id' => $client['id'], + 'access_token' => "ignored-access-token-$random", + 'refresh_token' => "ignored-refresh-token-$random", + ])->execute(); + $this->fail('Expected exception - User should not be able to write secret values.'); + } + catch (\Civi\API\Exception\UnauthorizedException $e) { + // OK + } + + // Tokens with secret values can still be created by system services. + $usePerms(['manage OAuth client']); + $createTokenFull = Civi\Api4\OAuthSysToken::create(FALSE)->setValues([ + 'client_id' => $client['id'], + 'access_token' => "example-access-token-$random", + 'refresh_token' => "example-refresh-token-$random", + ])->execute(); + $token = $createTokenFull->first(); + $this->assertTrue(is_numeric($token['id'])); + $this->assertEquals($client['id'], $token['client_id']); + $this->assertEquals("example-access-token-$random", $token['access_token']); + $this->assertEquals("example-refresh-token-$random", $token['refresh_token']); + + $usePerms(['manage OAuth client']); + $getTokens = Civi\Api4\OAuthSysToken::get()->execute(); + $this->assertEquals(1, count($getTokens)); + // ^^ Started at 0, added 1. + $token = $getTokens->first(); + $this->assertEquals($client['id'], $token['client_id']); + $this->assertArrayNotHasKey('access_token', $token); + $this->assertArrayNotHasKey('refresh_token', $token); + + $usePerms(['manage OAuth client']); + try { + Civi\Api4\OAuthSysToken::update() + ->setWhere([['client.guid', '=', "example-id-$random"]]) + ->setValues(['access_token' => "revised-access-token-$random"]) + ->execute(); + $this->fail('Expected exception - User should not be able to write secret values.'); + } + catch (\Civi\API\Exception\UnauthorizedException $e) { + // OK + } + + $usePerms(['manage OAuth client', 'manage OAuth client secrets']); + $getTokens = Civi\Api4\OAuthSysToken::get()->execute(); + $this->assertEquals(1, count($getTokens)); + $token = $getTokens->first(); + $this->assertEquals($client['id'], $token['client_id']); + $this->assertEquals("example-access-token-$random", $token['access_token']); + $this->assertEquals("example-refresh-token-$random", $token['refresh_token']); + } + +} -- 2.25.1