]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/OMB/lib/omboauthdatastore.php
Avatar resizing improvements and better code reuse
[quix0rs-gnu-social.git] / plugins / OMB / lib / omboauthdatastore.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::getKV('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::getKV('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::getKV('uri', $identifier_uri);
222         if (!$remote) throw new Exception('No such remote profile');
223         $profile = Profile::getKV('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::getKV('uri', $omb_profile->getIdentifierURI());
246
247             if ($remote) {
248                 $exists = true;
249                 $profile = Profile::getKV($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::deleteFromProfile($profile);
293             }
294
295             if ($exists) {
296                 if (!$remote->update($orig_remote)) {
297                     // TRANS: Exception thrown when updating a remote profile fails in OAuth store.
298                     throw new Exception(_('Error updating remote profile.'));
299                 }
300             } else {
301                 $remote->created = DB_DataObject_Cast::dateTime(); # current time
302                 if (!$remote->insert()) {
303                     // TRANS: Exception thrown when creating a remote profile fails in OAuth store.
304                     throw new Exception(_('Error inserting remote profile.'));
305                 }
306             }
307         }
308     }
309
310     function add_avatar($profile, $url)
311     {
312         $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
313         try {
314             copy($url, $temp_filename);
315             $imagefile = new ImageFile($profile->id, $temp_filename);
316             $filename = Avatar::filename($profile->id,
317                                          image_type_to_extension($imagefile->type),
318                                          null,
319                                          common_timestamp());
320             rename($temp_filename, Avatar::path($filename));
321         } catch (Exception $e) {
322             unlink($temp_filename);
323             throw $e;
324         }
325         return $profile->setOriginal($filename);
326     }
327
328     /**
329      * Save passed notice
330      *
331      * Stores the OMB notice $notice. The datastore may change the passed notice.
332      * This might by neccessary for URIs depending on a database key. Note that
333      * it is the user’s duty to present a mechanism for his OMB_Datastore to
334      * appropriately change his OMB_Notice.
335      * Throws exceptions in case of error.
336      *
337      * @param OMB_Notice $notice The OMB notice which should be saved
338      *
339      * @access public
340      **/
341     public function saveNotice(&$omb_notice) {
342         if (Notice::getKV('uri', $omb_notice->getIdentifierURI())) {
343             // TRANS: Exception thrown when a notice is denied because it has been sent before.
344             throw new Exception(_('Duplicate notice.'));
345         }
346         $author_uri = $omb_notice->getAuthor()->getIdentifierURI();
347         common_log(LOG_DEBUG, $author_uri, __FILE__);
348         $author = Remote_profile::getKV('uri', $author_uri);
349         if (!$author) {
350             $author = User::getKV('uri', $author_uri);
351         }
352         if (!$author) {
353             throw new Exception('No such user.');
354         }
355
356         common_log(LOG_DEBUG, print_r($author, true), __FILE__);
357
358         $notice = Notice::saveNew($author->id,
359                                   $omb_notice->getContent(),
360                                   'omb',
361                                   array('is_local' => Notice::REMOTE,
362                                         'uri' => $omb_notice->getIdentifierURI()));
363
364     }
365
366     /**
367      * Get subscriptions of a given profile
368      *
369      * Returns an array containing subscription informations for the specified
370      * profile. Every array entry should in turn be an array with keys
371      *   'uri´: The identifier URI of the subscriber
372      *   'token´: The subscribe token
373      *   'secret´: The secret token
374      * Throws exceptions in case of error.
375      *
376      * @param string $subscribed_user_uri The OMB identifier URI specifying the
377      *                                    subscribed profile
378      *
379      * @access public
380      *
381      * @return mixed An array containing the subscriptions or 0 if no
382      *               subscription has been found.
383      **/
384     public function getSubscriptions($subscribed_user_uri) {
385         $sub = new Subscription();
386
387         $user = $this->_getAnyProfile($subscribed_user_uri);
388
389         $sub->subscribed = $user->id;
390
391         if (!$sub->find(true)) {
392             return array();
393         }
394
395         /* Since we do not use OMB_Service_Provider’s action methods, there
396            is no need to actually return the subscriptions. */
397         return 1;
398     }
399
400     private function _getAnyProfile($uri)
401     {
402         $user = Remote_profile::getKV('uri', $uri);
403         if (!$user) {
404             $user = User::getKV('uri', $uri);
405         }
406         if (!$user) {
407             throw new Exception('No such user.');
408         }
409         return $user;
410     }
411
412     /**
413      * Delete a subscription
414      *
415      * Deletes the subscription from $subscriber_uri to $subscribed_user_uri.
416      * Throws exceptions in case of error.
417      *
418      * @param string $subscriber_uri      The OMB identifier URI specifying the
419      *                                    subscribing profile
420      *
421      * @param string $subscribed_user_uri The OMB identifier URI specifying the
422      *                                    subscribed profile
423      *
424      * @access public
425      **/
426     public function deleteSubscription($subscriber_uri, $subscribed_user_uri)
427     {
428         $sub = new Subscription();
429
430         $subscribed = $this->_getAnyProfile($subscribed_user_uri);
431         $subscriber = $this->_getAnyProfile($subscriber_uri);
432
433         $sub->subscribed = $subscribed->id;
434         $sub->subscriber = $subscriber->id;
435
436         $sub->delete();
437     }
438
439     /**
440      * Save a subscription
441      *
442      * Saves the subscription from $subscriber_uri to $subscribed_user_uri.
443      * Throws exceptions in case of error.
444      *
445      * @param string     $subscriber_uri      The OMB identifier URI specifying
446      *                                        the subscribing profile
447      *
448      * @param string     $subscribed_user_uri The OMB identifier URI specifying
449      *                                        the subscribed profile
450      * @param OAuthToken $token               The access token
451      *
452      * @access public
453      **/
454     public function saveSubscription($subscriber_uri, $subscribed_user_uri,
455                                                                        $token)
456     {
457         $sub = new Subscription();
458
459         $subscribed = $this->_getAnyProfile($subscribed_user_uri);
460         $subscriber = $this->_getAnyProfile($subscriber_uri);
461
462         if (!$subscriber->hasRight(Right::SUBSCRIBE)) {
463             common_log(LOG_INFO, __METHOD__ . ": remote subscriber banned ($subscriber_uri subbing to $subscribed_user_uri)");
464             // TRANS: Error message displayed to a banned user when they try to subscribe.
465             return _('You have been banned from subscribing.');
466         }
467
468         $sub->subscribed = $subscribed->id;
469         $sub->subscriber = $subscriber->id;
470
471         $sub_exists = $sub->find(true);
472
473         if ($sub_exists) {
474             $orig_sub = clone($sub);
475         } else {
476             $sub->created = DB_DataObject_Cast::dateTime();
477         }
478
479         $sub->token  = $token->key;
480         $sub->secret = $token->secret;
481
482         if ($sub_exists) {
483             $result = $sub->update($orig_sub);
484         } else {
485             $result = $sub->insert();
486         }
487
488         if (!$result) {
489             common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__);
490             // TRANS: Exception thrown when creating a new subscription fails in OAuth store.
491             throw new Exception(_('Could not insert new subscription.'));
492             return;
493         }
494
495         /* Notify user, if necessary. */
496
497         if ($subscribed instanceof User) {
498             mail_subscribe_notify_profile($subscribed,
499                                           Profile::getKV($subscriber->id));
500         }
501     }
502 }