3 * @copyright Copyright (C) 2010-2022, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Security\OAuth1;
24 use Friendica\Util\Strings;
33 public static $version = '1.0';
34 public static $POST_INPUT = 'php://input';
36 function __construct($http_method, $http_url, $parameters = null)
38 @$parameters or $parameters = [];
39 $parameters = array_merge(OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
40 $this->parameters = $parameters;
41 $this->http_method = $http_method;
42 $this->http_url = $http_url;
47 * attempt to build up a request from what was passed to the server
49 * @param string|null $http_method
50 * @param string|null $http_url
51 * @param string|null $parameters
53 * @return OAuthRequest
55 public static function from_request($http_method = null, $http_url = null, $parameters = null)
57 $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
60 @$http_url or $http_url = $scheme .
61 '://' . $_SERVER['HTTP_HOST'] .
63 $_SERVER['SERVER_PORT'] .
64 $_SERVER['REQUEST_URI'];
65 @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
67 // We weren't handed any parameters, so let's find the ones relevant to
69 // If you run XML-RPC or similar you should use this to provide your own
70 // parsed parameter-list
72 // Find request headers
73 $request_headers = OAuthUtil::get_headers();
75 // Parse the query-string to find GET parameters
76 $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
78 // It's a POST request of the proper content-type, so parse POST
79 // parameters and add those overriding any duplicates from GET
81 $http_method == "POST"
83 $request_headers["Content-Type"],
84 "application/x-www-form-urlencoded"
87 $post_data = OAuthUtil::parse_parameters(
88 file_get_contents(self::$POST_INPUT)
90 $parameters = array_merge($parameters, $post_data);
93 // We have a Authorization-header with OAuth data. Parse the header
94 // and add those overriding any duplicates from GET or POST
95 if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
96 $header_parameters = OAuthUtil::split_header(
97 $request_headers['Authorization']
99 $parameters = array_merge($parameters, $header_parameters);
102 // fix for friendica redirect system
104 $http_url = substr($http_url, 0, strpos($http_url, $parameters['pagename']) + strlen($parameters['pagename']));
105 unset($parameters['pagename']);
107 return new OAuthRequest($http_method, $http_url, $parameters);
111 * pretty much a helper function to set up the request
113 * @param OAuthConsumer $consumer
114 * @param OAuthToken $token
115 * @param string $http_method
116 * @param string $http_url
117 * @param array|null $parameters
119 * @return OAuthRequest
121 public static function from_consumer_and_token(OAuthConsumer $consumer, $http_method, $http_url, array $parameters = null, OAuthToken $token = null)
123 @$parameters or $parameters = [];
125 "oauth_version" => OAuthRequest::$version,
126 "oauth_nonce" => OAuthRequest::generate_nonce(),
127 "oauth_timestamp" => OAuthRequest::generate_timestamp(),
128 "oauth_consumer_key" => $consumer->key,
131 $defaults['oauth_token'] = $token->key;
133 $parameters = array_merge($defaults, $parameters);
135 return new OAuthRequest($http_method, $http_url, $parameters);
138 public function set_parameter($name, $value, $allow_duplicates = true)
140 if ($allow_duplicates && isset($this->parameters[$name])) {
141 // We have already added parameter(s) with this name, so add to the list
142 if (is_scalar($this->parameters[$name])) {
143 // This is the first duplicate, so transform scalar (string)
144 // into an array so we can add the duplicates
145 $this->parameters[$name] = [$this->parameters[$name]];
148 $this->parameters[$name][] = $value;
150 $this->parameters[$name] = $value;
154 public function get_parameter($name)
156 return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
159 public function get_parameters()
161 return $this->parameters;
164 public function unset_parameter($name)
166 unset($this->parameters[$name]);
170 * The request parameters, sorted and concatenated into a normalized string.
174 public function get_signable_parameters()
176 // Grab all parameters
177 $params = $this->parameters;
179 // Remove oauth_signature if present
180 // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
181 if (isset($params['oauth_signature'])) {
182 unset($params['oauth_signature']);
185 return OAuthUtil::build_http_query($params);
189 * Returns the base string of this request
191 * The base string defined as the method, the url
192 * and the parameters (normalized), each urlencoded
193 * and the concated with &.
195 public function get_signature_base_string()
198 $this->get_normalized_http_method(),
199 $this->get_normalized_http_url(),
200 $this->get_signable_parameters(),
203 $parts = OAuthUtil::urlencode_rfc3986($parts);
205 return implode('&', $parts);
209 * just uppercases the http method
211 public function get_normalized_http_method()
213 return strtoupper($this->http_method);
217 * parses the url and rebuilds it to be
220 public function get_normalized_http_url()
222 $parts = parse_url($this->http_url);
224 $port = @$parts['port'];
225 $scheme = $parts['scheme'];
226 $host = $parts['host'];
227 $path = @$parts['path'];
229 $port or $port = ($scheme == 'https') ? '443' : '80';
231 if (($scheme == 'https' && $port != '443')
232 || ($scheme == 'http' && $port != '80')
234 $host = "$host:$port";
236 return "$scheme://$host$path";
240 * builds a url usable for a GET request
242 public function to_url()
244 $post_data = $this->to_postdata();
245 $out = $this->get_normalized_http_url();
247 $out .= '?' . $post_data;
253 * builds the data one would send in a POST request
257 * @return array|string
259 public function to_postdata(bool $raw = false)
262 return $this->parameters;
264 return OAuthUtil::build_http_query($this->parameters);
268 * builds the Authorization: header
270 * @param string|null $realm
273 * @throws OAuthException
275 public function to_header($realm = null)
279 $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
282 $out = 'Authorization: OAuth';
284 foreach ($this->parameters as $k => $v) {
285 if (substr($k, 0, 5) != "oauth") continue;
287 throw new OAuthException('Arrays not supported in headers');
289 $out .= ($first) ? ' ' : ',';
290 $out .= OAuthUtil::urlencode_rfc3986($k) .
292 OAuthUtil::urlencode_rfc3986($v) .
299 public function __toString()
301 return $this->to_url();
305 public function sign_request(Signature\OAuthSignatureMethod $signature_method, $consumer, $token)
307 $this->set_parameter(
308 "oauth_signature_method",
309 $signature_method->get_name(),
312 $signature = $this->build_signature($signature_method, $consumer, $token);
313 $this->set_parameter("oauth_signature", $signature, false);
316 public function build_signature(Signature\OAuthSignatureMethod $signature_method, $consumer, $token)
318 $signature = $signature_method->build_signature($this, $consumer, $token);
323 * util function: current timestamp
325 private static function generate_timestamp()
331 * util function: current nonce
333 private static function generate_nonce()
335 return Strings::getRandomHex(32);