-
-
Notifications
You must be signed in to change notification settings - Fork 827
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #26044 from totten/jwt_generation_alt
Authx - Add APIv4 support for creating and validating credentials
- Loading branch information
Showing
6 changed files
with
296 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<?php | ||
|
||
/* | ||
+--------------------------------------------------------------------+ | ||
| Copyright CiviCRM LLC. All rights reserved. | | ||
| | | ||
| This work is published under the GNU AGPLv3 license with some | | ||
| permitted exceptions and without any warranty. For full license | | ||
| and copyright information, see https://civicrm.org/licensing | | ||
+--------------------------------------------------------------------+ | ||
*/ | ||
|
||
namespace Civi\Api4\Action\AuthxCredential; | ||
|
||
use Civi\Api4\Generic\Result; | ||
|
||
/** | ||
* Generate a security checksum for anonymous access to CiviCRM. | ||
* | ||
* @method int getContactId() Get contact ID param (required) | ||
* @method $this setContactId(int $contactId) Set the Contact Id | ||
* @method $this setTtl(int $ttl) Set TTL param | ||
* @method int getTtl() get the TTL param; | ||
*/ | ||
class Create extends \Civi\Api4\Generic\AbstractAction { | ||
|
||
/** | ||
* ID of contact | ||
* | ||
* @var int | ||
* @required | ||
*/ | ||
protected $contactId; | ||
|
||
/** | ||
* Expiration time (in seconds). Defaults to 300 seconds | ||
* | ||
* @var int | ||
*/ | ||
protected $ttl = 300; | ||
|
||
/** | ||
* @param \Civi\Api4\Generic\Result $result | ||
*/ | ||
public function _run(Result $result) { | ||
$token = \Civi::service('crypto.jwt')->encode([ | ||
'exp' => time() + $this->ttl, | ||
'sub' => 'cid:' . $this->contactId, | ||
'scope' => 'authx', | ||
]); | ||
|
||
$result[] = [ | ||
'cred' => 'Bearer ' . $token, | ||
]; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<?php | ||
|
||
/* | ||
+--------------------------------------------------------------------+ | ||
| Copyright CiviCRM LLC. All rights reserved. | | ||
| | | ||
| This work is published under the GNU AGPLv3 license with some | | ||
| permitted exceptions and without any warranty. For full license | | ||
| and copyright information, see https://civicrm.org/licensing | | ||
+--------------------------------------------------------------------+ | ||
*/ | ||
|
||
namespace Civi\Api4\Action\AuthxCredential; | ||
|
||
use Civi\Api4\Generic\Result; | ||
|
||
/** | ||
* Validate that a credential is still valid and can be used in CiviCRM. | ||
* | ||
* @method string getCred() Get Token to validate (required) | ||
* @method Validate setCred(string $token) Get contact ID param (required) | ||
*/ | ||
class Validate extends \Civi\Api4\Generic\AbstractAction { | ||
|
||
/** | ||
* Identify the login-flow. Used for policy enforcement. | ||
* | ||
* @var string | ||
*/ | ||
protected $flow = 'script'; | ||
|
||
/** | ||
* Credential to validate | ||
* | ||
* @var string | ||
* Ex: 'Bearer ABCD1234' | ||
* @required | ||
*/ | ||
protected $cred; | ||
|
||
/** | ||
* @param \Civi\Api4\Generic\Result $result | ||
* @throws \Civi\Authx\AuthxException | ||
*/ | ||
public function _run(Result $result) { | ||
$details = [ | ||
'flow' => $this->flow, | ||
'cred' => $this->cred, | ||
'siteKey' => NULL, /* Old school. Hopefully, we don't need to expose this. */ | ||
'useSession' => FALSE, | ||
]; | ||
$auth = new \Civi\Authx\Authenticator(); | ||
$auth->setRejectMode('exception'); | ||
$result[] = $auth->validate($details); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php | ||
|
||
/* | ||
+--------------------------------------------------------------------+ | ||
| Copyright CiviCRM LLC. All rights reserved. | | ||
| | | ||
| This work is published under the GNU AGPLv3 license with some | | ||
| permitted exceptions and without any warranty. For full license | | ||
| and copyright information, see https://civicrm.org/licensing | | ||
+--------------------------------------------------------------------+ | ||
*/ | ||
namespace Civi\Api4; | ||
|
||
/** | ||
* Methods of handling (JWT) authx credentialss | ||
* | ||
* @searchable none | ||
* @since 5.62 | ||
* @package Authx | ||
*/ | ||
class AuthxCredential extends Generic\AbstractEntity { | ||
|
||
/** | ||
* @param bool $checkPermissions | ||
* @return Action\AuthxCredential\create | ||
*/ | ||
public static function create($checkPermissions = TRUE) { | ||
return (new Action\AuthxCredential\Create(__CLASS__, __FUNCTION__)) | ||
->setCheckPermissions($checkPermissions); | ||
} | ||
|
||
/** | ||
* @param bool $checkPermissions | ||
* @return Action\AuthxCredential\validate | ||
*/ | ||
public static function validate($checkPermissions = TRUE) { | ||
return (new Action\AuthxCredential\Validate(__CLASS__, __FUNCTION__)) | ||
->setCheckPermissions($checkPermissions); | ||
} | ||
|
||
/** | ||
* @param bool $checkPermissions | ||
* @return Generic\BasicGetFieldsAction | ||
*/ | ||
public static function getFields($checkPermissions = TRUE) { | ||
return (new Generic\BasicGetFieldsAction(__CLASS__, __FUNCTION__, function() { | ||
return []; | ||
}))->setCheckPermissions($checkPermissions); | ||
} | ||
|
||
public static function permissions() { | ||
return [ | ||
'meta' => ['access CiviCRM'], | ||
'default' => ['administer CiviCRM'], | ||
'create' => ['generate any authx credential'], | ||
'validate' => ['validate any authx credential'], | ||
]; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?php | ||
|
||
namespace api\v4\Authx; | ||
|
||
use Civi\Api4\AuthxCredential; | ||
use Civi\Authx\AuthxException; | ||
use Civi\Test\HeadlessInterface; | ||
use Civi\Test\TransactionalInterface; | ||
use Firebase\JWT\JWT; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
/** | ||
* Test AuthxCredential API methods | ||
* @group headless | ||
*/ | ||
class AuthxCredentialTest extends TestCase implements HeadlessInterface, TransactionalInterface { | ||
|
||
use \Civi\Test\Api4TestTrait; | ||
use \Civi\Test\Api3TestTrait; | ||
use \Civi\Test\ContactTestTrait; | ||
|
||
public function setUpHeadless() { | ||
return \Civi\Test::headless() | ||
->installMe(__DIR__) | ||
->apply(); | ||
} | ||
|
||
public function testGenerateToken(): void { | ||
$this->_apiversion = 4; | ||
$contactRecord = $this->createTestRecord('Contact', ['contact_type' => 'Individual']); | ||
$this->createLoggedInUser(); | ||
$this->setPermissions([ | ||
'access CiviCRM', | ||
]); | ||
try { | ||
AuthxCredential::create()->setContactId($contactRecord['id'])->execute(); | ||
$this->fail('AuthxCredential Should not be created as permission is not granted'); | ||
} | ||
catch (\Exception $e) { | ||
} | ||
$this->setPermissions([ | ||
'access CiviCRM', | ||
'generate any authx credential', | ||
]); | ||
$jwt = AuthxCredential::create()->setContactId($contactRecord['id'])->execute(); | ||
$this->assertNotEmpty($jwt[0]['cred']); | ||
} | ||
|
||
public function testValidation(): void { | ||
$this->_apiversion = 4; | ||
$contactRecord = $this->createTestRecord('Contact', ['contact_type' => 'Individual']); | ||
$this->createLoggedInUser(); | ||
$this->setPermissions([ | ||
'access CiviCRM', | ||
'generate any authx credential', | ||
]); | ||
$jwt = AuthxCredential::create()->setContactId($contactRecord['id'])->execute(); | ||
|
||
$this->setPermissions([ | ||
'access CiviCRM', | ||
'validate any authx credential', | ||
]); | ||
$validate = AuthxCredential::validate()->setCred($jwt[0]['cred'])->execute(); | ||
$this->assertEquals('jwt', $validate[0]['credType']); | ||
$this->assertEquals($contactRecord['id'], $validate[0]['contactId']); | ||
$this->assertEquals('cid:' . $contactRecord['id'], $validate[0]['jwt']['sub']); | ||
|
||
try { | ||
JWT::$timestamp = time() + 360; | ||
AuthxCredential::validate()->setCred($jwt[0]['cred'])->execute(); | ||
$this->fail('Expected exception for expired token'); | ||
} | ||
catch (AuthxException $e) { | ||
$this->assertEquals('Expired token', $e->getMessage()); | ||
} | ||
finally { | ||
JWT::$timestamp = NULL; | ||
} | ||
} | ||
|
||
/** | ||
* Set ACL permissions, overwriting any existing ones. | ||
* | ||
* @param array $permissions | ||
* Array of permissions e.g ['access CiviCRM','access CiviContribute'], | ||
*/ | ||
protected function setPermissions(array $permissions): void { | ||
\CRM_Core_Config::singleton()->userPermissionClass->permissions = $permissions; | ||
} | ||
|
||
} |