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