1<?php
2
3namespace IMSGlobal\LTI\OAuth;
4
5/**
6 * Class to represent an %OAuth Request
7 *
8 * @copyright  Andy Smith
9 * @version 2008-08-04
10 * @license https://opensource.org/licenses/MIT The MIT License
11 */
12class OAuthRequest {
13
14    protected $parameters;
15    protected $http_method;
16    protected $http_url;
17    // for debug purposes
18    public $base_string;
19    public static $version = '1.0';
20
21    function __construct($http_method, $http_url, $parameters = null) {
22
23        $parameters = ($parameters) ? $parameters : array();
24        $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
25        $this->parameters = $parameters;
26        $this->http_method = $http_method;
27        $this->http_url = $http_url;
28
29    }
30
31
32    /**
33     * attempt to build up a request from what was passed to the server
34     */
35    public static function from_request($http_method = null, $http_url = null, $parameters = null) {
36
37      $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
38                ? 'http'
39                : 'https';
40      $http_url = ($http_url) ? $http_url : $scheme .
41                                '://' . $_SERVER['SERVER_NAME'] .
42                                ':' .
43                                $_SERVER['SERVER_PORT'] .
44                                $_SERVER['REQUEST_URI'];
45      $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];
46
47      // We weren't handed any parameters, so let's find the ones relevant to
48      // this request.
49      // If you run XML-RPC or similar you should use this to provide your own
50      // parsed parameter-list
51      if (!$parameters) {
52          // Find request headers
53          $request_headers = OAuthUtil::get_headers();
54
55          // Parse the query-string to find GET parameters
56          if (isset($_SERVER['QUERY_STRING'])) {
57              $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
58          } else {
59              $parameters = array();
60          }
61
62          // We have a Authorization-header with OAuth data. Parse the header and add those.
63          if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
64              $header_parameters = OAuthUtil::split_header($request_headers['Authorization']);
65              $parameters = array_merge($parameters, $header_parameters);
66          }
67
68          // If there are parameters in $_POST, these are likely what will be used. Therefore, they should be considered
69          // the final value in the case of any duplicates from sources parsed above.
70          $parameters = array_merge($parameters, $_POST);
71      }
72
73      return new OAuthRequest($http_method, $http_url, $parameters);
74    }
75
76    /**
77     * pretty much a helper function to set up the request
78     */
79    public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters = null) {
80
81        $parameters = ($parameters) ?  $parameters : array();
82        $defaults = array('oauth_version' => OAuthRequest::$version,
83                          'oauth_nonce' => OAuthRequest::generate_nonce(),
84                          'oauth_timestamp' => OAuthRequest::generate_timestamp(),
85                          'oauth_consumer_key' => $consumer->key);
86        if ($token)
87            $defaults['oauth_token'] = $token->key;
88
89        $parameters = array_merge($defaults, $parameters);
90
91        return new OAuthRequest($http_method, $http_url, $parameters);
92
93    }
94
95    public function set_parameter($name, $value, $allow_duplicates = true) {
96
97      if ($allow_duplicates && isset($this->parameters[$name])) {
98          // We have already added parameter(s) with this name, so add to the list
99          if (is_scalar($this->parameters[$name])) {
100              // This is the first duplicate, so transform scalar (string)
101              // into an array so we can add the duplicates
102              $this->parameters[$name] = array($this->parameters[$name]);
103          }
104
105          $this->parameters[$name][] = $value;
106      } else {
107          $this->parameters[$name] = $value;
108      }
109    }
110
111    public function get_parameter($name) {
112        return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
113    }
114
115    public function get_parameters() {
116        return $this->parameters;
117    }
118
119    public function unset_parameter($name) {
120        unset($this->parameters[$name]);
121    }
122
123    /**
124     * The request parameters, sorted and concatenated into a normalized string.
125     * @return string
126     */
127    public function get_signable_parameters() {
128
129        // Grab all parameters
130        $params = $this->parameters;
131
132        // Remove oauth_signature if present
133        // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
134        if (isset($params['oauth_signature'])) {
135            unset($params['oauth_signature']);
136        }
137
138        return OAuthUtil::build_http_query($params);
139
140    }
141
142    /**
143     * Returns the base string of this request
144     *
145     * The base string defined as the method, the url
146     * and the parameters (normalized), each urlencoded
147     * and the concated with &.
148     */
149    public function get_signature_base_string() {
150        $parts = array(
151            $this->get_normalized_http_method(),
152            $this->get_normalized_http_url(),
153            $this->get_signable_parameters()
154        );
155
156        $parts = OAuthUtil::urlencode_rfc3986($parts);
157
158        return implode('&', $parts);
159
160    }
161
162    /**
163     * just uppercases the http method
164     */
165    public function get_normalized_http_method() {
166        return strtoupper($this->http_method);
167    }
168
169    /**
170     * parses the url and rebuilds it to be
171     * scheme://host/path
172     */
173    public function get_normalized_http_url() {
174
175        $parts = parse_url($this->http_url);
176
177        $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
178        $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
179        $host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
180        $path = (isset($parts['path'])) ? $parts['path'] : '';
181
182        if (($scheme == 'https' && $port != '443')
183            || ($scheme == 'http' && $port != '80')) {
184            $host = "$host:$port";
185        }
186
187        return "$scheme://$host$path";
188
189    }
190
191    /**
192     * builds a url usable for a GET request
193     */
194    public function to_url() {
195
196        $post_data = $this->to_postdata();
197        $out = $this->get_normalized_http_url();
198        if ($post_data) {
199            $out .= '?'.$post_data;
200        }
201
202        return $out;
203
204    }
205
206    /**
207     * builds the data one would send in a POST request
208     */
209    public function to_postdata() {
210        return OAuthUtil::build_http_query($this->parameters);
211    }
212
213    /**
214     * builds the Authorization: header
215     */
216    public function to_header($realm = null) {
217
218        $first = true;
219        if($realm) {
220            $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
221            $first = false;
222        } else
223            $out = 'Authorization: OAuth';
224
225        $total = array();
226        foreach ($this->parameters as $k => $v) {
227            if (substr($k, 0, 5) != "oauth") continue;
228            if (is_array($v)) {
229              throw new OAuthException('Arrays not supported in headers');
230            }
231            $out .= ($first) ? ' ' : ',';
232            $out .= OAuthUtil::urlencode_rfc3986($k) .
233                    '="' .
234                    OAuthUtil::urlencode_rfc3986($v) .
235                    '"';
236            $first = false;
237        }
238
239        return $out;
240
241    }
242
243    public function __toString() {
244        return $this->to_url();
245    }
246
247
248    public function sign_request($signature_method, $consumer, $token) {
249
250        $this->set_parameter(
251          "oauth_signature_method",
252          $signature_method->get_name(),
253          false
254        );
255        $signature = $this->build_signature($signature_method, $consumer, $token);
256        $this->set_parameter("oauth_signature", $signature, false);
257
258    }
259
260    public function build_signature($signature_method, $consumer, $token) {
261        $signature = $signature_method->build_signature($this, $consumer, $token);
262        return $signature;
263    }
264
265    /**
266     * util function: current timestamp
267     */
268    private static function generate_timestamp() {
269        return time();
270    }
271
272    /**
273     * util function: current nonce
274     */
275    private static function generate_nonce() {
276        $mt = microtime();
277        $rand = mt_rand();
278
279        return md5($mt . $rand); // md5s look nicer than numbers
280    }
281
282}
283