]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/apioauthstore.php
Add/update translator documentation.
[quix0rs-gnu-social.git] / lib / apioauthstore.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, 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 INSTALLDIR . '/lib/oauthstore.php';
23
24 // @todo FIXME: Class documentation missing.
25 class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
26 {
27     function lookup_consumer($consumerKey)
28     {
29         $con = Consumer::staticGet('consumer_key', $consumerKey);
30
31         if (!$con) {
32
33             // Create an anon consumer and anon application if one
34             // doesn't exist already
35             if ($consumerKey == 'anonymous') {
36
37                 common_debug("API OAuth - creating anonymous consumer");
38                 $con = new Consumer();
39                 $con->consumer_key    = $consumerKey;
40                 $con->consumer_secret = $consumerKey;
41                 $con->created         = common_sql_now();
42
43                 $result = $con->insert();
44                 if (!$result) {
45                     // TRANS: Server error displayed when trying to create an anynymous OAuth consumer.
46                     $this->serverError(_('Could not create anonymous consumer.'));
47                 }
48
49                 $app = Oauth_application::getByConsumerKey('anonymous');
50
51                 if (!$app) {
52                     common_debug("API OAuth - creating anonymous application");
53                     $app               = new OAuth_application();
54                     $app->owner        = 1; // XXX: What to do here?
55                     $app->consumer_key = $con->consumer_key;
56                     $app->name         = 'anonymous';
57                     $app->icon         = 'default-avatar-stream.png'; // XXX: Fix this!
58                     $app->description  = "An anonymous application";
59                     // XXX: allow the user to set the access type when
60                     // authorizing? Currently we default to r+w for anonymous
61                     // OAuth client applications
62                     $app->access_type  = 3; // read + write
63                     $app->type         = 2; // desktop
64                     $app->created      = common_sql_now();
65
66                     $id = $app->insert();
67
68                     if (!$id) {
69                         // TRANS: Server error displayed when trying to create an anynymous OAuth application.
70                         $this->serverError(_("Could not create anonymous OAuth application."));
71                     }
72                 }
73             } else {
74                 return null;
75             }
76         }
77
78         return new OAuthConsumer(
79             $con->consumer_key,
80             $con->consumer_secret
81         );
82     }
83
84     function getAppByRequestToken($token_key)
85     {
86         // Look up the full req token
87         $req_token = $this->lookup_token(
88             null,
89             'request',
90             $token_key
91         );
92
93         if (empty($req_token)) {
94             common_debug("Couldn't get request token from oauth datastore");
95             return null;
96         }
97
98         // Look up the full Token
99         $token = new Token();
100         $token->tok = $req_token->key;
101         $result = $token->find(true);
102
103         if (empty($result)) {
104             common_debug('Couldn\'t find req token in the token table.');
105             return null;
106         }
107
108         // Look up the app
109         $app = new Oauth_application();
110         $app->consumer_key = $token->consumer_key;
111         $result = $app->find(true);
112
113         if (!empty($result)) {
114             return $app;
115         } else {
116             common_debug("Couldn't find the app!");
117             return null;
118         }
119     }
120
121     function new_access_token($token, $consumer, $verifier)
122     {
123         common_debug(
124             sprintf(
125                 "New access token from request token %s, consumer %s and verifier %s ",
126                 $token,
127                 $consumer,
128                 $verifier
129             ),
130             __FILE__
131         );
132
133         $rt = new Token();
134
135         $rt->consumer_key = $consumer->key;
136         $rt->tok          = $token->key;
137         $rt->type         = 0; // request
138
139         $app = Oauth_application::getByConsumerKey($consumer->key);
140         assert(!empty($app));
141
142         if ($rt->find(true) && $rt->state == 1 && $rt->verifier == $verifier) { // authorized
143
144             common_debug('Request token found.', __FILE__);
145
146             // find the app and profile associated with this token
147             $tokenAssoc = Oauth_token_association::staticGet('token', $rt->tok);
148
149             if (!$tokenAssoc) {
150                 throw new Exception(
151                     // TRANS: Exception thrown when no token association could be found.
152                     _('Could not find a profile and application associated with the request token.')
153                 );
154             }
155
156             // Check to see if we have previously issued an access token for
157             // this application and profile; if so we can just return the
158             // existing access token. That seems to be the best practice. It
159             // makes it so users only have to authorize the app once per
160             // machine.
161
162             $appUser = new Oauth_application_user();
163
164             $appUser->application_id = $app->id;
165             $appUser->profile_id     = $tokenAssoc->profile_id;
166
167             $result = $appUser->find(true);
168
169             if (!empty($result)) {
170
171                 common_log(LOG_INFO,
172                      sprintf(
173                         "Existing access token found for application %s, profile %s.",
174                         $app->id,
175                         $tokenAssoc->profile_id
176                      )
177                 );
178
179                 $at = null;
180
181                 // Special case: we used to store request tokens in the
182                 // Oauth_application_user record, and the access_type would
183                 // always be 0 (no access) as a failsafe until an access
184                 // token was issued and replaced the request token. There could
185                 // be a few old Oauth_application_user records storing request
186                 // tokens still around, and we don't want to accidentally
187                 // return a useless request token instead of a new access
188                 // token. So if we find one, we generate a new access token
189                 // and update the existing Oauth_application_user record before
190                 // returning the new access token. This should be rare.
191
192                 if ($appUser->access_type == 0) {
193
194                     $at = $this->generateNewAccessToken($consumer, $rt, $verifier);
195                     $this->updateAppUser($appUser, $app, $at);
196
197                 } else {
198
199                     $at = new Token();
200
201                     // fetch the full access token
202                     $at->consumer_key = $consumer->key;
203                     $at->tok          = $appUser->token;
204
205                     $result = $at->find(true);
206
207                     if (!$result) {
208                         throw new Exception(
209                             // TRANS: Exception thrown when no access token can be issued.
210                             _('Could not issue access token.')
211                         );
212                     }
213                 }
214
215                 // Yay, we can re-issue the access token
216                 return new OAuthToken($at->tok, $at->secret);
217
218             } else {
219
220                common_log(LOG_INFO,
221                     sprintf(
222                         "Creating new access token for application %s, profile %s.",
223                         $app->id,
224                         $tokenAssoc->profile_id
225                      )
226                 );
227
228                 $at = $this->generateNewAccessToken($consumer, $rt, $verifier);
229                 $this->newAppUser($tokenAssoc, $app, $at);
230
231                 // Okay, good
232                 return new OAuthToken($at->tok, $at->secret);
233             }
234
235         } else {
236
237             // the token was not authorized or not verfied
238             common_log(
239                 LOG_INFO,
240                 sprintf(
241                     "API OAuth - Attempt to exchange unauthorized or unverified request token %s for an access token.",
242                      $rt->tok
243                 )
244             );
245             return null;
246         }
247     }
248
249     /*
250      * Generate a new access token and save it to the database
251      *
252      * @param Consumer $consumer the OAuth consumer
253      * @param Token    $rt       the authorized request token
254      * @param string   $verifier the OAuth 1.0a verifier
255      *
256      * @access private
257      *
258      * @return Token   $at       the new access token
259      */
260     private function generateNewAccessToken($consumer, $rt, $verifier)
261     {
262         $at = new Token();
263
264         $at->consumer_key      = $consumer->key;
265         $at->tok               = common_good_rand(16);
266         $at->secret            = common_good_rand(16);
267         $at->type              = 1; // access
268         $at->verifier          = $verifier;
269         $at->verified_callback = $rt->verified_callback; // 1.0a
270         $at->created           = common_sql_now();
271
272         if (!$at->insert()) {
273             $e = $at->_lastError;
274             common_debug('access token "' . $at->tok . '" not inserted: "' . $e->message . '"', __FILE__);
275             return null;
276         } else {
277             common_debug('access token "' . $at->tok . '" inserted', __FILE__);
278             // burn the old one
279             $orig_rt   = clone($rt);
280             $rt->state = 2; // used
281             if (!$rt->update($orig_rt)) {
282                 return null;
283             }
284             common_debug('request token "' . $rt->tok . '" updated', __FILE__);
285         }
286
287         return $at;
288     }
289
290    /*
291     * Add a new app user (Oauth_application_user) record
292     *
293     * @param Oauth_token_association $tokenAssoc token-to-app association
294     * @param Oauth_application       $app        the OAuth client app
295     * @param Token                   $at         the access token
296     *
297     * @access private
298     *
299     * @return void
300     */
301     private function newAppUser($tokenAssoc, $app, $at)
302     {
303         $appUser = new Oauth_application_user();
304
305         $appUser->profile_id     = $tokenAssoc->profile_id;
306         $appUser->application_id = $app->id;
307         $appUser->access_type    = $app->access_type;
308         $appUser->token          = $at->tok;
309         $appUser->created        = common_sql_now();
310
311         $result = $appUser->insert();
312
313         if (!$result) {
314             common_log_db_error($appUser, 'INSERT', __FILE__);
315
316             throw new Exception(
317                 // TRANS: Exception thrown when a database error occurs.
318                 _('Database error inserting OAuth application user.')
319             );
320         }
321     }
322
323    /*
324     * Update an existing app user (Oauth_application_user) record
325     *
326     * @param Oauth_application_user $appUser existing app user rec
327     * @param Oauth_application      $app     the OAuth client app
328     * @param Token                  $at      the access token
329     *
330     * @access private
331     *
332     * @return void
333     */
334     private function updateAppUser($appUser, $app, $at)
335     {
336         $original = clone($appUser);
337         $appUser->access_type = $app->access_type;
338         $appUser->token       = $at->tok;
339
340         $result = $appUser->update($original);
341
342         if (!$result) {
343             common_log_db_error($appUser, 'UPDATE', __FILE__);
344             throw new Exception(
345                 // TRANS: Exception thrown when a database error occurs.
346                 _('Database error updating OAuth application user.')
347             );
348         }
349     }
350
351     /**
352      * Revoke specified access token
353      *
354      * Revokes the token specified by $token_key.
355      * Throws exceptions in case of error.
356      *
357      * @param string $token_key the token to be revoked
358      * @param int    $type      type of token (0 = req, 1 = access)
359      *
360      * @access public
361      *
362      * @return void
363      */
364     public function revoke_token($token_key, $type = 0) {
365         $rt        = new Token();
366         $rt->tok   = $token_key;
367         $rt->type  = $type;
368         $rt->state = 0;
369
370         if (!$rt->find(true)) {
371             // TRANS: Exception thrown when an attempt is made to revoke an unknown token.
372             throw new Exception(_('Tried to revoke unknown token.'));
373         }
374
375         if (!$rt->delete()) {
376             // TRANS: Exception thrown when an attempt is made to remove a revoked token.
377             throw new Exception(_('Failed to delete revoked token.'));
378         }
379     }
380
381     /*
382      * Create a new request token. Overrided to support OAuth 1.0a callback
383      *
384      * @param OAuthConsumer $consumer the OAuth Consumer for this token
385      * @param string        $callback the verified OAuth callback URL
386      *
387      * @return OAuthToken   $token a new unauthorized OAuth request token
388      */
389     function new_request_token($consumer, $callback)
390     {
391         $t = new Token();
392         $t->consumer_key = $consumer->key;
393         $t->tok = common_good_rand(16);
394         $t->secret = common_good_rand(16);
395         $t->type = 0; // request
396         $t->state = 0; // unauthorized
397         $t->verified_callback = $callback;
398
399         if ($callback === 'oob') {
400             // six digit pin
401             $t->verifier = mt_rand(0, 9999999);
402         } else {
403             $t->verifier = common_good_rand(8);
404         }
405
406         $t->created = DB_DataObject_Cast::dateTime();
407         if (!$t->insert()) {
408             return null;
409         } else {
410             return new OAuthToken($t->tok, $t->secret);
411         }
412     }
413 }