cas_mock_server-8.x-1.0/tests/src/Context/CasMockServerContext.php
tests/src/Context/CasMockServerContext.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\cas_mock_server\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\TableNode;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\DrupalExtension\Context\RawDrupalContext;
use Drupal\cas_mock_server\ServerManagerInterface;
use Drupal\cas_mock_server\UserManagerInterface;
use Drupal\externalauth\ExternalAuthInterface;
/**
* Step definitions for using the CAS mock server in Behat scenarios.
*/
class CasMockServerContext extends RawDrupalContext {
/**
* Whether the mock server should be disabled when the scenario ends.
*
* @var bool
*/
protected $disableMockServerAfterScenario = FALSE;
/**
* A list of users that were created by a scenario.
*
* These are being tracked so they can be cleaned up after the scenario ends.
* The array is an associative array keyed by username and having the user
* email as values.
*
* @var string[]
*/
protected $users = [];
/**
* Constructs a new CasMockServerContext object.
*
* @param array $attributesMap
* Human readable attribute labels keyed by CAS attribute machine names.
* Do not access this property directly, use ::getAttributesMap() instead.
* @param \Drupal\cas_mock_server\ServerManagerInterface $serverManager
* The CAS mock server manager service.
* @param \Drupal\cas_mock_server\UserManagerInterface $casUserManager
* The CAS mock user manager service.
* @param \Drupal\externalauth\ExternalAuthInterface $externalAuth
* The external authentication service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager service.
*/
public function __construct(
protected array $attributesMap,
protected ServerManagerInterface $serverManager,
protected UserManagerInterface $casUserManager,
protected ExternalAuthInterface $externalAuth,
protected EntityTypeManagerInterface $entityTypeManager,
) {}
/**
* Enables the mock server.
*
* @param \Behat\Behat\Hook\Scope\BeforeScenarioScope $scope
* The before scenario scope.
*
* @BeforeScenario @casMockServer
*/
public function startMockServer(BeforeScenarioScope $scope): void {
if (!$this->serverManager->isServerActive()) {
$this->disableMockServerAfterScenario = TRUE;
$this->serverManager->start();
}
}
/**
* Disables the mock server.
*
* @AfterScenario @casMockServer
*/
public function stopMockServer(): void {
if ($this->disableMockServerAfterScenario) {
$this->disableMockServerAfterScenario = FALSE;
$this->serverManager->stop();
}
}
/**
* Clean up any CAS users created in the scenario.
*
* @AfterScenario @casMockServer
*/
public function cleanCasUsers(): void {
// Early bailout if there are no users to clean up.
if (empty($this->users)) {
return;
}
// Delete the users for the mock user storage.
$this->casUserManager->deleteUsers(array_keys($this->users));
// Delete users that might have been created in Drupal after logging in
// through CAS.
$user_storage = $this->entityTypeManager->getStorage('user');
$query = $user_storage->getQuery();
$or_condition = $query->orConditionGroup()
->condition('name', array_keys($this->users), 'IN')
// Some users, created in Drupal, might have a different name than the CAS
// user name as some event subscribers are able to alter them. Do an
// additional check by email.
->condition('mail', array_filter(array_values($this->users)), 'IN');
$user_ids = $user_storage->getQuery()->condition($or_condition)->accessCheck(FALSE)->execute();
$users = $user_storage->loadMultiple($user_ids);
if (!empty($users)) {
foreach ($users as $user) {
user_cancel([], $user->id(), 'user_cancel_delete');
}
$this->getDriver()->processBatch();
}
$this->users = [];
}
/**
* Registers users in the mock CAS service.
*
* This takes a table of user attribute table, with the first row containing
* human readable headers that are defined in the `attributes_map` parameter
* in `behat.yml`. Example table format:
*
* @codingStandardsIgnoreStart
* | Username | Email | Password | First name | Last name | Local username |
* | chuck | chuck@norris.eu | Qwerty | Chuck | Norris | chuck_local |
* | jb007 | 007@mi6.eu | shaken_stirred | James | Bond | |
* @codingStandardsIgnoreEnd
*
* The `Username`, `Email` and `Password` columns are required. All other
* attributes are user defined. The optional `Local username` can be used to
* link the CAS user to an existing Drupal account.
*
* The CAS module might create Drupal user accounts for these users on a
* successful authentication. At the end of the scenario the Drupal user
* accounts that match these usernames will be cleaned up.
*
* @param \Behat\Gherkin\Node\TableNode $users_data
* The users to register.
*
* @throws \Exception
* If non-existing local username has been passed.
*
* @Given (the following )CAS users:
*/
public function registerUsers(TableNode $users_data): void {
$users = [];
$attributes_map = $this->getAttributesMap();
foreach ($users_data->getColumnsHash() as $user_data) {
$values = [];
$local_username = NULL;
// Replace the human readable column headers with the machine names of the
// attributes.
foreach ($user_data as $key => $value) {
if ($key === 'Local username' && $value) {
$local_username = $value;
continue;
}
if (array_key_exists($key, $attributes_map)) {
$values[$attributes_map[$key]] = $value;
}
else {
throw new \RuntimeException("Unknown attribute '$key' in user table. Declare it in behat.yml in the attributes_map parameter.");
}
}
// Check that the required attributes are present.
$missing_attributes = array_diff(UserManagerInterface::REQUIRED_ATTRIBUTES, array_keys($values));
if (!empty($missing_attributes)) {
$missing_attributes = implode(',', $missing_attributes);
throw new \RuntimeException("Required attributes '$missing_attributes' are missing in the user table.");
}
$users[$values['username']] = $values;
if ($local_username) {
/** @var \Drupal\user\UserInterface $local_account */
$local_account = $this->entityTypeManager->getStorage('user')->loadByProperties(['name' => $local_username]);
if (!$local_account) {
throw new \Exception("Non-existing Drupal user '$local_username'.");
}
$this->externalAuth->linkExistingAccount($values['username'], 'cas', $local_account);
}
// Keep track of the users that are created so they can be cleaned up
// after the test.
$this->users[$values['username']] = $values['email'];
}
$this->casUserManager->addUsers($users);
}
/**
* Returns the array that maps human readable attributes to machine names.
*
* @return array
* An associative array of CAS attribute names, keyed by human readable
* attribute labels.
*/
protected function getAttributesMap(): array {
// Provide default values for the required attributes.
return array_flip($this->attributesMap + [
'username' => 'Username',
'email' => 'Email',
'password' => 'Password',
]);
}
}
