diff --git a/ext/oauth-client/CRM/OAuth/BAO/OAuthContactToken.php b/ext/oauth-client/CRM/OAuth/BAO/OAuthContactToken.php index c6b2add4d30..69228fb6225 100644 --- a/ext/oauth-client/CRM/OAuth/BAO/OAuthContactToken.php +++ b/ext/oauth-client/CRM/OAuth/BAO/OAuthContactToken.php @@ -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 $e) { + 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 */ diff --git a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Create.php b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Create.php deleted file mode 100644 index 78055ededf3..00000000000 --- a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Create.php +++ /dev/null @@ -1,62 +0,0 @@ -fillContactIdFromTag(); - $this->assertPermissionForTokenContact(); - parent::_run($result); - } - - private function fillContactIdFromTag(): void { - if (isset($this->values['contact_id'])) { - return; - } - - $tag = $this->values['tag'] ?? NULL; - - if ('linkContact:' === substr($tag, 0, 12)) { - $this->values['contact_id'] = substr($tag, 12); - } - elseif ('nullContactId' === $tag) { - $this->values['contact_id'] = NULL; - } - elseif ('createContact' === $tag) { - $contact = \CRM_OAuth_ContactFromToken::createContact($this->values); - $this->values['contact_id'] = $contact['id']; - } - else { - $this->values['contact_id'] = \CRM_Core_Session::singleton() - ->getLoggedInContactID(); - } - } - - /** - * @throws \Civi\API\Exception\UnauthorizedException - */ - private function assertPermissionForTokenContact(): void { - if (!$this->getCheckPermissions()) { - return; - } - if (\CRM_Core_Permission::check('manage all OAuth contact tokens')) { - return; - } - if (\CRM_Core_Permission::check('manage my OAuth contact tokens')) { - $loggedInContactID = \CRM_Core_Session::singleton() - ->getLoggedInContactID(); - $tokenContactID = $this->values['contact_id'] ?? NULL; - if ($loggedInContactID == $tokenContactID) { - return; - } - } - throw new \Civi\API\Exception\UnauthorizedException(ts( - "You do not have permission to create OAuth tokens for contact id %1", - [1 => $tokenContactID])); - } - -} diff --git a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Delete.php b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Delete.php deleted file mode 100644 index d0d02bc1aec..00000000000 --- a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Delete.php +++ /dev/null @@ -1,10 +0,0 @@ -getLoggedInContactID(); - foreach ($this->where as $clause) { - [$field, $op, $val] = $clause; - if ($field !== 'contact_id') { - continue; - } - if (($op === '=' || $op === 'LIKE') && $val != $loggedInContactID) { - return FALSE; - } - if ($op === 'IN' && $val != [$loggedInContactID]) { - return FALSE; - } - } - $this->addWhere('contact_id', '=', $loggedInContactID); - return TRUE; - } - -} diff --git a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Update.php b/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Update.php deleted file mode 100644 index 954566a6529..00000000000 --- a/ext/oauth-client/Civi/Api4/Action/OAuthContactToken/Update.php +++ /dev/null @@ -1,10 +0,0 @@ -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'], diff --git a/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php index 94579c9c254..5dd61ce826a 100644 --- a/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php +++ b/ext/oauth-client/tests/phpunit/Civi/OAuth/AuthCodeFlowTest.php @@ -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() { @@ -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'])); } } diff --git a/ext/oauth-client/tests/phpunit/api/v4/OAuthContactTokenTest.php b/ext/oauth-client/tests/phpunit/api/v4/OAuthContactTokenTest.php index ed19b092222..360df66235c 100644 --- a/ext/oauth-client/tests/phpunit/api/v4/OAuthContactTokenTest.php +++ b/ext/oauth-client/tests/phpunit/api/v4/OAuthContactTokenTest.php @@ -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(); @@ -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(); @@ -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) @@ -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']) @@ -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']) @@ -235,7 +235,7 @@ 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); @@ -243,17 +243,12 @@ public function testUpdate() { $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() @@ -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,