-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds Checksum validation solution for Drupal Views (#478)
* Adds Checksum validation solution for Drupal Views * Fix indent * Shifts to use dependency injection * Shift logger to use LoggerChannelTrait * Request vis depency injection * Fix typo * WIP Attempting to fix dependency injection abject failure awaits * Coding standards. --------- Co-authored-by: Luke Stewart <[email protected]>
- Loading branch information
1 parent
cbbc74e
commit 8876e95
Showing
3 changed files
with
205 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
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,87 @@ | ||
<?php | ||
|
||
namespace Drupal\civicrm_entity\Access; | ||
|
||
use Civi\Api4\Contact; | ||
use Drupal\civicrm_entity\CiviCrmApiInterface; | ||
use Drupal\Core\Access\AccessResult; | ||
use Drupal\Core\Logger\LoggerChannelTrait; | ||
use Drupal\Core\Routing\Access\AccessInterface; | ||
use Drupal\Core\Session\AccountInterface; | ||
use Symfony\Component\HttpFoundation\RequestStack; | ||
use Symfony\Component\Routing\Route; | ||
|
||
/** | ||
* Checks access for displaying views using the ContactChecksum plugin. | ||
*/ | ||
class ContactChecksumCheckAccess implements AccessInterface { | ||
|
||
use LoggerChannelTrait; | ||
|
||
/** | ||
* The request stack. | ||
* | ||
* @var \Symfony\Component\HttpFoundation\RequestStack | ||
*/ | ||
protected $requestStack; | ||
|
||
/** | ||
* The CiviCRM API service. | ||
* | ||
* @var \Drupal\civicrm_entity\CiviCrmApiInterface | ||
*/ | ||
protected $civicrmApi; | ||
|
||
/** | ||
* Constructs a ContactChecksumCheckAccess object. | ||
* | ||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack | ||
* The request stack. | ||
* @param \Drupal\civicrm_entity\CiviCrmApiInterface $civicrm_api | ||
* The CiviCRM API bridge. | ||
*/ | ||
public function __construct(RequestStack $request_stack, CiviCrmApiInterface $civicrm_api) { | ||
$this->requestStack = $request_stack; | ||
$this->civicrmApi = $civicrm_api; | ||
} | ||
|
||
/** | ||
* A custom access check. | ||
* | ||
* @param \Drupal\Core\Session\AccountInterface $account | ||
* Run access checks for this account. | ||
* @param \Symfony\Component\Routing\Route $route | ||
* The route for which an access check is being done. | ||
* | ||
* @return \Drupal\Core\Access\AccessResultInterface | ||
* The access result. | ||
*/ | ||
public function access(AccountInterface $account, Route $route) { | ||
$options = unserialize($route->getRequirement('var_options')); | ||
|
||
$access_by_role = !empty(array_intersect(array_filter($options['role']), $account->getRoles())); | ||
if ($access_by_role) { | ||
$this->getlogger('ContactChecksumCheckAccess')->info('Access by role'); | ||
return AccessResult::allowed(); | ||
} | ||
$request = $this->requestStack->getCurrentRequest(); | ||
|
||
$cid1 = filter_var($request->query->get('cid1'), FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); | ||
$checksum = $request->query->get('cs'); | ||
|
||
if (empty($cid1) || empty($checksum)) { | ||
$this->getlogger('ContactChecksumCheckAccess')->info('No cid1 or cs param'); | ||
return AccessResult::forbidden(); | ||
} | ||
|
||
// This forces a call to Civicrm initialize. | ||
$this->civicrmApi->getFields('Contact'); | ||
|
||
$results = Contact::validateChecksum(FALSE) | ||
->setContactId($cid1) | ||
->setChecksum($checksum) | ||
->execute(); | ||
return empty($results[0]['valid']) ? AccessResult::forbidden() : AccessResult::allowed(); | ||
} | ||
|
||
} |
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,111 @@ | ||
<?php | ||
|
||
namespace Drupal\civicrm_entity\Plugin\views\access; | ||
|
||
use Civi\Api4\Contact; | ||
use Drupal\Core\Cache\CacheableDependencyInterface; | ||
use Drupal\Core\Session\AccountInterface; | ||
use Drupal\user\Plugin\views\access\Role; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
use Symfony\Component\Routing\Route; | ||
|
||
/** | ||
* Access plugin that provides role-based access control. | ||
* | ||
* @ingroup views_access_plugins | ||
* | ||
* @ViewsAccess( | ||
* id = "civicrm_entity_contact_checksum", | ||
* title = @Translation("CiviCRM Entity: Contact Checksum"), | ||
* help = @Translation("Access will be granted if the contact checksum validates against contact cid1") | ||
* ) | ||
*/ | ||
class ContactChecksum extends Role implements CacheableDependencyInterface { | ||
|
||
/** | ||
* The CiviCRM API service. | ||
* | ||
* @var \Drupal\civicrm_entity\CiviCrmApiInterface | ||
*/ | ||
protected $civicrmApi; | ||
|
||
/** | ||
* The request stack. | ||
* | ||
* @var \Symfony\Component\HttpFoundation\RequestStack | ||
*/ | ||
protected $requestStack; | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { | ||
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); | ||
$instance->requestStack = $container->get('request_stack'); | ||
$instance->civicrmApi = $container->get('civicrm_entity.api'); | ||
|
||
return $instance; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function access(AccountInterface $account) { | ||
// Check if logged in and has access. | ||
$logged_in_access = parent::access($account); | ||
|
||
if ($logged_in_access) { | ||
return TRUE; | ||
} | ||
$request = $this->requestStack->getCurrentRequest(); | ||
$cid1 = filter_var($request->query->get('cid1'), FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); | ||
$checksum = $request->query->get('cs'); | ||
|
||
// Force CiviCRM to be intialized - ideally we'd use the api | ||
// wrapper however it's api3 and we need api4. | ||
// This forces a call to Civicrm initialize. | ||
$this->civicrmApi->getFields('Contact'); | ||
|
||
$results = Contact::validateChecksum(FALSE) | ||
->setContactId($cid1) | ||
->setChecksum($checksum) | ||
->execute(); | ||
return !empty($results[0]['valid']) ?? FALSE; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function alterRouteDefinition(Route $route) { | ||
$route->setRequirement('_civicrm_entity_checksum_access_check', 'TRUE'); | ||
$route->setRequirement('var_options', serialize($this->options)); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getCacheMaxAge() { | ||
// Ideally we would expire based on the age of the checksum | ||
// however this might not work for anon users. So no caching. | ||
// https://www.drupal.org/docs/drupal-apis/cache-api/cache-max-age | ||
return 0; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getCacheContexts() { | ||
$contexts = parent::getCacheContexts(); | ||
$contexts[] = 'url.query_args:checksum'; | ||
$contexts[] = 'url.query_args:cid'; | ||
return $contexts; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getCacheTags() { | ||
return []; | ||
} | ||
|
||
} |