Skip to content

Commit

Permalink
Oauth - Move CUD permissions to the BAO
Browse files Browse the repository at this point in the history
This ensures permissions are checked regardless of the api version or action being performed.
It also consolidates the code used to check access so that the API checkAccess action will
stay consistently and always perform the same access checks as the other api actions.
  • Loading branch information
colemanw committed Jun 11, 2022
1 parent 2281eb1 commit 8af7e41
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 153 deletions.
94 changes: 94 additions & 0 deletions ext/oauth-client/CRM/OAuth/BAO/OAuthContactToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,100 @@

class CRM_OAuth_BAO_OAuthContactToken extends CRM_OAuth_DAO_OAuthContactToken {

/**
* Create or update OAuthContactToken based on array-data
*
* @param array $record
* @return CRM_OAuth_DAO_OAuthContactToken
*/
public static function create($record) {
self::fillAndValidate($record, CRM_Core_Session::getLoggedInContactID());
return static::writeRecord($record);
}

/**
* @param $id
* @return CRM_OAuth_BAO_OAuthContactToken
* @throws CRM_Core_Exception
* @throws \Civi\API\Exception\UnauthorizedException
*/
public static function del($id) {
$record = ['id' => $id];
self::fillAndValidate($record, CRM_Core_Session::getLoggedInContactID());
return static::deleteRecord($record);
}

/**
* @param string $entityName
* @param string $action
* @param array $record
* @param $userId
* @return bool
* @see CRM_Core_DAO::checkAccess
*/
public static function _checkAccess(string $entityName, string $action, array $record, $userId): bool {
try {
$record['check_permissions'] = TRUE;
self::fillAndValidate($record, $userId);
return TRUE;
}
catch (\Civi\API\Exception\UnauthorizedException) {
return FALSE;
}
}

/**
* @param $record
* @param $userId
* @throws \Civi\API\Exception\UnauthorizedException
*/
private static function fillAndValidate(&$record, $userId) {
if (!empty($record['id']) && empty($record['contact_id'])) {
$record['contact_id'] = CRM_Core_DAO::getFieldValue(__CLASS__, $record['id'], 'contact_id');
}
self::fillContactIdFromTag($record);
if (!empty($record['check_permissions'])) {
$cid = $record['contact_id'];
if (!CRM_Contact_BAO_Contact_Permission::allow($cid, CRM_Core_Permission::EDIT, $userId)) {
throw new \Civi\API\Exception\UnauthorizedException('Access denied to contact');
}
if (!CRM_Core_Permission::check([['manage all OAuth contact tokens', 'manage my OAuth contact tokens']], $userId)) {
throw new \Civi\API\Exception\UnauthorizedException('Access denied to OAuthContactToken');
}
if (
!CRM_Core_Permission::check(['manage all OAuth contact tokens'], $userId) &&
$cid != $userId
) {
throw new \Civi\API\Exception\UnauthorizedException('Access denied to OAuthContactToken for contact');
}
}
}

/**
* @param array $record
*/
private static function fillContactIdFromTag(&$record): void {
if (isset($record['contact_id'])) {
return;
}

$tag = $record['tag'] ?? NULL;

if ('linkContact:' === substr($tag, 0, 12)) {
$record['contact_id'] = substr($tag, 12);
}
elseif ('nullContactId' === $tag) {
$record['contact_id'] = NULL;
}
elseif ('createContact' === $tag) {
$contact = CRM_OAuth_ContactFromToken::createContact($record);
$record['contact_id'] = $contact['id'];
}
else {
$record['contact_id'] = CRM_Core_Session::getLoggedInContactID();
}
}

/**
* @inheritDoc
*/
Expand Down
62 changes: 0 additions & 62 deletions ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Create.php

This file was deleted.

10 changes: 0 additions & 10 deletions ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Delete.php

This file was deleted.

This file was deleted.

10 changes: 0 additions & 10 deletions ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Update.php

This file was deleted.

15 changes: 0 additions & 15 deletions ext/oauth-client/Civi/Api4/OAuthContactToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,6 @@
*/
class OAuthContactToken extends Generic\DAOEntity {

public static function create($checkPermissions = TRUE) {
$action = new Action\OAuthContactToken\Create(static::class, __FUNCTION__);
return $action->setCheckPermissions($checkPermissions);
}

public static function update($checkPermissions = TRUE) {
$action = new Action\OAuthContactToken\Update(static::class, __FUNCTION__);
return $action->setCheckPermissions($checkPermissions);
}

public static function delete($checkPermissions = TRUE) {
$action = new Action\OAuthContactToken\Delete(static::class, __FUNCTION__);
return $action->setCheckPermissions($checkPermissions);
}

public static function permissions(): array {
return [
'meta' => ['access CiviCRM'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public function testContactToken_AnonymousUser_SetNullContactId() {
$this->assertEquals(['foo'], $tokenRecord['scopes']);
$this->assertEquals('example-access-token-value', $tokenRecord['access_token']);
$this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']);
$this->assertNull($tokenRecord['contact_id']);
$this->assertTrue(!isset($tokenRecord['contact_id']));
}

public function testContactToken_AnonymousUser_CreateContact() {
Expand Down Expand Up @@ -385,7 +385,7 @@ public function testContactToken_LoggedInUser_SetNullContactId() {
$this->assertEquals(['foo'], $tokenRecord['scopes']);
$this->assertEquals('example-access-token-value', $tokenRecord['access_token']);
$this->assertEquals('example-refresh-token-value', $tokenRecord['refresh_token']);
$this->assertNull($tokenRecord['contact_id']);
$this->assertTrue(!isset($tokenRecord['contact_id']));
}

}
41 changes: 18 additions & 23 deletions ext/oauth-client/tests/phpunit/api/v4/OAuthContactTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public function testCreate() {
$strangerTokenCreateVals = $this->getTestTokenCreateValues(
$client, $notLoggedInContactID, 'other');

$this->usePerms(['manage all OAuth contact tokens']);
$this->usePerms(['manage all OAuth contact tokens', 'edit all contacts']);
$createOtherContactToken = Civi\Api4\OAuthContactToken::create()
->setValues($strangerTokenCreateVals)
->execute();
Expand All @@ -149,7 +149,7 @@ public function testCreate() {
$this->assertEquals($strangerTokenCreateVals['access_token'], $token['access_token']);
$this->assertEquals($strangerTokenCreateVals['refresh_token'], $token['refresh_token']);

$this->usePerms(['manage my OAuth contact tokens']);
$this->usePerms(['manage my OAuth contact tokens', 'edit my contact']);
$createOwnToken = Civi\Api4\OAuthContactToken::create()
->setValues($ownTokenCreateVals)
->execute();
Expand All @@ -161,7 +161,7 @@ public function testCreate() {
$this->assertEquals($ownTokenCreateVals['access_token'], $token['access_token']);
$this->assertEquals($ownTokenCreateVals['refresh_token'], $token['refresh_token']);

$this->usePerms(['manage my OAuth contact tokens']);
$this->usePerms(['manage my OAuth contact tokens', 'edit all contacts']);
try {
Civi\Api4\OAuthContactToken::create()
->setValues($strangerTokenCreateVals)
Expand Down Expand Up @@ -217,7 +217,7 @@ public function testUpdate() {
$notLoggedInContactID
);

$this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
$this->usePerms(['manage all OAuth contact tokens', 'edit all contacts']);
$updateTokensWithFullAccess = Civi\Api4\OAuthContactToken::update()
->addWhere('contact_id', '=', $notLoggedInContactID)
->setValues(['access_token' => 'stranger-token-revised'])
Expand All @@ -226,7 +226,7 @@ public function testUpdate() {
$token = $updateTokensWithFullAccess->first();
$this->assertEquals($strangerContactToken['id'], $token['id']);

$this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
$this->usePerms(['manage my OAuth contact tokens', 'edit my contact']);
$updateTokensWithLimitedAccess = Civi\Api4\OAuthContactToken::update()
->addWhere('client_id.guid', '=', $client['guid'])
->setValues(['access_token' => 'own-token-revised'])
Expand All @@ -235,25 +235,20 @@ public function testUpdate() {
$token = $updateTokensWithLimitedAccess->first();
$this->assertEquals($ownContactToken['id'], $token['id']);

$this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
$this->usePerms(['manage my OAuth contact tokens', 'edit my contact']);
$getUpdatedTokensWithLimitedAccess = Civi\Api4\OAuthContactToken::get()
->execute();
$this->assertCount(1, $getUpdatedTokensWithLimitedAccess);
$token = $getUpdatedTokensWithLimitedAccess->first();
$this->assertEquals($loggedInContactID, $token['contact_id']);
$this->assertEquals("own-token-revised", $token['access_token']);

$this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
try {
Civi\Api4\OAuthContactToken::update()
->addWhere('contact_id', '=', $notLoggedInContactID)
->setValues(['access_token' => "stranger-token-revised"])
->execute();
$this->fail('Expected \Civi\API\Exception\UnauthorizedException but none was thrown');
}
catch (\Civi\API\Exception\UnauthorizedException $e) {
// exception successfully thrown
}
$this->usePerms(['manage my OAuth contact tokens', 'view all contacts']);
$updates = Civi\Api4\OAuthContactToken::update()
->addWhere('contact_id', '=', $notLoggedInContactID)
->setValues(['access_token' => "stranger-token-revised"])
->execute();
$this->assertCount(0, $updates, 'User should not have access to update');

$this->usePerms(['manage my OAuth contact tokens', 'view my contact']);
$updateTokensForWrongContact = Civi\Api4\OAuthContactToken::update()
Expand All @@ -269,30 +264,30 @@ public function testDelete() {
[$loggedInContactID, $notLoggedInContactID] = $this->createTestContactIDs();
$this->createOwnAndStrangerTokens($client, $loggedInContactID, $notLoggedInContactID);

$this->usePerms(['manage my OAuth contact tokens', 'view all contacts']);
$this->usePerms(['manage my OAuth contact tokens', 'edit all contacts']);
$deleteTokensWithLimitedAccess = Civi\Api4\OAuthContactToken::delete()
->setWhere([['client_id.guid', '=', $client['guid']]])
->execute();

$this->usePerms(['manage my OAuth contact tokens', 'view all contacts']);
$this->usePerms(['manage my OAuth contact tokens', 'edit all contacts']);
$getTokensWithLimitedAccess = Civi\Api4\OAuthContactToken::get()->execute();
$this->assertCount(0, $getTokensWithLimitedAccess);

$this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
$this->usePerms(['manage all OAuth contact tokens', 'edit all contacts']);
$getTokensWithFullAccess = Civi\Api4\OAuthContactToken::get()->execute();
$this->assertCount(1, $getTokensWithFullAccess);

$this->usePerms(['manage my OAuth contact tokens', 'view all contacts']);
$this->expectException(\Civi\API\Exception\UnauthorizedException::class);
Civi\Api4\OAuthContactToken::delete()
$deleted = Civi\Api4\OAuthContactToken::delete()
->addWhere('contact_id', '=', $notLoggedInContactID)
->execute();
$this->assertCount(0, $deleted);
}

public function testGetByScope() {
$client = $this->createClient();

$this->usePerms(['manage all OAuth contact tokens', 'view all contacts']);
$this->usePerms(['manage all OAuth contact tokens', 'edit all contacts']);
$tokenCreationVals = [
'client_id' => $client['id'],
'contact_id' => 1,
Expand Down

0 comments on commit 8af7e41

Please sign in to comment.