You are here

class Storage in OAuth2 Server 7

Provides Drupal storage (through the underlying Entity API) for the library.

Hierarchy

  • class \Drupal\oauth2_server\Storage implements \OAuth2\Storage\RefreshTokenInterface, \OAuth2\Storage\UserCredentialsInterface, \OAuth2\OpenID\Storage\UserClaimsInterface, \OAuth2\Storage\PublicKeyInterface, \OAuth2\OpenID\Storage\AuthorizationCodeInterface, \OAuth2\Storage\AccessTokenInterface, \OAuth2\Storage\ClientCredentialsInterface, \OAuth2\Storage\JwtBearerInterface

Expanded class hierarchy of Storage

File

lib/Drupal/oauth2_server/Storage.php, line 17

Namespace

Drupal\oauth2_server
View source
class Storage implements AuthorizationCodeInterface, AccessTokenInterface, ClientCredentialsInterface, JwtBearerInterface, RefreshTokenInterface, UserCredentialsInterface, UserClaimsInterface, PublicKeyInterface {

  /* ClientCredentialsInterface */
  public function checkClientCredentials($client_key, $client_secret = null) {
    $client = $this
      ->getClientDetails($client_key);
    if (!$client) {
      return FALSE;
    }
    return oauth2_server_check_client_secret($client['client_secret'], $client_secret);
  }
  public function isPublicClient($client_key) {
    $client = $this
      ->getClientDetails($client_key);
    return $client && empty($client['client_secret']);
  }
  public function getClientDetails($client_key) {
    $client = oauth2_server_client_load($client_key);
    if ($client) {

      // Return a client array in the format expected by the library.
      $client = array(
        'client_id' => $client->client_key,
        'client_secret' => $client->client_secret,
        'public_key' => $client->public_key,
        // The library expects multiple redirect uris to be separated by
        // a space, but the module separates them by a newline, matching
        // Drupal behavior in other areas.
        'redirect_uri' => str_replace(array(
          "\r\n",
          "\r",
          "\n",
        ), ' ', $client->redirect_uri),
      );
    }
    return $client;
  }
  public function getClientScope($client_key) {

    // The module doesn't currently support per-client scopes.
    return NULL;
  }
  public function checkRestrictedGrantType($client_key, $grant_type) {
    $client = oauth2_server_client_load($client_key);
    $server = oauth2_server_load($client->server);
    if (!empty($client->settings['override_grant_types'])) {
      $grant_types = array_filter($client->settings['grant_types']);
      $allow_implicit = !empty($client->settings['allow_implicit']);
    }
    else {

      // Fallback to the global server settings.
      $grant_types = array_filter($server->settings['grant_types']);
      $allow_implicit = !empty($server->settings['allow_implicit']);
    }

    // Implicit flow is enabled by a different setting, so it needs to be
    // added to the check separately.
    if ($allow_implicit) {
      $grant_types['implicit'] = 'implicit';
    }
    return in_array($grant_type, $grant_types);
  }

  /* AccessTokenInterface */
  public function getAccessToken($access_token) {
    $token = oauth2_server_token_load($access_token);
    if (!$token) {
      return FALSE;
    }
    $token_wrapper = entity_metadata_wrapper('oauth2_server_token', $token);

    // If the user is blocked, deny access.
    if ($token->uid && !$token_wrapper->user->status
      ->value()) {
      return FALSE;
    }
    $scopes = array();
    foreach ($token_wrapper->scopes as $scope_wrapper) {
      $scopes[] = $scope_wrapper->name
        ->value();
    }

    // Return a token array in the format expected by the library.
    $token_array = array(
      'server' => $token_wrapper->client->server
        ->raw(),
      'client_id' => $token_wrapper->client->client_key
        ->value(),
      'user_id' => $token->uid ? $token_wrapper->user->uid
        ->value() : NULL,
      'access_token' => $token_wrapper->token
        ->value(),
      'expires' => (int) $token_wrapper->expires
        ->value(),
      'scope' => implode(' ', $scopes),
    );
    if (!empty($token_array['user_id']) && module_exists('uuid')) {
      $token_array['user_uuid'] = $token_wrapper->user->uuid
        ->value();
    }

    // Track last access on the token.
    $this
      ->logAccessTime($token);
    return $token_array;
  }

  /**
   * Track the time the token was accessed.
   *
   * @param \OAuth2ServerToken $token
   */
  protected function logAccessTime(\OAuth2ServerToken $token) {
    if ($token->last_access != REQUEST_TIME) {
      $token->last_access = REQUEST_TIME;
      $token
        ->save();
    }
  }
  public function setAccessToken($access_token, $client_key, $uid, $expires, $scope = null) {
    $client = oauth2_server_client_load($client_key);
    if (!$client) {
      throw new \InvalidArgumentException("The supplied client couldn't be loaded.");
    }

    // If no token was found, start with a new entity.
    $token = oauth2_server_token_load($access_token);
    if (!$token) {

      // The username is not required, the "Client credentials" grant type
      // doesn't provide it, for instance.
      if (!$uid || !user_load($uid)) {
        $uid = 0;
      }
      $token = entity_create('oauth2_server_token', array(
        'type' => 'access',
      ));
      $token->client_id = $client->client_id;
      $token->uid = $uid;
      $token->token = $access_token;
    }
    $token->expires = $expires;
    $this
      ->setScopeData($token, $client->server, $scope);
    $status = $token
      ->save();
    return $status;
  }

  /* AuthorizationCodeInterface */
  public function getAuthorizationCode($code) {
    $code = oauth2_server_authorization_code_load($code);
    if ($code) {
      $code_wrapper = entity_metadata_wrapper('oauth2_server_authorization_code', $code);
      $scopes = array();
      foreach ($code_wrapper->scopes as $scope_wrapper) {
        $scopes[] = $scope_wrapper->name
          ->value();
      }

      // Return a code array in the format expected by the library.
      $code = array(
        'server' => $code_wrapper->client->server
          ->raw(),
        'client_id' => $code_wrapper->client->client_key
          ->value(),
        'user_id' => $code_wrapper->user->uid
          ->value(),
        'authorization_code' => $code_wrapper->code
          ->value(),
        'redirect_uri' => $code_wrapper->redirect_uri
          ->value(),
        'expires' => (int) $code_wrapper->expires
          ->value(),
        'scope' => implode(' ', $scopes),
        'id_token' => $code_wrapper->id_token
          ->value(),
      );
      if (module_exists('uuid')) {
        $code['user_uuid'] = $code_wrapper->user->uuid
          ->value();
      }

      // Examine the id_token and alter the OpenID Connect 'sub' property if
      // necessary. The 'sub' property is usually the user's UID, but this is
      // configurable for backwards compatibility reasons. See:
      // https://www.drupal.org/node/2274357#comment-9779467
      $sub_property = variable_get('oauth2_server_user_sub_property', 'uid');
      if (!empty($code['id_token']) && $sub_property != 'uid') {
        $account = $code_wrapper->user
          ->value();
        $desired_sub = $account->{$sub_property};
        $parts = explode('.', $code['id_token']);
        $claims = json_decode(oauth2_server_base64url_decode($parts[1]), TRUE);
        if (isset($claims['sub']) && $desired_sub != $claims['sub']) {
          $claims['sub'] = $desired_sub;
          $parts[1] = oauth2_server_base64url_encode(json_encode($claims));
          $code['id_token'] = implode('.', $parts);
        }
      }
    }
    return $code;
  }
  public function setAuthorizationCode($code, $client_key, $uid, $redirect_uri, $expires, $scope = null, $id_token = null) {
    $client = oauth2_server_client_load($client_key);
    if (!$client) {
      throw new \InvalidArgumentException("The supplied client couldn't be loaded.");
    }

    // If no code was found, start with a new entity.
    $authorization_code = oauth2_server_authorization_code_load($code);
    if (!$authorization_code) {
      $user = user_load($uid);
      if (!$user) {
        throw new \InvalidArgumentException("The supplied user couldn't be loaded.");
      }
      $authorization_code = entity_create('oauth2_server_authorization_code', array());
      $authorization_code->client_id = $client->client_id;
      $authorization_code->uid = $uid;
      $authorization_code->code = $code;
      $authorization_code->id_token = $id_token;
    }
    $authorization_code->redirect_uri = $redirect_uri;
    $authorization_code->expires = $expires;
    $this
      ->setScopeData($authorization_code, $client->server, $scope);
    $status = $authorization_code
      ->save();
    return $status;
  }
  public function expireAuthorizationCode($code) {
    $code = oauth2_server_authorization_code_load($code);
    if ($code) {
      $code
        ->delete();
    }
  }

  /* JwtBearerInterface */
  public function getClientKey($client_key, $subject) {

    // While the API supports a key per user (subject), the module only supports
    // one key per client, since it's the simpler and more frequent use case.
    $client = $this
      ->getClientDetails($client_key);
    return $client ? $client['public_key'] : FALSE;
  }
  public function getJti($client_key, $subject, $audience, $expires, $jti) {
    $client = oauth2_server_client_load($client_key);
    if (!$client) {

      // The client_key should be validated prior to this method being called,
      // but the library doesn't do that currently.
      return;
    }
    $data = array(
      ':client_id' => $client->client_id,
      ':subject' => $subject,
      ':jti' => $jti,
      ':expires' => $expires,
    );
    $found = db_query('SELECT 1 FROM {oauth2_server_jti}
                        WHERE client_id = :client_id AND subject = :subject
                          AND jti = :jti AND expires = :expires', $data)
      ->fetchField();
    if ($found) {

      // JTI found, return the data back in the expected format.
      return array(
        'issuer' => $client_key,
        'subject' => $subject,
        'jti' => $jti,
        'expires' => $expires,
      );
    }
  }
  public function setJti($client_key, $subject, $audience, $expires, $jti) {
    $client = oauth2_server_client_load($client_key);
    if (!$client) {

      // The client_key should be validated prior to this method being called,
      // but the library doesn't do that currently.
      return;
    }

    // The audience is not stored because it's always previously validated
    // to match the server's token endpoint url. Therefore, it is redundant.
    db_insert('oauth2_server_jti')
      ->fields(array(
      'client_id' => $client->client_id,
      'subject' => $subject,
      'jti' => $jti,
      'expires' => $expires,
    ))
      ->execute();
  }

  /* UserCredentialsInterface */
  public function checkUserCredentials($username, $password) {
    $account = user_load_by_name($username);
    if (!$account) {

      // An email address might have been supplied instead of the username.
      $account = user_load_by_mail($username);
    }
    if ($account && $account->status) {
      require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
      return user_check_password($password, $account);
    }
    return FALSE;
  }
  public function getUserDetails($username) {
    $account = user_load_by_name($username);
    if (!$account) {

      // An email address might have been supplied instead of the username.
      $account = user_load_by_mail($username);
    }
    if ($account) {
      return array(
        'user_id' => $account->uid,
      );
    }
    return FALSE;
  }

  /* UserClaimsInterface */
  public function getUserClaims($uid, $scope) {
    $account = user_load($uid);
    if (!$account) {
      throw new \InvalidArgumentException("The supplied user couldn't be loaded.");
    }
    $requested_scopes = explode(' ', trim($scope));

    // The OpenID Connect 'sub' (Subject Identifier) property is usually the
    // user's UID, but this is configurable for backwards compatibility reasons.
    // See: https://www.drupal.org/node/2274357#comment-9779467
    $sub_property = variable_get('oauth2_server_user_sub_property', 'uid');

    // Prepare the default claims.
    $claims = array(
      'sub' => $account->{$sub_property},
    );
    if (in_array('email', $requested_scopes)) {
      $claims['email'] = $account->mail;
      $claims['email_verified'] = variable_get('user_email_verification', TRUE);
    }
    if (in_array('profile', $requested_scopes)) {
      if (!empty($account->name)) {
        $claims['name'] = format_username($account);
        $claims['preferred_username'] = $account->name;
      }
      if (!empty($account->timezone)) {
        $claims['zoneinfo'] = $account->timezone;
      }
      if (user_access('access user profiles', drupal_anonymous_user())) {
        $claims['profile'] = url('user/' . $account->uid, array(
          'absolute' => TRUE,
        ));
      }
      if ($picture = $this
        ->getUserPicture($account)) {
        $claims['picture'] = $picture;
      }
    }

    // Allow modules to supply additional claims.
    $claims += module_invoke_all('oauth2_server_user_claims', $account, $requested_scopes);

    // Finally, allow modules to alter claims.
    drupal_alter('oauth2_server_user_claims', $claims, $account, $requested_scopes);
    return $claims;
  }

  /* RefreshTokenInterface */
  public function getRefreshToken($refresh_token) {
    $token = oauth2_server_token_load($refresh_token);
    if ($token) {
      $token_wrapper = entity_metadata_wrapper('oauth2_server_token', $token);
      $scopes = array();
      foreach ($token_wrapper->scopes as $scope_wrapper) {
        $scopes[] = $scope_wrapper->name
          ->value();
      }

      // Return a token array in the format expected by the library.
      $token = array(
        'server' => $token_wrapper->client->server
          ->raw(),
        'client_id' => $token_wrapper->client->client_key
          ->value(),
        'user_id' => $token_wrapper->user->uid
          ->value(),
        'refresh_token' => $token_wrapper->token
          ->value(),
        'expires' => (int) $token_wrapper->expires
          ->value(),
        'scope' => implode(' ', $scopes),
      );
      if (module_exists('uuid')) {
        $token['user_uuid'] = $token_wrapper->user->uuid
          ->value();
      }
    }
    return $token;
  }
  public function setRefreshToken($refresh_token, $client_key, $uid, $expires, $scope = null) {
    $client = oauth2_server_client_load($client_key);
    if (!$client) {
      throw new \InvalidArgumentException("The supplied client couldn't be loaded.");
    }

    // If no token was found, start with a new entity.
    $token = oauth2_server_token_load($refresh_token);
    if (!$token) {
      $user = user_load($uid);
      if (!$user) {
        throw new \InvalidArgumentException("The supplied user couldn't be loaded.");
      }
      $token = entity_create('oauth2_server_token', array(
        'type' => 'refresh',
      ));
      $token->client_id = $client->client_id;
      $token->uid = $uid;
      $token->token = $refresh_token;
    }
    $token->expires = $expires;
    $this
      ->setScopeData($token, $client->server, $scope);
    $status = $token
      ->save();
    return $status;
  }
  public function unsetRefreshToken($refresh_token) {
    $token = oauth2_server_token_load($refresh_token);
    if ($token) {
      $token
        ->delete();
    }
  }
  public function unsetAccessToken($access_token) {
    $token = oauth2_server_token_load($access_token);
    if ($token) {
      $token
        ->delete();
    }
  }

  /**
   * Sets the "scopes" entityreference field on the passed entity.
   *
   * @param $entity
   *   The entity containing the "scopes" entityreference field.
   * @param $server
   *   The machine name of the server.
   * @param $scope
   *   Scopes in a space-separated string.
   */
  private function setScopeData($entity, $server, $scope) {
    $entity->scopes = array();
    if ($scope) {
      $scopes = preg_split('/\\s+/', $scope);
      $loaded_scopes = oauth2_server_scope_load_multiple($server, $scopes);
      foreach ($loaded_scopes as $loaded_scope) {
        $entity->scopes[LANGUAGE_NONE][] = array(
          'target_id' => $loaded_scope->scope_id,
        );
      }
    }
  }

  /* PublicKeyInterface */
  public function getPublicKey($client_key = null) {

    // The library allows for per-client keys. The module uses global keys
    // that are regenerated every day, following Google's example.
    $keys = oauth2_server_get_keys();
    return $keys['public_key'];
  }
  public function getPrivateKey($client_key = null) {

    // The library allows for per-client keys. The module uses global keys
    // that are regenerated every day, following Google's example.
    $keys = oauth2_server_get_keys();
    return $keys['private_key'];
  }
  public function getEncryptionAlgorithm($client_key = null) {
    return 'RS256';
  }

  /**
   * Get the user's picture to return as an OpenID Connect claim.
   *
   * @param object $account
   *   The user account object.
   *
   * @return string|NULL
   *   An absolute URL to the user picture, or NULL if none is found.
   */
  protected function getUserPicture($account) {
    if (!variable_get('user_pictures', 0)) {
      return NULL;
    }

    // This uses logic from template_preprocess_user_picture().
    if (!empty($account->picture)) {
      if (is_numeric($account->picture)) {
        $account->picture = file_load($account->picture);
      }
      if (!empty($account->picture->uri)) {
        $picture_path = $account->picture->uri;
      }
    }
    else {
      $picture_path = variable_get('user_picture_default', NULL);
    }
    if (empty($picture_path)) {
      return NULL;
    }
    if (module_exists('image') && file_valid_uri($picture_path) && ($style = variable_get('user_picture_style', ''))) {
      $picture_url = image_style_url($style, $picture_path);
    }
    else {
      $picture_url = url(file_create_url($picture_path), array(
        'absolute' => TRUE,
      ));
    }
    return $picture_url;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Storage::checkClientCredentials public function
Storage::checkRestrictedGrantType public function
Storage::checkUserCredentials public function
Storage::expireAuthorizationCode public function
Storage::getAccessToken public function
Storage::getAuthorizationCode public function
Storage::getClientDetails public function
Storage::getClientKey public function
Storage::getClientScope public function
Storage::getEncryptionAlgorithm public function
Storage::getJti public function
Storage::getPrivateKey public function
Storage::getPublicKey public function
Storage::getRefreshToken public function
Storage::getUserClaims public function
Storage::getUserDetails public function
Storage::getUserPicture protected function Get the user's picture to return as an OpenID Connect claim.
Storage::isPublicClient public function
Storage::logAccessTime protected function Track the time the token was accessed.
Storage::setAccessToken public function
Storage::setAuthorizationCode public function
Storage::setJti public function
Storage::setRefreshToken public function
Storage::setScopeData private function Sets the "scopes" entityreference field on the passed entity.
Storage::unsetAccessToken public function
Storage::unsetRefreshToken public function