Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 401 and 402] [Versions 401 and 403]

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