]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/oauthstore.php
Merge branch '0.9.x' into openidplugin
[quix0rs-gnu-social.git] / lib / oauthstore.php
1 <?php
2 /*
3  * Laconica - a distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, Control Yourself, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 if (!defined('LACONICA')) {
21     exit(1);
22 }
23
24 require_once 'libomb/datastore.php';
25
26 class LaconicaDataStore extends OMB_Datastore
27 {
28
29     // We keep a record of who's contacted us
30     function lookup_consumer($consumer_key)
31     {
32         $con = Consumer::staticGet('consumer_key', $consumer_key);
33         if (!$con) {
34             $con = new Consumer();
35             $con->consumer_key = $consumer_key;
36             $con->seed = common_good_rand(16);
37             $con->created = DB_DataObject_Cast::dateTime();
38             if (!$con->insert()) {
39                 return null;
40             }
41         }
42         return new OAuthConsumer($con->consumer_key, '');
43     }
44
45     function lookup_token($consumer, $token_type, $token_key)
46     {
47         $t = new Token();
48         if (!is_null($consumer)) {
49             $t->consumer_key = $consumer->key;
50         }
51         $t->tok = $token_key;
52         $t->type = ($token_type == 'access') ? 1 : 0;
53         if ($t->find(true)) {
54             return new OAuthToken($t->tok, $t->secret);
55         } else {
56             return null;
57         }
58     }
59
60     // http://oauth.net/core/1.0/#nonce
61     // "The Consumer SHALL then generate a Nonce value that is unique for
62     // all requests with that timestamp."
63
64     // XXX: It's not clear why the token is here
65
66     function lookup_nonce($consumer, $token, $nonce, $timestamp)
67     {
68         $n = new Nonce();
69         $n->consumer_key = $consumer->key;
70         $n->ts = $timestamp;
71         $n->nonce = $nonce;
72         if ($n->find(true)) {
73             return true;
74         } else {
75             $n->created = DB_DataObject_Cast::dateTime();
76             $n->insert();
77             return false;
78         }
79     }
80
81     function new_request_token($consumer)
82     {
83         $t = new Token();
84         $t->consumer_key = $consumer->key;
85         $t->tok = common_good_rand(16);
86         $t->secret = common_good_rand(16);
87         $t->type = 0; // request
88         $t->state = 0; // unauthorized
89         $t->created = DB_DataObject_Cast::dateTime();
90         if (!$t->insert()) {
91             return null;
92         } else {
93             return new OAuthToken($t->tok, $t->secret);
94         }
95     }
96
97     // defined in OAuthDataStore, but not implemented anywhere
98
99     function fetch_request_token($consumer)
100     {
101         return $this->new_request_token($consumer);
102     }
103
104     function new_access_token($token, $consumer)
105     {
106         common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__);
107         $rt = new Token();
108         $rt->consumer_key = $consumer->key;
109         $rt->tok = $token->key;
110         $rt->type = 0; // request
111         if ($rt->find(true) && $rt->state == 1) { // authorized
112             common_debug('request token found.', __FILE__);
113             $at = new Token();
114             $at->consumer_key = $consumer->key;
115             $at->tok = common_good_rand(16);
116             $at->secret = common_good_rand(16);
117             $at->type = 1; // access
118             $at->created = DB_DataObject_Cast::dateTime();
119             if (!$at->insert()) {
120                 $e = $at->_lastError;
121                 common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__);
122                 return null;
123             } else {
124                 common_debug('access token "'.$at->tok.'" inserted', __FILE__);
125                 // burn the old one
126                 $orig_rt = clone($rt);
127                 $rt->state = 2; // used
128                 if (!$rt->update($orig_rt)) {
129                     return null;
130                 }
131                 common_debug('request token "'.$rt->tok.'" updated', __FILE__);
132                 // Update subscription
133                 // XXX: mixing levels here
134                 $sub = Subscription::staticGet('token', $rt->tok);
135                 if (!$sub) {
136                     return null;
137                 }
138                 common_debug('subscription for request token found', __FILE__);
139                 $orig_sub = clone($sub);
140                 $sub->token = $at->tok;
141                 $sub->secret = $at->secret;
142                 if (!$sub->update($orig_sub)) {
143                     return null;
144                 } else {
145                     common_debug('subscription updated to use access token', __FILE__);
146                     return new OAuthToken($at->tok, $at->secret);
147                 }
148             }
149         } else {
150             return null;
151         }
152     }
153
154     // defined in OAuthDataStore, but not implemented anywhere
155
156     function fetch_access_token($consumer)
157     {
158         return $this->new_access_token($consumer);
159     }
160
161
162     /**
163      * Revoke specified OAuth token
164      *
165      * Revokes the authorization token specified by $token_key.
166      * Throws exceptions in case of error.
167      *
168      * @param string $token_key The token to be revoked
169      *
170      * @access public
171      **/
172     public function revoke_token($token_key) {
173         $rt = new Token();
174         $rt->tok = $token_key;
175         $rt->type = 0;
176         $rt->state = 0;
177         if (!$rt->find(true)) {
178             throw new Exception('Tried to revoke unknown token');
179         }
180         if (!$rt->delete()) {
181             throw new Exception('Failed to delete revoked token');
182         }
183     }
184
185     /**
186      * Authorize specified OAuth token
187      *
188      * Authorizes the authorization token specified by $token_key.
189      * Throws exceptions in case of error.
190      *
191      * @param string $token_key The token to be authorized
192      *
193      * @access public
194      **/
195     public function authorize_token($token_key) {
196         $rt = new Token();
197         $rt->tok = $token_key;
198         $rt->type = 0;
199         $rt->state = 0;
200         if (!$rt->find(true)) {
201             throw new Exception('Tried to authorize unknown token');
202         }
203         $orig_rt = clone($rt);
204         $rt->state = 1; # Authorized but not used
205         if (!$rt->update($orig_rt)) {
206             throw new Exception('Failed to authorize token');
207         }
208     }
209
210     /**
211      * Get profile by identifying URI
212      *
213      * Returns an OMB_Profile object representing the OMB profile identified by
214      * $identifier_uri.
215      * Returns null if there is no such OMB profile.
216      * Throws exceptions in case of other error.
217      *
218      * @param string $identifier_uri The OMB identifier URI specifying the
219      *                               requested profile
220      *
221      * @access public
222      *
223      * @return OMB_Profile The corresponding profile
224      **/
225     public function getProfile($identifier_uri) {
226         /* getProfile is only used for remote profiles by libomb.
227            TODO: Make it work with local ones anyway. */
228         $remote = Remote_profile::staticGet('uri', $identifier_uri);
229         if (!$remote) throw new Exception('No such remote profile');
230         $profile = Profile::staticGet('id', $remote->id);
231         if (!$profile) throw new Exception('No profile for remote user');
232
233         require_once INSTALLDIR.'/lib/omb.php';
234         return profile_to_omb_profile($identifier_uri, $profile);
235     }
236
237     /**
238      * Save passed profile
239      *
240      * Stores the OMB profile $profile. Overwrites an existing entry.
241      * Throws exceptions in case of error.
242      *
243      * @param OMB_Profile $profile   The OMB profile which should be saved
244      *
245      * @access public
246      **/
247     public function saveProfile($omb_profile) {
248         if (common_profile_url($omb_profile->getNickname()) ==
249                                                 $omb_profile->getProfileURL()) {
250             throw new Exception('Not implemented');
251         } else {
252             $remote = Remote_profile::staticGet('uri', $omb_profile->getIdentifierURI());
253
254             if ($remote) {
255                 $exists = true;
256                 $profile = Profile::staticGet($remote->id);
257                 $orig_remote = clone($remote);
258                 $orig_profile = clone($profile);
259                 # XXX: compare current postNotice and updateProfile URLs to the ones
260                 # stored in the DB to avoid (possibly...) above attack
261             } else {
262                 $exists = false;
263                 $remote = new Remote_profile();
264                 $remote->uri = $omb_profile->getIdentifierURI();
265                 $profile = new Profile();
266             }
267
268             $profile->nickname = $omb_profile->getNickname();
269             $profile->profileurl = $omb_profile->getProfileURL();
270
271             $fullname = $omb_profile->getFullname();
272             $profile->fullname = is_null($fullname) ? '' : $fullname;
273             $homepage = $omb_profile->getHomepage();
274             $profile->homepage = is_null($homepage) ? '' : $homepage;
275             $bio = $omb_profile->getBio();
276             $profile->bio = is_null($bio) ? '' : $bio;
277             $location = $omb_profile->getLocation();
278             $profile->location = is_null($location) ? '' : $location;
279
280             if ($exists) {
281                 $profile->update($orig_profile);
282             } else {
283                 $profile->created = DB_DataObject_Cast::dateTime(); # current time
284                 $id = $profile->insert();
285                 if (!$id) {
286                     throw new Exception(_('Error inserting new profile'));
287                 }
288                 $remote->id = $id;
289             }
290
291             $avatar_url = $omb_profile->getAvatarURL();
292             if ($avatar_url) {
293                 if (!$this->add_avatar($profile, $avatar_url)) {
294                     throw new Exception(_('Error inserting avatar'));
295                 }
296             } else {
297                 $avatar = $profile->getOriginalAvatar();
298                 if($avatar) $avatar->delete();
299                 $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
300                 if($avatar) $avatar->delete();
301                 $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
302                 if($avatar) $avatar->delete();
303                 $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
304                 if($avatar) $avatar->delete();
305             }
306
307             if ($exists) {
308                 if (!$remote->update($orig_remote)) {
309                     throw new Exception(_('Error updating remote profile'));
310                 }
311             } else {
312                 $remote->created = DB_DataObject_Cast::dateTime(); # current time
313                 if (!$remote->insert()) {
314                     throw new Exception(_('Error inserting remote profile'));
315                 }
316             }
317         }
318     }
319
320     function add_avatar($profile, $url)
321     {
322         $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
323         copy($url, $temp_filename);
324         $imagefile = new ImageFile($profile->id, $temp_filename);
325         $filename = Avatar::filename($profile->id,
326                                      image_type_to_extension($imagefile->type),
327                                      null,
328                                      common_timestamp());
329         rename($temp_filename, Avatar::path($filename));
330         return $profile->setOriginal($filename);
331     }
332
333     /**
334      * Save passed notice
335      *
336      * Stores the OMB notice $notice. The datastore may change the passed notice.
337      * This might by neccessary for URIs depending on a database key. Note that
338      * it is the user’s duty to present a mechanism for his OMB_Datastore to
339      * appropriately change his OMB_Notice.
340      * Throws exceptions in case of error.
341      *
342      * @param OMB_Notice $notice The OMB notice which should be saved
343      *
344      * @access public
345      **/
346     public function saveNotice(&$omb_notice) {
347         if (Notice::staticGet('uri', $omb_notice->getIdentifierURI())) {
348             throw new Exception(_('Duplicate notice'));
349         }
350         $author_uri = $omb_notice->getAuthor()->getIdentifierURI();
351         common_log(LOG_DEBUG, $author_uri, __FILE__);
352         $author = Remote_profile::staticGet('uri', $author_uri);
353         if (!$author) {
354             $author = User::staticGet('uri', $author_uri);
355         }
356         if (!$author) {
357             throw new Exception('No such user');
358         }
359
360         common_log(LOG_DEBUG, print_r($author, true), __FILE__);
361
362         $notice = Notice::saveNew($author->id,
363                                   $omb_notice->getContent(),
364                                   'omb',
365                                   false,
366                                   null,
367                                   $omb_notice->getIdentifierURI());
368         if (is_string($notice)) {
369             throw new Exception($notice);
370         }
371         common_broadcast_notice($notice, true);
372     }
373
374     /**
375      * Get subscriptions of a given profile
376      *
377      * Returns an array containing subscription informations for the specified
378      * profile. Every array entry should in turn be an array with keys
379      *   'uri´: The identifier URI of the subscriber
380      *   'token´: The subscribe token
381      *   'secret´: The secret token
382      * Throws exceptions in case of error.
383      *
384      * @param string $subscribed_user_uri The OMB identifier URI specifying the
385      *                                    subscribed profile
386      *
387      * @access public
388      *
389      * @return mixed An array containing the subscriptions or 0 if no
390      *               subscription has been found.
391      **/
392     public function getSubscriptions($subscribed_user_uri) {
393         $sub = new Subscription();
394
395         $user = $this->_getAnyProfile($subscribed_user_uri);
396
397         $sub->subscribed = $user->id;
398
399         if (!$sub->find(true)) {
400             return 0;
401         }
402
403         /* Since we do not use OMB_Service_Provider’s action methods, there
404            is no need to actually return the subscriptions. */
405         return 1;
406     }
407
408     private function _getAnyProfile($uri)
409     {
410         $user = Remote_profile::staticGet('uri', $uri);
411         if (!$user) {
412             $user = User::staticGet('uri', $uri);
413         }
414         if (!$user) {
415             throw new Exception('No such user');
416         }
417         return $user;
418     }
419
420     /**
421      * Delete a subscription
422      *
423      * Deletes the subscription from $subscriber_uri to $subscribed_user_uri.
424      * Throws exceptions in case of error.
425      *
426      * @param string $subscriber_uri      The OMB identifier URI specifying the
427      *                                    subscribing profile
428      *
429      * @param string $subscribed_user_uri The OMB identifier URI specifying the
430      *                                    subscribed profile
431      *
432      * @access public
433      **/
434     public function deleteSubscription($subscriber_uri, $subscribed_user_uri)
435     {
436         $sub = new Subscription();
437
438         $subscribed = $this->_getAnyProfile($subscribed_user_uri);
439         $subscriber = $this->_getAnyProfile($subscriber_uri);
440
441         $sub->subscribed = $subscribed->id;
442         $sub->subscriber = $subscriber->id;
443
444         $sub->delete();
445     }
446
447     /**
448      * Save a subscription
449      *
450      * Saves the subscription from $subscriber_uri to $subscribed_user_uri.
451      * Throws exceptions in case of error.
452      *
453      * @param string     $subscriber_uri      The OMB identifier URI specifying
454      *                                        the subscribing profile
455      *
456      * @param string     $subscribed_user_uri The OMB identifier URI specifying
457      *                                        the subscribed profile
458      * @param OAuthToken $token               The access token
459      *
460      * @access public
461      **/
462     public function saveSubscription($subscriber_uri, $subscribed_user_uri,
463                                                                        $token)
464     {
465         $sub = new Subscription();
466
467         $subscribed = $this->_getAnyProfile($subscribed_user_uri);
468         $subscriber = $this->_getAnyProfile($subscriber_uri);
469
470         $sub->subscribed = $subscribed->id;
471         $sub->subscriber = $subscriber->id;
472
473         $sub_exists = $sub->find(true);
474
475         if ($sub_exists) {
476             $orig_sub = clone($sub);
477         } else {
478             $sub->created = DB_DataObject_Cast::dateTime();
479         }
480
481         $sub->token  = $token->key;
482         $sub->secret = $token->secret;
483
484         if ($sub_exists) {
485             $result = $sub->update($orig_sub);
486         } else {
487             $result = $sub->insert();
488         }
489
490         if (!$result) {
491             common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__);
492             throw new Exception(_('Couldn\'t insert new subscription.'));
493             return;
494         }
495
496         /* Notify user, if necessary. */
497
498         if ($subscribed instanceof User) {
499             mail_subscribe_notify_profile($subscribed,
500                                           Profile::staticGet($subscriber->id));
501         }
502     }
503 }
504 ?>