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