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