You are here

class Utility in OAuth2 Server 8

Same name and namespace in other branches
  1. 2.0.x src/Utility.php \Drupal\oauth2_server\Utility

Contains utility methods for the OAuth2 Server.

@package Drupal\oauth2_server

@todo Maybe move some of these methods to other classes (and/or split this class into several utility classes).

Hierarchy

  • class \Drupal\oauth2_server\Utility

Expanded class hierarchy of Utility

7 files declare their use of Utility
AuthorizeForm.php in src/Form/AuthorizeForm.php
ClientForm.php in src/Form/ClientForm.php
OAuth2Controller.php in src/Controller/OAuth2Controller.php
OAuth2ServerTest.php in tests/src/Functional/OAuth2ServerTest.php
oauth2_server.module in ./oauth2_server.module
The Oauth2 Server module.

... See full list

File

src/Utility.php, line 21

Namespace

Drupal\oauth2_server
View source
class Utility {

  /**
   * Returns an array of supported grant types and related data.
   */
  public static function getGrantTypes() {
    return [
      'authorization_code' => [
        'name' => t('Authorization code'),
        'class' => '\\OAuth2\\OpenID\\GrantType\\AuthorizationCode',
      ],
      'client_credentials' => [
        'name' => t('Client credentials'),
        'class' => '\\OAuth2\\GrantType\\ClientCredentials',
      ],
      'urn:ietf:params:oauth:grant-type:jwt-bearer' => [
        'name' => t('JWT bearer'),
        'class' => '\\OAuth2\\GrantType\\JwtBearer',
      ],
      'refresh_token' => [
        'name' => t('Refresh token'),
        'class' => '\\OAuth2\\GrantType\\RefreshToken',
        'settings callback' => [
          __NAMESPACE__ . '\\Form\\ServerForm',
          'refreshTokenSettings',
        ],
        'default settings' => [
          'always_issue_new_refresh_token' => FALSE,
          'unset_refresh_token_after_use' => TRUE,
        ],
      ],
      'password' => [
        'name' => t('User credentials'),
        'class' => '\\OAuth2\\GrantType\\UserCredentials',
      ],
    ];
  }

  /**
   * Decodes base64url encoded data.
   *
   * @param string $data
   *   A string containing the base64url encoded data.
   *
   * @return string|false
   *   The decoded data, or FALSE on failure.
   */
  public static function base64urlDecode($data) {
    $data = str_replace([
      '-',
      '_',
    ], [
      '+',
      '/',
    ], $data);
    return base64_decode($data);
  }

  /**
   * Encodes a string as base64url.
   *
   * @param string $data
   *   The string to encode.
   *
   * @return string
   *   The encoded data.
   */
  public static function base64urlEncode($data) {
    return str_replace([
      '+',
      '/',
    ], [
      '-',
      '_',
    ], base64_encode($data));
  }

  /**
   * Returns the pair of private and public keys used to sign tokens.
   *
   * @return array
   *   An array with the following keys:
   *   - private_key: The private key.
   *   - public_key: The public key certificate (PEM encoded X.509).
   *
   * @see oauth2_server_generate_keys()
   */
  public static function getKeys() {
    $keys = \Drupal::state()
      ->get('oauth2_server.keys', FALSE);
    if (!$keys) {
      $keys = static::generateKeys();
      \Drupal::state()
        ->set('oauth2_server.keys', $keys);
    }
    return $keys;
  }

  /**
   * Generates a pair of private and public keys using OpenSSL.
   *
   * The public key is stored in a PEM encoded X.509 certificate, following
   * Google's example. The certificate can be passed to openssl_verify()
   * directly.
   *
   * @return array
   *   An array with the following keys:
   *   - private_key: The generated private key.
   *   - public_key: The generated public key certificate (PEM encoded X.509).
   */
  public static function generateKeys() {
    $module_path = drupal_get_path('module', 'oauth2_server');
    $config = [
      'config' => DRUPAL_ROOT . '/' . $module_path . '/oauth2_server.openssl.cnf',
    ];

    // Generate a private key.
    $resource = openssl_pkey_new($config);
    openssl_pkey_export($resource, $private_key);

    // Generate a public key certificate valid for 2 days.
    $serial = \Drupal::state()
      ->get('oauth2_server.next_certificate_id', 0);
    $uri = new Url('<front>', [], [
      'absolute' => TRUE,
      'https' => TRUE,
    ]);
    $dn = [
      'CN' => $uri
        ->toString(),
    ];
    $csr = openssl_csr_new($dn, $resource, $config);
    $x509 = openssl_csr_sign($csr, NULL, $resource, 2, $config, $serial);
    openssl_x509_export($x509, $public_key_certificate);

    // Increment the id for next time. db_next_id() is not used since it can't
    // guarantee sequential numbers.
    \Drupal::state()
      ->set('oauth2_server.next_certificate_id', ++$serial);
    return [
      'private_key' => $private_key,
      'public_key' => $public_key_certificate,
    ];
  }

  /**
   * Initializes and returns an OAuth2 server.
   *
   * @param \Drupal\oauth2_server\ServerInterface|null $server
   *   The server entity to use for supplying settings to the server, and
   *   initializing the scope. NULL only when we expect the validation to
   *   fail due to an incomplete or invalid request.
   * @param \Drupal\oauth2_server\OAuth2StorageInterface $storage
   *   The storage service to use for retrieving data.
   *
   * @return \OAuth2\Server
   *   An instance of OAuth2\Server.
   */
  public static function startServer(ServerInterface $server = NULL, OAuth2StorageInterface $storage) {
    $grant_types = static::getGrantTypes();
    if ($server) {
      $uri = new Url('<front>', [], [
        'absolute' => TRUE,
        'https' => TRUE,
      ]);
      $settings = $server->settings + [
        'issuer' => $uri
          ->toString(),
      ] + $server->settings['advanced_settings'];
      unset($settings['advanced_settings']);

      // The setting 'use_crypto_tokens' was changed to 'use_jwt_access_tokens'
      // in v1.6 of the library. So this provides both.
      $settings['use_jwt_access_tokens'] = !empty($settings['use_crypto_tokens']) ?: FALSE;

      // Initialize the server and add the scope util.
      $oauth2_server = new Server($storage, $settings);
      $scope_util = new ScopeUtility($server);
      $oauth2_server
        ->setScopeUtil($scope_util);

      // Determine the available grant types based on server settings.
      $enabled_grant_types = array_filter($settings['grant_types']);
    }
    else {
      $oauth2_server = new Server($storage);

      // Enable all grant types. One of them will handle the validation failure.
      $enabled_grant_types = array_keys($grant_types);
      $settings = [];
    }

    // Initialize the enabled grant types.
    foreach ($enabled_grant_types as $grant_type_name) {
      if ($grant_type_name == 'urn:ietf:params:oauth:grant-type:jwt-bearer') {
        $audience = new Url('oauth2_server.token', [], [
          'absolute' => TRUE,
        ]);
        $grant_type = new $grant_types[$grant_type_name]['class']($storage, $audience
          ->toString());
      }
      else {
        $grant_type = new $grant_types[$grant_type_name]['class']($storage, $settings);
      }
      $oauth2_server
        ->addGrantType($grant_type);
    }

    // Implicit flow requires its own instance of
    // OAuth2_GrantType_AuthorizationCode.
    if (!empty($settings['allow_implicit'])) {

      // @todo The $settings parameter doesn't seem to be used.
      $grant_type = new AuthorizationCode($storage, $settings);
      $oauth2_server
        ->addGrantType($grant_type, 'implicit');
    }
    return $oauth2_server;
  }

  /**
   * Get the client credentials from authorization header or request body.
   *
   * Used during token requests.
   *
   * @param \OAuth2\RequestInterface $request
   *   An instance of \OAuth2\HttpFoundationBridge\Request.
   *
   * @return array|null
   *   An array with the following keys:
   *   - client_id: The client key.
   *   - client_secret: The client secret.
   *   or NULL if no client credentials were found.
   */
  public static function getClientCredentials(RequestInterface $request) {

    // Get the client credentials from the Authorization header.
    if (!is_null($request
      ->headers('PHP_AUTH_USER'))) {
      return [
        'client_id' => $request
          ->headers('PHP_AUTH_USER'),
        'client_secret' => $request
          ->headers('PHP_AUTH_PW', ''),
      ];
    }

    // Get the client credentials from the request body (POST).
    // Per spec, this method is not recommended and should be limited to clients
    // unable to utilize HTTP authentication.
    if (!is_null($request
      ->request('client_id'))) {
      return [
        'client_id' => $request
          ->request('client_id'),
        'client_secret' => $request
          ->request('client_secret', ''),
      ];
    }

    // This request contains a JWT, extract the client_id from there.
    if (!is_null($request
      ->request('assertion'))) {
      $jwt_util = new Jwt();
      $jwt = $jwt_util
        ->decode($request
        ->request('assertion'), NULL, FALSE);
      if (!empty($jwt['iss'])) {
        return [
          'client_id' => $jwt['iss'],
          // The JWT bearer grant type doesn't use the client_secret.
          'client_secret' => '',
        ];
      }
    }
    return NULL;
  }

  /**
   * Returns whether the current site needs to have keys generated.
   *
   * @return bool
   *   TRUE if at least one server uses JWT Access Tokens or OpenID Connect,
   *   FALSE otherwise.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public static function siteNeedsKeys() {

    /** @var \Drupal\oauth2_server\ServerInterface[] $servers */
    $servers = \Drupal::entityTypeManager()
      ->getStorage('oauth2_server')
      ->loadMultiple();
    foreach ($servers as $server) {
      if (!empty($server->settings['use_crypto_tokens'])) {
        return TRUE;
      }
      if (!empty($server->settings['use_openid_connect'])) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Check access for the passed server and scope.
   *
   * @param string $server_name
   *   The name of the server for which access should be verified.
   * @param string|null $scope
   *   An optional string of space-separated scopes to check.
   *
   * @return \OAuth2\ResponseInterface|array
   *   A valid access token if found, otherwise an \OAuth2\Response object
   *   containing an appropriate response message and status code.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public static function checkAccess($server_name, $scope = NULL) {

    /** @var \Drupal\oauth2_server\ServerInterface $server */
    $server = \Drupal::entityTypeManager()
      ->getStorage('oauth2_server')
      ->load($server_name);
    $storage = \Drupal::service('oauth2_server.storage');
    $oauth2_server = Utility::startServer($server, $storage);
    $response = new BridgeResponse();
    $request = \Drupal::requestStack()
      ->getCurrentRequest();
    $bridgeRequest = BridgeRequest::createFromRequest($request);
    $token = $oauth2_server
      ->getAccessTokenData($bridgeRequest, $response);

    // If there's no token, that means validation failed. Stop here.
    if (!$token) {
      return $response;
    }

    // Make sure that the token we have matches our server.
    if ($token['server'] != $server
      ->id()) {
      $response
        ->setError(401, 'invalid_grant', 'The access token provided is invalid');
      $response
        ->addHttpHeaders([
        'WWW-Authenticate' => sprintf('%s, realm="%s", scope="%s"', 'bearer', 'Service', $scope),
      ]);
      return $response;
    }

    // Check scope, if provided. If token doesn't have a scope, it's null/empty,
    // or it's insufficient, throw an error.
    $scope_util = new ScopeUtility($server);
    if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$scope_util
      ->checkScope($scope, $token["scope"]))) {
      $response
        ->setError(401, 'insufficient_scope', 'The request requires higher privileges than provided by the access token');
      $response
        ->addHttpHeaders([
        'WWW-Authenticate' => sprintf('%s, realm="%s", scope="%s"', 'bearer', 'Service', $scope),
      ]);
      return $response;
    }
    return $token;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Utility::base64urlDecode public static function Decodes base64url encoded data.
Utility::base64urlEncode public static function Encodes a string as base64url.
Utility::checkAccess public static function Check access for the passed server and scope.
Utility::generateKeys public static function Generates a pair of private and public keys using OpenSSL.
Utility::getClientCredentials public static function Get the client credentials from authorization header or request body.
Utility::getGrantTypes public static function Returns an array of supported grant types and related data.
Utility::getKeys public static function Returns the pair of private and public keys used to sign tokens.
Utility::siteNeedsKeys public static function Returns whether the current site needs to have keys generated.
Utility::startServer public static function Initializes and returns an OAuth2 server.