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