yoti-8.x-2.5/src/YotiHelper.php

src/YotiHelper.php
<?php

namespace Drupal\yoti;

use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Url;
use Drupal\user\Entity\User;
use Drupal\yoti\Models\YotiUserModel;
use Yoti\ActivityDetails;
use Yoti\Entity\Profile;

require_once __DIR__ . '/../sdk/boot.php';

/**
 * Class YotiHelper.
 *
 * @package Drupal\yoti
 */
class YotiHelper {
  /**
   * Yoti user database table name.
   */
  const YOTI_USER_TABLE_NAME = 'users_yoti';

  /**
   * Yoti link button default text.
   */
  const YOTI_LINK_BUTTON_DEFAULT_TEXT = 'Use Yoti';

  /**
   * Yoti PEM file upload location.
   */
  const YOTI_PEM_FILE_UPLOAD_LOCATION = 'private://yoti';

  /**
   * Yoti selfie filename attribute.
   */
  const ATTR_SELFIE_FILE_NAME = 'selfie_filename';

  /**
   * Yoti Drupal SDK identifier.
   */
  const SDK_IDENTIFIER = 'Drupal';

  /**
   * Age verification attribute.
   */
  const AGE_VERIFICATION_ATTR = 'age_verified';

  /**
   * Yoti Hub URL.
   */
  const YOTI_HUB_URL = 'https://hub.yoti.com';

  /**
   * Permission to view Yoti selfie images.
   */
  const YOTI_PERMISSION_VIEW_SELFIE = 'view yoti selfie images';

  /**
   * Field for selfie bin file.
   */
  const YOTI_BIN_FIELD_SELFIE = 'selfie';

  /**
   * MySQL Database connection.
   *
   * @var \Drupal\Core\Database\Driver\mysql\Connection
   */
  protected $database;

  /**
   * User storage Interface.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $userStorage;

  /**
   * Logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * Yoti plugin config data.
   *
   * @var \Drupal\yoti\YotiConfigInterface
   */
  private $config;

  /**
   * Yoti SDK Service.
   *
   * @var \Drupal\yoti\YotiSdkInterface
   */
  private $sdk;

  /**
   * Cache tag invalidator.
   *
   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
   */
  private $cacheTagsInvalidator;

  /**
   * YotiHelper constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityManager
   *   Entity Type Manager.
   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cacheTagsInvalidator
   *   Cache tags invalidator.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   Logger Factory.
   * @param \Drupal\yoti\YotiSdkInterface $sdk
   *   Yoti SDK.
   * @param \Drupal\yoti\YotiConfigInterface $config
   *   Yoti Configuration.
   */
  public function __construct(
    EntityTypeManagerInterface $entityManager,
    CacheTagsInvalidatorInterface $cacheTagsInvalidator = NULL,
    LoggerChannelFactoryInterface $loggerFactory = NULL,
    YotiSdkInterface $sdk = NULL,
    YotiConfigInterface $config = NULL
  ) {
    try {
      $this->userStorage = $entityManager->getStorage('user');
    }
    catch (\Exception $e) {
      YotiHelper::setFlash('Could not retrieve user data', 'error');
    }

    // Fetch services for backwards compatibility.
    // All injected services will be required in the next major release.
    if (is_null($cacheTagsInvalidator)) {
      $cacheTagsInvalidator = \Drupal::service('cache_tags.invalidator');
    }
    if (is_null($loggerFactory)) {
      $loggerFactory = \Drupal::service('logger.factory');
    }
    if (is_null($sdk)) {
      $sdk = \Drupal::service('yoti.sdk');
    }
    if (is_null($config)) {
      $config = \Drupal::service('yoti.config');
    }

    $this->config = $config;
    $this->cacheTagsInvalidator = $cacheTagsInvalidator;
    $this->logger = $loggerFactory->get('yoti');
    $this->sdk = $sdk;
  }

  /**
   * Link Drupal user to Yoti user.
   *
   * @param mixed $currentUser
   *   Drupal user object.
   *
   * @return mixed
   *   TRUE|FALSE|redirect.
   */
  public function link($currentUser = NULL) {
    if (!$currentUser) {
      $currentUser = \Drupal::currentUser();
    }

    $token = (!empty($_GET['token'])) ? $_GET['token'] : NULL;

    // If no token then ignore.
    if (!$token) {
      YotiHelper::setFlash('Could not get Yoti token.', 'error');

      return FALSE;
    }

    // Init yoti client and attempt to request user details.
    try {
      $yotiClient = $this->sdk->getClient();
      $activityDetails = $yotiClient->getActivityDetails($token);
      $profile = $activityDetails->getProfile();
    }
    catch (\Exception $e) {
      YotiHelper::setFlash('Yoti could not successfully connect to your account.', 'error');

      return FALSE;
    }

    if (!$this->passedAgeVerification($profile)) {
      self::setFlash("Could not log you in as you haven't passed the age verification", 'error');
      return FALSE;
    }

    // Invalidate cache for current user.
    $this->invalidateUserCache($currentUser->id());

    // Check if Yoti user exists.
    $drupalUid = $this->getDrupalUid($activityDetails->getRememberMeId());

    // If Yoti user exists in db but isn't the current account
    // then remove it from Yoti table.
    if (
        $drupalUid
        && $currentUser
        && $currentUser->id() !== $drupalUid
        && !User::load($drupalUid)
    ) {
      // Remove user account.
      YotiUserModel::deleteYotiUserById($drupalUid);
    }

    // If user isn't logged in.
    if ($currentUser->isAnonymous()) {
      // Register new user.
      if (!$drupalUid) {
        // Attempt to connect by email.
        $drupalUid = $this->shouldLoginByEmail($activityDetails);

        // If config 'only log in existing user' is enabled then check
        // if user exists, if not then redirect to login page.
        if (!$drupalUid) {
          if (empty($this->config->getOnlyExisting())) {
            try {
              $drupalUid = $this->createUser($activityDetails);
            }
            catch (\Exception $e) {
              $this->logger->error($e->getMessage());
            }
          }
          else {
            self::storeYotiUser($activityDetails);
            // Generate the registration path.
            $pathToRegister = YotiHelper::getPathFullUrl(Url::fromRoute('yoti.register')->getInternalPath());
            return new TrustedRedirectResponse($pathToRegister);
          }
        }

        // No user id? no account.
        if (!$drupalUid) {
          // If unable to create user then bail.
          self::setFlash('Could not create user account.', 'error');

          return FALSE;
        }
      }

      // Log user in.
      $this->loginUser($drupalUid);
    }
    else {
      // If current logged in user doesn't match yoti user registered then bail.
      if ($drupalUid && $currentUser->id() !== $drupalUid) {
        self::setFlash('This Yoti account is already linked to another account.', 'error');
      }
      // If Drupal user not found in Yoti table then create new Yoti user.
      elseif (!$drupalUid) {
        $this->createYotiUser($currentUser->id(), $activityDetails);
      }
    }

    return TRUE;
  }

  /**
   * Check if age verification applies and is valid.
   *
   * @param \Yoti\Entity\Profile $profile
   *   Yoti user profile Object.
   *
   * @return bool
   *   Return TRUE or FALSE
   */
  public function passedAgeVerification(Profile $profile) {
    return !($this->config->getAgeVerification() && !$this->oneAgeIsVerified($profile));
  }

  /**
   * Check that one age is verified.
   *
   * @param \Yoti\Entity\Profile $profile
   *   Yoti user profile Object.
   *
   * @return bool
   *   Return TRUE or FALSE
   */
  private function oneAgeIsVerified(Profile $profile) {
    $ageVerificationsArr = $this->processAgeVerifications($profile);
    return empty($ageVerificationsArr) || in_array('Yes', array_values($ageVerificationsArr));
  }

  /**
   * Generate URL based on page path.
   *
   * @param mixed $path
   *   Page path.
   *
   * @return string
   *   Generated path URL
   */
  public static function getPathFullUrl($path = NULL) {
    // Get the root path including any subdomain.
    $fullUrl = \Drupal::request()->getBaseUrl();
    if (!empty($path)) {
      // Add the target path to the root path.
      $fullUrl .= ($path[0] === '/') ? $path : '/' . $path;
    }

    return $fullUrl;
  }

  /**
   * Unlink account from currently logged in.
   */
  public function unlink() {
    $currentUser = \Drupal::currentUser();
    // Unlink Yoti user.
    if (!$currentUser->isAnonymous()) {
      $this->deleteSelfie($currentUser->id());
      YotiUserModel::deleteYotiUserById($currentUser->id());
      $this->invalidateUserCache($currentUser->id());
      return TRUE;
    }
    return FALSE;
  }

  /**
   * Delete selfie for given user ID.
   *
   * @param int $userId
   *   The Drupal user ID.
   */
  private function deleteSelfie($userId) {
    $dbProfile = YotiUserModel::getYotiUserById($userId);
    if (!$dbProfile) {
      return;
    }

    $userProfileArr = unserialize($dbProfile['data']);
    if (!isset($userProfileArr[self::ATTR_SELFIE_FILE_NAME])) {
      return;
    }

    $selfieFileName = $userProfileArr[self::ATTR_SELFIE_FILE_NAME];
    $selfieFullPath = self::uploadDir() . '/' . $selfieFileName;
    if (is_file($selfieFullPath)) {
      unlink($selfieFullPath);
    }
  }

  /**
   * Invalidate cache for current user.
   *
   * @param int $userId
   *   The Drupal user ID.
   */
  private function invalidateUserCache($userId) {
    if ($user = $this->userStorage->load($userId)) {
      $this->cacheTagsInvalidator->invalidateTags($user->getCacheTagsToInvalidate());
    }
  }

  /**
   * Store Yoti user in the session.
   *
   * @param \Yoti\ActivityDetails $activityDetails
   *   Yoti user details.
   */
  public static function storeYotiUser(ActivityDetails $activityDetails) {
    $session = \Drupal::service('session');
    if (!$session->isStarted()) {
      $session->migrate();
    }
    $_SESSION['yoti-user'] = serialize($activityDetails);
  }

  /**
   * Retrieve Yoti user from the session.
   *
   * @return \Yoti\ActivityDetails|null
   *   Yoti user details.
   */
  public static function getYotiUserFromStore() {
    $session = \Drupal::service('session');
    if (!$session->isStarted()) {
      $session->migrate();
    }
    return array_key_exists('yoti-user', $_SESSION) ? unserialize($_SESSION['yoti-user']) : NULL;
  }

  /**
   * Remove Yoti user from the session.
   */
  public static function clearYotiUserStore() {
    $session = \Drupal::service('session');
    if (!$session->isStarted()) {
      $session->migrate();
    }
    unset($_SESSION['yoti-user']);
  }

  /**
   * Set notification message.
   *
   * @param string $message
   *   Notification message.
   * @param string $type
   *   Notification status.
   */
  public static function setFlash($message, $type = 'status') {
    \Drupal::messenger()->addMessage($message, $type);
  }

  /**
   * Generate Yoti username.
   *
   * @param \Yoti\Entity\Profile $profile
   *   Yoti user data.
   * @param string $prefix
   *   Yoti username prefix.
   *
   * @return string
   *   Yoti username.
   */
  private function generateUsername(Profile $profile, $prefix = 'yoti.user') {
    $givenNames = $this->getUserGivenNames($profile);
    $familyName = $profile->getFamilyName() ? $profile->getFamilyName()->getValue() : NULL;

    if (NULL !== $givenNames && NULL !== $familyName) {
      $userFullName = $givenNames . ' ' . $familyName;
      $userProvidedPrefix = strtolower(str_replace(' ', '.', $userFullName));
      $prefix = $this->isValidUsername($userProvidedPrefix) ? $userProvidedPrefix : $prefix;
    }

    // Get the number of user name that starts with prefix.
    $usernameCount = YotiUserModel::getUsernameCountByPrefix($prefix);

    // Generate username.
    $username = $prefix;
    if ($usernameCount > 0) {
      do {
        $username = $prefix . ++$usernameCount;
      } while ($this->userStorage->loadByProperties(['name' => $username]));
    }

    return $username;
  }

  /**
   * Generate Yoti user email.
   *
   * @param string $prefix
   *   User email prefix.
   * @param string $domain
   *   Email domain name.
   *
   * @return string
   *   Yoti user email.
   */
  private function generateEmail($prefix = 'yoti.user', $domain = 'example.com') {
    // Get the number of user name that starts with yoti.user prefix.
    $userEmailCount = YotiUserModel::getUserEmailCountByPrefix($prefix);

    // Generate Yoti unique user email.
    $email = "{$prefix}@{$domain}";
    if ($userEmailCount > 0) {
      do {
        $email = $prefix . ++$userEmailCount . "@$domain";
      } while ($this->userStorage->loadByProperties(['mail' => $email]));
    }

    return $email;
  }

  /**
   * If user has more than one given name return the first one.
   *
   * @param \Yoti\Entity\Profile $profile
   *   Yoti user details.
   *
   * @return null|string
   *   Return single user given name
   */
  private function getUserGivenNames(Profile $profile) {
    $givenNamesObj = $profile->getGivenNames();
    $givenNames = $givenNamesObj ? $givenNamesObj->getValue() : NULL;
    $givenNamesArr = explode(' ', $givenNames);
    return (count($givenNamesArr) > 1) ? $givenNamesArr[0] : $givenNames;
  }

  /**
   * Check a username has valid characters.
   *
   * @param string $username
   *   Username to be validated.
   *
   * @return bool|int
   *   Return TRUE or FALSE
   */
  protected function isValidUsername($username) {
    return (NULL === user_validate_name($username));
  }

  /**
   * Create Drupal user.
   *
   * @param \Yoti\ActivityDetails $activityDetails
   *   Yoti user details.
   *
   * @return int
   *   Yoti user ID.
   *
   * @throws \RuntimeException
   */
  private function createUser(ActivityDetails $activityDetails) {
    $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
    $user = User::create();
    $profile = $activityDetails->getProfile();
    $emailObj = $profile->getEmailAddress();
    $userProvidedEmail = $emailObj ? $emailObj->getValue() : NULL;

    // If user has provided an email address and it's not in use then use it,
    // otherwise use Yoti generic email.
    $isValidEmail = \Drupal::service('email.validator')->isValid($userProvidedEmail);
    $userProvidedEmailCanBeUsed = $isValidEmail && !user_load_by_mail($userProvidedEmail);
    $userEmail = $userProvidedEmailCanBeUsed ? $userProvidedEmail : $this->generateEmail();

    // Mandatory settings.
    $user->setPassword(user_password());
    $user->enforceIsNew();
    $user->setEmail($userEmail);
    // This username must be unique and accept only a-Z,0-9, - _ @ .
    $user->setUsername($this->generateUsername($profile));

    // Optional settings.
    $user->set('init', 'email');
    $user->set('langcode', $language);
    $user->set('preferred_langcode', $language);
    $user->set('preferred_admin_langcode', $language);
    $user->activate();
    if (!$user->save()) {
      throw new \RuntimeException('Could not save Yoti user');
    }

    // Set new user ID.
    $userId = $user->id();
    $this->createYotiUser($userId, $activityDetails);

    return $userId;
  }

  /**
   * Get Drupal user UID.
   *
   * @param int $yotiId
   *   Yoti user ID.
   * @param string $field
   *   Drupal option field name.
   *
   * @return int
   *   Returns user UID.
   */
  private function getDrupalUid($yotiId, $field = 'identifier') {
    $col = YotiUserModel::getUserUidByYotiId($yotiId, $field);
    return $col ? reset($col) : NULL;
  }

  /**
   * Create Yoti user.
   *
   * @param int $userId
   *   Drupal user ID.
   * @param \Yoti\ActivityDetails $activityDetails
   *   Yoti user data.
   *
   * @throws \Exception
   */
  public function createYotiUser($userId, ActivityDetails $activityDetails) {
    $profile = $activityDetails->getProfile();
    $meta = $this->processProfileAttributes($profile);

    $this->cleanUserData($meta);

    $selfieFilename = NULL;
    if ($selfie = $profile->getSelfie()) {
      $content = $selfie->getValue()->getContent();
      $uploadDir = self::uploadDir(FALSE);
      if (!is_dir($uploadDir)) {
        \Drupal::service('file_system')->mkdir($uploadDir, 0777, TRUE);
      }

      $selfieFilename = md5("selfie_$userId" . time()) . '.png';
      file_put_contents(self::uploadDir() . "/$selfieFilename", $content);

      $meta[self::ATTR_SELFIE_FILE_NAME] = $selfieFilename;
    }

    YotiUserModel::createYotiUser($userId, $activityDetails, $meta);
  }

  /**
   * Process profile attributes into an associative array.
   *
   * @param \Yoti\Entity\Profile $profile
   *   Yoti user data.
   *
   * @return array
   *   Array of process profile attributes.
   */
  private function processProfileAttributes(Profile $profile) {
    $attrsArr = [];
    $excludedAttrs = [
      Profile::ATTR_DOCUMENT_DETAILS,
      Profile::ATTR_STRUCTURED_POSTAL_ADDRESS,
    ];

    foreach ($profile->getAttributes() as $attrName => $attrObj) {
      if (in_array($attrName, $excludedAttrs) || $attrObj === NULL) {
        continue;
      }
      $value = $attrObj->getValue();
      if ($attrName === Profile::ATTR_DATE_OF_BIRTH && NULL !== $value) {
        $value = $value->format('d-m-Y');
      }
      if ($attrName === Profile::ATTR_SELFIE && NULL !== $value) {
        $value = $value->getContent();
      }
      $attrsArr[$attrName] = $value;
    }

    $ageVerificationsArr = $this->processAgeVerifications($profile);
    if (!empty($ageVerificationsArr)) {
      $attrsArr = array_merge(
            $attrsArr,
            $ageVerificationsArr
        );
    }
    return $attrsArr;
  }

  /**
   * Process age verifications into an associative array.
   *
   * @param \Yoti\Entity\Profile $profile
   *   Yoti user data.
   *
   * @return array
   *   Array of age verifications.
   */
  private function processAgeVerifications(Profile $profile) {
    $ageVerificationsArr = [];
    $ageStr = '';
    /** @var \Yoti\Entity\AgeVerification $ageVerification */
    foreach ($profile->getAgeVerifications() as $ageAttr => $ageVerification) {
      $attrName = str_replace(':', '_', ucwords($ageAttr, '_'));
      $result = $ageVerification->getResult() ? 'Yes' : 'No';
      $ageVerificationsArr[$attrName] = $result;
      $ageStr .= $attrName . ': ' . $result . ',';
    }
    if (!empty($ageStr)) {
      // This is for profile display.
      $ageVerificationsArr[self::AGE_VERIFICATION_ATTR] = rtrim($ageStr, ',');
    }
    return $ageVerificationsArr;
  }

  /**
   * Remove unwanted profile attributes.
   *
   * @param mixed $profileArr
   *   User profile data.
   */
  private function cleanUserData(&$profileArr) {
    $providedAttr = array_keys($profileArr);
    $wantedAttr = array_keys(self::getUserProfileAttributes());
    $unwantedAttr = array_diff($providedAttr, $wantedAttr);
    foreach ($unwantedAttr as $attr) {
      unset($profileArr[$attr]);
    }
    // Don't save selfie to the db.
    unset($profileArr[Profile::ATTR_SELFIE]);
  }

  /**
   * Logs user in.
   *
   * @param int $userId
   *   User ID.
   */
  private function loginUser($userId) {
    if ($user = User::load($userId)) {
      user_login_finalize($user);
    }
  }

  /**
   * File upload directory.
   *
   * @param bool $realPath
   *   File path.
   *
   * @return string
   *   Directory full path.
   */
  public static function uploadDir($realPath = TRUE) {
    $yotiPemUploadDir = YotiHelper::YOTI_PEM_FILE_UPLOAD_LOCATION;
    return $realPath ? \Drupal::service('file_system')->realpath($yotiPemUploadDir) : $yotiPemUploadDir;
  }

  /**
   * File upload dir URL.
   *
   * @return string
   *   Full upload dir URL.
   */
  public static function uploadUrl() {
    return file_create_url(self::uploadDir(FALSE));
  }

  /**
   * Yoti config data.
   *
   * @deprecated use `yoti.config` service instead.
   *
   * @return array
   *   Config data as array.
   */
  public static function getConfig() {
    return \Drupal::service('yoti.config')->getSettings();
  }

  /**
   * Get Yoti app login URL.
   *
   * @deprecated use `yoti.sdk` service instead.
   *
   * @return null|string
   *   Yoti App URL.
   */
  public static function getLoginUrl() {
    return \Drupal::service('yoti.sdk')->getLoginUrl();
  }

  /**
   * Attempt to log user in by email.
   *
   * @param \Yoti\ActivityDetails $activityDetails
   *   Yoti user details Object.
   *
   * @return null|int
   *   Yoti user Id.
   */
  private function shouldLoginByEmail(ActivityDetails $activityDetails) {
    $drupalUid = NULL;
    $profile = $activityDetails->getProfile();
    $emailObj = $profile->getEmailAddress();

    $email = $emailObj ? $emailObj->getValue() : NULL;
    $emailConfig = $this->config->getUserEmail();

    // Attempt to connect by email.
    if ($email && !empty($emailConfig)) {
      $byMail = user_load_by_mail($email);
      if ($byMail) {
        $drupalUid = $byMail->id();
        $this->createYotiUser($drupalUid, $activityDetails);
      }
    }
    return $drupalUid;
  }

  /**
   * Get Yoti user profile attributes.
   *
   * @return array
   *   Yoti user profile attributes
   */
  public static function getUserProfileAttributes() {
    return [
      Profile::ATTR_SELFIE => 'Selfie',
      Profile::ATTR_FULL_NAME => 'Full Name',
      Profile::ATTR_GIVEN_NAMES => 'Given Name(s)',
      Profile::ATTR_FAMILY_NAME => 'Family Name',
      Profile::ATTR_PHONE_NUMBER => 'Mobile Number',
      Profile::ATTR_EMAIL_ADDRESS => 'Email Address',
      Profile::ATTR_DATE_OF_BIRTH => 'Date Of Birth',
      self::AGE_VERIFICATION_ATTR => 'Age Verified',
      Profile::ATTR_POSTAL_ADDRESS => 'Postal Address',
      Profile::ATTR_GENDER => 'Gender',
      Profile::ATTR_NATIONALITY => 'Nationality',
    ];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc