sqrl-2.0.0-rc1/src/Identities.php
src/Identities.php
<?php
namespace Drupal\sqrl;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Utility\Random;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\sqrl\Entity\IdentityInterface;
use Drupal\user\UserDataInterface;
use Drupal\user\UserInterface;
/**
* Provides services for identities.
*/
class Identities {
use StringManipulation;
private const DEFAULT_EMAIL_DOMAIN = '@local.localhost';
/**
* The IDK.
*
* @var string
*/
protected string $idk;
/**
* The PIDK.
*
* @var string
*/
protected string $pidk;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The user data service.
*
* @var \Drupal\user\UserDataInterface
*/
protected UserDataInterface $userData;
/**
* The random service.
*
* @var \Drupal\Component\Utility\Random
*/
protected Random $random;
/**
* The configuration.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected ImmutableConfig $config;
/**
* The log channel.
*
* @var \Drupal\sqrl\Log
*/
protected Log $logger;
/**
* Identities constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\user\UserDataInterface $user_data
* The user data service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\sqrl\Log $logger
* The log channel.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, UserDataInterface $user_data, ConfigFactoryInterface $config_factory, Log $logger) {
$this->entityTypeManager = $entity_type_manager;
$this->userData = $user_data;
$this->random = new Random();
$this->config = $config_factory->get('sqrl.settings');
$this->logger = $logger;
}
/**
* Set the IDK.
*
* @param string $idk
* The IDK.
*
* @return self
* This identities.
*/
public function setIdk(string $idk): Identities {
$this->idk = $idk;
return $this;
}
/**
* Sets the PIDK.
*
* @param string $pidk
* The PIDK.
*
* @return self
* This identities.
*/
public function setPidk(string $pidk): Identities {
$this->pidk = $pidk;
return $this;
}
/**
* Gets an identity.
*
* @param string $key
* The key.
* @param int|null $uid
* The optional user ID.
*
* @return \Drupal\sqrl\Entity\IdentityInterface|null
* The identity, if found, NULL otherwise.
*/
protected function getIdentity(string $key, ?int $uid = NULL): ?IdentityInterface {
if (empty($key)) {
return NULL;
}
$conditions = [
'idk' => $key,
];
if ($uid !== NULL) {
$conditions['user'] = $uid;
}
try {
/** @var \Drupal\sqrl\Entity\IdentityInterface[] $identities */
$identities = $this->entityTypeManager->getStorage('sqrl_identity')->loadByProperties($conditions);
}
catch (InvalidPluginDefinitionException | PluginNotFoundException) {
// Deliberately ignored.
}
if (!empty($identities)) {
return reset($identities);
}
return NULL;
}
/**
* Get all identities.
*
* @param int $uid
* The user ID.
*
* @return \Drupal\sqrl\Entity\IdentityInterface[]
* The found identities for the given user ID.
*/
public function getIdentities(int $uid): array {
try {
/** @var \Drupal\sqrl\Entity\IdentityInterface[] $identities */
$identities = $this->entityTypeManager->getStorage('sqrl_identity')->loadByProperties([
'user' => $uid,
]);
}
catch (InvalidPluginDefinitionException | PluginNotFoundException) {
// Deliberately ignored.
}
if (!empty($identities)) {
return $identities;
}
return [];
}
/**
* Get an identity by IDK.
*
* @param int|null $uid
* The optional user ID.
*
* @return \Drupal\sqrl\Entity\IdentityInterface|null
* The identity, if found, NULL otherwise.
*/
public function getIdentityByIdk(?int $uid = NULL): ?IdentityInterface {
return $this->getIdentity($this->idk, $uid);
}
/**
* Get an identity by PIDK.
*
* @param int|null $uid
* The optional user ID.
*
* @return \Drupal\sqrl\Entity\IdentityInterface|null
* The identity, if found, NULL otherwise.
*/
public function getIdentityByPidk(?int $uid = NULL): ?IdentityInterface {
return $this->getIdentity($this->pidk, $uid);
}
/**
* Removes all identities for a cancelled account.
*
* @param \Drupal\user\UserInterface $account
* The account.
*/
public function cancel(UserInterface $account): void {
foreach ($this->getIdentities($account->id()) as $identity) {
try {
if ($identity->removeUser($account)) {
$identity->save();
}
}
catch (EntityStorageException) {
}
}
}
/**
* Updates the password of an identity.
*
* @param \Drupal\sqrl\Entity\IdentityInterface $identity
* The identity.
* @param string $secret
* The secret.
* @param bool $encrypt
* Flag, if the password should be encrypted.
*/
public function updatePassword(IdentityInterface $identity, string $secret, bool $encrypt): void {
if ($this->config->get('allow_multiple_ids_per_account') || $this->config->get('allow_multiple_accounts_per_id')) {
// When there is NOT a 1:1-relationship between SQRL ID and user account,
// we can't encrypt and decrypt user passwords while they should be
// unaccessible.
return;
}
$users = $identity->getUsers();
if (count($users) !== 1) {
// Well, something is not as it should be, let's log this as an exception.
$this->logger->error('Identity %id should have exactly one account associated', [
'%id' => $identity->id(),
]);
return;
}
$ciphers = openssl_get_cipher_methods();
$cipher = empty($ciphers) ? '' : reset($ciphers);
$nonceSize = openssl_cipher_iv_length($cipher);
$strong = TRUE;
/* @noinspection CryptographicallySecureRandomnessInspection */
$iv = openssl_random_pseudo_bytes($nonceSize, $strong);
if (!$strong || !$iv) {
$this->logger->error('Your system does not produce secure randomness');
return;
}
$user = reset($users);
$name = 'enc-pw-id-' . $identity->id();
if ($encrypt) {
$password = openssl_encrypt(
$user->getPassword(),
$cipher,
$secret,
OPENSSL_RAW_DATA,
$iv
);
$this->userData->set('sqrl', $user->id(), $name, $this->base64Encode($password));
$user->setPassword('');
}
else {
$password = openssl_decrypt(
$this->base64Decode($this->userData->get('sqrl', $user->id(), $name)),
$cipher,
$secret,
OPENSSL_RAW_DATA,
$iv
);
$user->setPassword($password);
}
try {
$user->save();
}
catch (EntityStorageException) {
$this->logger->error('Unable to save user account when encrypting/decrypting password.');
}
}
/**
* Determines if the user has at least one enabled identity.
*
* @param int $uid
* The user ID.
*
* @return bool
* TRUE, if the user has at least one enabled identity, FALSE otherwise.
*/
public function hasUserEnabledIdentities(int $uid): bool {
try {
return (bool) $this->entityTypeManager->getStorage('sqrl_identity')->getQuery()
->accessCheck(FALSE)
->condition('user', $uid)
->condition('status', 1)
->notExists('sid')
->count()
->execute();
}
catch (InvalidPluginDefinitionException | PluginNotFoundException) {
}
return FALSE;
}
/**
* Gets a dummy, random email address.
*
* @return string
* The email address.
*/
public function dummyMail(): string {
return urlencode($this->random->string(15)) . self::DEFAULT_EMAIL_DOMAIN;
}
/**
* Determines if the email address is a dummy one.
*
* @param string $mail
* The email address.
*
* @return bool
* TRUE, if the email address is a dummy one, FALSE otherwise.
*/
public function isDummyMail(string $mail): bool {
return str_contains($mail, self::DEFAULT_EMAIL_DOMAIN);
}
}
