You are here

final class TeamMemberApiProductAccessHandler in Apigee Edge 8

Default team member API product access handler implementation.

Some inspiration and code borrowed from core's EntityAccessControlHandler.

Hierarchy

Expanded class hierarchy of TeamMemberApiProductAccessHandler

1 string reference to 'TeamMemberApiProductAccessHandler'
apigee_edge_teams.services.yml in modules/apigee_edge_teams/apigee_edge_teams.services.yml
modules/apigee_edge_teams/apigee_edge_teams.services.yml
1 service uses TeamMemberApiProductAccessHandler
apigee_edge_teams.team_member_api_product_access_handler in modules/apigee_edge_teams/apigee_edge_teams.services.yml
Drupal\apigee_edge_teams\TeamMemberApiProductAccessHandler

File

modules/apigee_edge_teams/src/TeamMemberApiProductAccessHandler.php, line 36

Namespace

Drupal\apigee_edge_teams
View source
final class TeamMemberApiProductAccessHandler implements TeamMemberApiProductAccessHandlerInterface {

  /**
   * The module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  private $moduleHandler;

  /**
   * Stores calculated access check results.
   *
   * @var array
   */
  private $accessCache = [];

  /**
   * The team permission handler.
   *
   * @var \Drupal\apigee_edge_teams\TeamPermissionHandlerInterface
   */
  private $teamPermissionHandler;

  /**
   * The currently logged-in user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  private $currentUser;

  /**
   * The team membership manager service.
   *
   * @var \Drupal\apigee_edge_teams\TeamMembershipManagerInterface
   */
  private $teamMembershipManager;

  /**
   * TeamApiProductAccessHandler constructor.
   *
   * @param \Drupal\apigee_edge_teams\TeamMembershipManagerInterface $team_membership_manager
   *   The team membership manager service.
   * @param \Drupal\apigee_edge_teams\TeamPermissionHandlerInterface $team_permission_handler
   *   The team permission handler.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler service.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The currently logged-in user.
   */
  public function __construct(TeamMembershipManagerInterface $team_membership_manager, TeamPermissionHandlerInterface $team_permission_handler, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
    $this->teamMembershipManager = $team_membership_manager;
    $this->teamPermissionHandler = $team_permission_handler;
    $this->moduleHandler = $module_handler;
    $this->currentUser = $current_user;
  }

  /**
   * {@inheritdoc}
   */
  public function access(ApiProductInterface $api_product, string $operation, TeamInterface $team, AccountInterface $account = NULL, bool $return_as_object = FALSE) {
    if ($account === NULL) {
      $account = $this->currentUser;
    }
    if (($return = $this
      ->getCache($api_product, $operation, $team, $account)) !== NULL) {

      // Cache hit, no work necessary.
      return $return_as_object ? $return : $return
        ->isAllowed();
    }
    if ($account
      ->isAnonymous()) {
      $return = AccessResult::forbidden('Anonymous user can not be member of a team.');
    }
    else {
      try {
        $developer_team_ids = $this->teamMembershipManager
          ->getTeams($account
          ->getEmail());
      } catch (\Exception $e) {
        $developer_team_ids = [];
      }
      if (in_array($team
        ->id(), $developer_team_ids)) {

        // We grant access to the entity if both of these conditions are met:
        // - No modules say to deny access.
        // - At least one module says to grant access.
        $access = $this->moduleHandler
          ->invokeAll('apigee_edge_teams_team_api_product_access', [
          $api_product,
          $operation,
          $team,
          $account,
        ]);
        $return = $this
          ->processAccessHookResults($access);

        // Also execute the default access check except when the access result
        // is already forbidden, as in that case, it can not be anything else.
        if (!$return
          ->isForbidden()) {
          $return = $return
            ->orIf($this
            ->checkAccess($api_product, $operation, $team, $account));
        }
      }
      else {
        $return = AccessResultForbidden::forbidden("{$account->getEmail()} is not member of {$team->id()} team.");
      }
    }
    $this
      ->setCache($return, $api_product, $operation, $team, $account);
    return $return_as_object ? $return : $return
      ->isAllowed();
  }

  /**
   * Performs access checks.
   *
   * @param \Drupal\apigee_edge\Entity\ApiProductInterface $api_product
   *   The API Product entity for which to check access.
   * @param string $operation
   *   The entity operation. Usually one of 'view', 'update', 'create',
   *   'delete' or 'assign".
   * @param \Drupal\apigee_edge_teams\Entity\TeamInterface $team
   *   The team for which to check access.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The team member for which to check access.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The access result.
   */
  private function checkAccess(ApiProductInterface $api_product, string $operation, TeamInterface $team, AccountInterface $account) : AccessResultInterface {
    if (!in_array($operation, [
      'view',
      'view label',
      'assign',
    ])) {
      return AccessResult::neutral(sprintf('%s is not supported by %s.', $operation, __FUNCTION__));
    }
    $product_visibility = $api_product
      ->getAttributeValue('access') ?? 'public';
    return AccessResult::allowedIf(in_array("api_product_access_{$product_visibility}", $this->teamPermissionHandler
      ->getDeveloperPermissionsByTeam($team, $account)))
      ->addCacheableDependency($team)
      ->addCacheableDependency($api_product)
      ->addCacheableDependency($account);
  }

  /**
   * We grant access to the entity if all conditions are met.
   *
   * Conditions:
   * - No modules say to deny access.
   * - At least one module says to grant access.
   *
   * @param \Drupal\Core\Access\AccessResultInterface[] $access
   *   An array of access results of the fired access hook.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The combined result of the various access checks' results. All their
   *   cacheability metadata is merged as well.
   *
   * @see \Drupal\Core\Access\AccessResultInterface::orIf()
   */
  protected function processAccessHookResults(array $access) : AccessResultInterface {

    // No results means no opinion.
    if (empty($access)) {
      return AccessResult::neutral();
    }

    /** @var \Drupal\Core\Access\AccessResultInterface $result */
    $result = array_shift($access);
    foreach ($access as $other) {
      $result = $result
        ->orIf($other);
    }
    return $result;
  }

  /**
   * Tries to retrieve a previously cached access value from the static cache.
   *
   * @param \Drupal\apigee_edge\Entity\ApiProductInterface $api_product
   *   The API Product entity for which to check access.
   * @param string $operation
   *   The entity operation. Usually one of 'view', 'update', 'create',
   *   'delete' or 'assign".
   * @param \Drupal\apigee_edge_teams\Entity\TeamInterface $team
   *   The team for which to check access.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The team member for which to check access.
   *
   * @return \Drupal\Core\Access\AccessResultInterface|null
   *   The cached AccessResult, or NULL if there is no record for the given
   *   API Product, operation, and team and account in the cache.
   */
  protected function getCache(ApiProductInterface $api_product, string $operation, TeamInterface $team, AccountInterface $account) : ?AccessResultInterface {

    // Return from cache if a value has been set for it previously.
    if (isset($this->accessCache[$team
      ->id()][$account
      ->id()][$api_product
      ->id()][$operation])) {
      return $this->accessCache[$team
        ->id()][$account
        ->id()][$api_product
        ->id()][$operation];
    }
    return NULL;
  }

  /**
   * Statically caches whether the given user has access.
   *
   * @param \Drupal\Core\Access\AccessResultInterface $access
   *   The access result.
   * @param \Drupal\apigee_edge\Entity\ApiProductInterface $api_product
   *   The API Product entity for which to check access.
   * @param string $operation
   *   The entity operation. Usually one of 'view', 'update', 'create',
   *   'delete' or 'assign".
   * @param \Drupal\apigee_edge_teams\Entity\TeamInterface $team
   *   The team for which to check access.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The team member for which to check access.
   */
  protected function setCache(AccessResultInterface $access, ApiProductInterface $api_product, string $operation, TeamInterface $team, AccountInterface $account) : void {

    // Save the given value in the static cache and directly return it.
    $this->accessCache[$team
      ->id()][$account
      ->id()][$api_product
      ->id()][$operation] = $access;
  }

  /**
   * {@inheritdoc}
   */
  public function resetCache() : void {
    $this->accessCache = [];
  }

}

Members

Namesort descending Modifiers Type Description Overrides
TeamMemberApiProductAccessHandler::$accessCache private property Stores calculated access check results.
TeamMemberApiProductAccessHandler::$currentUser private property The currently logged-in user.
TeamMemberApiProductAccessHandler::$moduleHandler private property The module handler service.
TeamMemberApiProductAccessHandler::$teamMembershipManager private property The team membership manager service.
TeamMemberApiProductAccessHandler::$teamPermissionHandler private property The team permission handler.
TeamMemberApiProductAccessHandler::access public function Checks access to an operation on a given API product. Overrides TeamMemberApiProductAccessHandlerInterface::access
TeamMemberApiProductAccessHandler::checkAccess private function Performs access checks.
TeamMemberApiProductAccessHandler::getCache protected function Tries to retrieve a previously cached access value from the static cache.
TeamMemberApiProductAccessHandler::processAccessHookResults protected function We grant access to the entity if all conditions are met.
TeamMemberApiProductAccessHandler::resetCache public function Clears all cached access checks. Overrides TeamMemberApiProductAccessHandlerInterface::resetCache
TeamMemberApiProductAccessHandler::setCache protected function Statically caches whether the given user has access.
TeamMemberApiProductAccessHandler::__construct public function TeamApiProductAccessHandler constructor.