Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   1  <?php
   2  
   3  namespace IMSGlobal\LTI\OAuth;
   4  
   5  /**
   6   * Class to represent an %OAuth Server
   7   *
   8   * @copyright  Andy Smith
   9   * @version 2008-08-04
  10   * @license https://opensource.org/licenses/MIT The MIT License
  11   */
  12  #[\AllowDynamicProperties]
  13  class OAuthServer {
  14  
  15      protected $timestamp_threshold = 300; // in seconds, five minutes
  16      protected $version = '1.0';             // hi blaine
  17      protected $signature_methods = array();
  18  
  19      protected $data_store;
  20  
  21      function __construct($data_store) {
  22          $this->data_store = $data_store;
  23      }
  24  
  25      public function add_signature_method($signature_method) {
  26          $this->signature_methods[$signature_method->get_name()] = $signature_method;
  27      }
  28  
  29      // high level functions
  30  
  31      /**
  32       * process a request_token request
  33       * returns the request token on success
  34       */
  35      public function fetch_request_token(&$request) {
  36  
  37          $this->get_version($request);
  38  
  39          $consumer = $this->get_consumer($request);
  40  
  41          // no token required for the initial token request
  42          $token = NULL;
  43  
  44          $this->check_signature($request, $consumer, $token);
  45  
  46          // Rev A change
  47          $callback = $request->get_parameter('oauth_callback');
  48          $new_token = $this->data_store->new_request_token($consumer, $callback);
  49  
  50          return $new_token;
  51  
  52      }
  53  
  54      /**
  55       * process an access_token request
  56       * returns the access token on success
  57       */
  58      public function fetch_access_token(&$request) {
  59  
  60          $this->get_version($request);
  61  
  62          $consumer = $this->get_consumer($request);
  63  
  64          // requires authorized request token
  65          $token = $this->get_token($request, $consumer, "request");
  66  
  67          $this->check_signature($request, $consumer, $token);
  68  
  69          // Rev A change
  70          $verifier = $request->get_parameter('oauth_verifier');
  71          $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
  72  
  73          return $new_token;
  74  
  75      }
  76  
  77      /**
  78       * verify an api call, checks all the parameters
  79       */
  80      public function verify_request(&$request) {
  81  
  82          $this->get_version($request);
  83          $consumer = $this->get_consumer($request);
  84          $token = $this->get_token($request, $consumer, "access");
  85          $this->check_signature($request, $consumer, $token);
  86  
  87          return array($consumer, $token);
  88  
  89      }
  90  
  91      // Internals from here
  92      /**
  93       * version 1
  94       */
  95      private function get_version(&$request) {
  96  
  97          $version = $request->get_parameter("oauth_version");
  98          if (!$version) {
  99              // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
 100              // Chapter 7.0 ("Accessing Protected Ressources")
 101              $version = '1.0';
 102          }
 103          if ($version !== $this->version) {
 104              throw new OAuthException("OAuth version '$version' not supported");
 105          }
 106  
 107          return $version;
 108  
 109      }
 110  
 111      /**
 112       * figure out the signature with some defaults
 113       */
 114      private function get_signature_method($request) {
 115  
 116          $signature_method = $request instanceof OAuthRequest
 117              ? $request->get_parameter('oauth_signature_method') : NULL;
 118  
 119          if (!$signature_method) {
 120              // According to chapter 7 ("Accessing Protected Ressources") the signature-method
 121              // parameter is required, and we can't just fallback to PLAINTEXT
 122              throw new OAuthException('No signature method parameter. This parameter is required');
 123          }
 124  
 125          if (!in_array($signature_method,
 126                        array_keys($this->signature_methods))) {
 127              throw new OAuthException(
 128                "Signature method '$signature_method' not supported " .
 129                'try one of the following: ' .
 130                implode(', ', array_keys($this->signature_methods))
 131              );
 132          }
 133  
 134          return $this->signature_methods[$signature_method];
 135  
 136      }
 137  
 138      /**
 139       * try to find the consumer for the provided request's consumer key
 140       */
 141      private function get_consumer($request) {
 142  
 143          $consumer_key = $request instanceof OAuthRequest
 144              ? $request->get_parameter('oauth_consumer_key') : NULL;
 145  
 146          if (!$consumer_key) {
 147              throw new OAuthException('Invalid consumer key');
 148          }
 149  
 150          $consumer = $this->data_store->lookup_consumer($consumer_key);
 151          if (!$consumer) {
 152              throw new OAuthException('Invalid consumer');
 153          }
 154  
 155          return $consumer;
 156  
 157      }
 158  
 159      /**
 160       * try to find the token for the provided request's token key
 161       */
 162      private function get_token($request, $consumer, $token_type="access") {
 163  
 164          $token_field = $request instanceof OAuthRequest
 165               ? $request->get_parameter('oauth_token') : NULL;
 166  
 167          $token = $this->data_store->lookup_token($consumer, $token_type, $token_field);
 168          if (!$token) {
 169              throw new OAuthException("Invalid $token_type token: $token_field");
 170          }
 171  
 172          return $token;
 173  
 174      }
 175  
 176      /**
 177       * all-in-one function to check the signature on a request
 178       * should guess the signature method appropriately
 179       */
 180      private function check_signature($request, $consumer, $token) {
 181  
 182          // this should probably be in a different method
 183          $timestamp = $request instanceof OAuthRequest
 184              ? $request->get_parameter('oauth_timestamp')
 185              : NULL;
 186          $nonce = $request instanceof OAuthRequest
 187              ? $request->get_parameter('oauth_nonce')
 188              : NULL;
 189  
 190          $this->check_timestamp($timestamp);
 191          $this->check_nonce($consumer, $token, $nonce, $timestamp);
 192  
 193          $signature_method = $this->get_signature_method($request);
 194  
 195          $signature = $request->get_parameter('oauth_signature');
 196          $valid_sig = $signature_method->check_signature($request, $consumer, $token, $signature);
 197  
 198          if (!$valid_sig) {
 199              throw new OAuthException('Invalid signature');
 200          }
 201      }
 202  
 203      /**
 204       * check that the timestamp is new enough
 205       */
 206      private function check_timestamp($timestamp) {
 207          if(!$timestamp)
 208              throw new OAuthException('Missing timestamp parameter. The parameter is required');
 209  
 210          // verify that timestamp is recentish
 211          $now = time();
 212          if (abs($now - $timestamp) > $this->timestamp_threshold) {
 213              throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
 214          }
 215  
 216      }
 217  
 218      /**
 219       * check that the nonce is not repeated
 220       */
 221      private function check_nonce($consumer, $token, $nonce, $timestamp) {
 222  
 223          if(!$nonce)
 224            throw new OAuthException('Missing nonce parameter. The parameter is required');
 225  
 226          // verify that the nonce is uniqueish
 227          $found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp);
 228          if ($found) {
 229              throw new OAuthException("Nonce already used: $nonce");
 230          }
 231  
 232      }
 233  
 234  }