]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/FacebookSSO/actions/facebookfinishlogin.php
e61f3515479b70b556b56ac4004e60b1d285ceba
[quix0rs-gnu-social.git] / plugins / FacebookSSO / actions / facebookfinishlogin.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * Login or register a local user based on a Facebook user
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  Plugin
23  * @package   StatusNet
24  * @author    Zach Copley <zach@status.net>
25  * @copyright 2010 StatusNet, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://status.net/
28  */
29
30 if (!defined('STATUSNET')) {
31     exit(1);
32 }
33
34 class FacebookfinishloginAction extends Action
35 {
36     private $facebook = null; // Facebook client
37     private $fbuid    = null; // Facebook user ID
38     private $fbuser   = null; // Facebook user object (JSON)
39
40     function prepare($args) {
41
42         parent::prepare($args);
43
44         $this->facebook = new Facebook(
45             array(
46                 'appId'  => common_config('facebook', 'appid'),
47                 'secret' => common_config('facebook', 'secret'),
48                 'cookie' => true,
49             )
50         );
51
52         // Check for a Facebook user session
53
54         $session = $this->facebook->getSession();
55         $me      = null;
56
57         if ($session) {
58             try {
59                 $this->fbuid  = $this->facebook->getUser();
60                 $this->fbuser = $this->facebook->api('/me');
61             } catch (FacebookApiException $e) {
62                 common_log(LOG_ERROR, $e, __FILE__);
63             }
64         }
65
66         if (!empty($this->fbuser)) {
67
68             // OKAY, all is well... proceed to register
69
70             common_debug("Found a valid Facebook user.", __FILE__);
71         } else {
72
73             // This shouldn't happen in the regular course of things
74
75             list($proxy, $ip) = common_client_ip();
76
77             common_log(
78                 LOG_WARNING,
79                     sprintf(
80                         'Failed Facebook authentication attempt, proxy = %s, ip = %s.',
81                          $proxy,
82                          $ip
83                     ),
84                     __FILE__
85             );
86
87             $this->clientError(
88                 _m('You must be logged into Facebook to register a local account using Facebook.')
89             );
90         }
91
92         return true;
93     }
94
95     function handle($args)
96     {
97         parent::handle($args);
98
99         if (common_is_real_login()) {
100             
101             // User is already logged in, are her accounts already linked?
102
103             $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
104
105             if (!empty($flink)) {
106
107                 // User already has a linked Facebook account and shouldn't be here!
108
109                 common_debug(
110                     sprintf(
111                         'There\'s already a local user %d linked with Facebook user %s.',
112                         $flink->user_id,
113                         $this->fbuid
114                     )
115                 );
116
117                 $this->clientError(
118                     _m('There is already a local account linked with that Facebook account.')
119                 );
120
121             } else {
122
123                 // Possibly reconnect an existing account
124                 
125                 $this->connectUser();
126             }
127
128         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
129
130             $token = $this->trimmed('token');
131
132             if (!$token || $token != common_session_token()) {
133                 $this->showForm(
134                     _m('There was a problem with your session token. Try again, please.'));
135                 return;
136             }
137
138             if ($this->arg('create')) {
139
140                 if (!$this->boolean('license')) {
141                     $this->showForm(
142                         _m('You can\'t register if you don\'t agree to the license.'),
143                         $this->trimmed('newname')
144                     );
145                     return;
146                 }
147
148                 // We has a valid Facebook session and the Facebook user has
149                 // agreed to the SN license, so create a new user
150                 $this->createNewUser();
151
152             } else if ($this->arg('connect')) {
153
154                 $this->connectNewUser();
155
156             } else {
157
158                 $this->showForm(
159                     _m('An unknown error has occured.'),
160                     $this->trimmed('newname')
161                 );
162             }
163         } else {
164
165             $this->tryLogin();
166         }
167     }
168
169     function showPageNotice()
170     {
171         if ($this->error) {
172
173             $this->element('div', array('class' => 'error'), $this->error);
174
175         } else {
176         
177             $this->element(
178                 'div', 'instructions',
179                 // TRANS: %s is the site name.
180                 sprintf(
181                     _m('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new local account, or connect with an existing local account.'),
182                     common_config('site', 'name')
183                 )
184             );
185         }
186     }
187
188     function title()
189     {
190         // TRANS: Page title.
191         return _m('Facebook Setup');
192     }
193
194     function showForm($error=null, $username=null)
195     {
196         $this->error = $error;
197         $this->username = $username;
198
199         $this->showPage();
200     }
201
202     function showPage()
203     {
204         parent::showPage();
205     }
206
207     /**
208      * @fixme much of this duplicates core code, which is very fragile.
209      * Should probably be replaced with an extensible mini version of
210      * the core registration form.
211      */
212     function showContent()
213     {
214         if (!empty($this->message_text)) {
215             $this->element('p', null, $this->message);
216             return;
217         }
218
219         $this->elementStart('form', array('method' => 'post',
220                                           'id' => 'form_settings_facebook_connect',
221                                           'class' => 'form_settings',
222                                           'action' => common_local_url('facebookfinishlogin')));
223         $this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
224         // TRANS: Legend.
225         $this->element('legend', null, _m('Connection options'));
226         $this->elementStart('ul', 'form_data');
227         $this->elementStart('li');
228         $this->element('input', array('type' => 'checkbox',
229                                       'id' => 'license',
230                                       'class' => 'checkbox',
231                                       'name' => 'license',
232                                       'value' => 'true'));
233         $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
234         // TRANS: %s is the name of the license used by the user for their status updates.
235         $message = _m('My text and files are available under %s ' .
236                      'except this private data: password, ' .
237                      'email address, IM address, and phone number.');
238         $link = '<a href="' .
239                 htmlspecialchars(common_config('license', 'url')) .
240                 '">' .
241                 htmlspecialchars(common_config('license', 'title')) .
242                 '</a>';
243         $this->raw(sprintf(htmlspecialchars($message), $link));
244         $this->elementEnd('label');
245         $this->elementEnd('li');
246         $this->elementEnd('ul');
247
248         $this->elementStart('fieldset');
249         $this->hidden('token', common_session_token());
250         $this->element('legend', null,
251                        // TRANS: Legend.
252                        _m('Create new account'));
253         $this->element('p', null,
254                        _m('Create a new user with this nickname.'));
255         $this->elementStart('ul', 'form_data');
256         $this->elementStart('li');
257         // TRANS: Field label.
258         $this->input('newname', _m('New nickname'),
259                      ($this->username) ? $this->username : '',
260                      _m('1-64 lowercase letters or numbers, no punctuation or spaces'));
261         $this->elementEnd('li');
262         $this->elementEnd('ul');
263         // TRANS: Submit button.
264         $this->submit('create', _m('BUTTON','Create'));
265         $this->elementEnd('fieldset');
266
267         $this->elementStart('fieldset');
268         // TRANS: Legend.
269         $this->element('legend', null,
270                        _m('Connect existing account'));
271         $this->element('p', null,
272                        _m('If you already have an account, login with your username and password to connect it to your Facebook.'));
273         $this->elementStart('ul', 'form_data');
274         $this->elementStart('li');
275         // TRANS: Field label.
276         $this->input('nickname', _m('Existing nickname'));
277         $this->elementEnd('li');
278         $this->elementStart('li');
279         $this->password('password', _m('Password'));
280         $this->elementEnd('li');
281         $this->elementEnd('ul');
282         // TRANS: Submit button.
283         $this->submit('connect', _m('BUTTON','Connect'));
284         $this->elementEnd('fieldset');
285
286         $this->elementEnd('fieldset');
287         $this->elementEnd('form');
288     }
289
290     function message($msg)
291     {
292         $this->message_text = $msg;
293         $this->showPage();
294     }
295
296     function createNewUser()
297     {
298         if (common_config('site', 'closed')) {
299             // TRANS: Client error trying to register with registrations not allowed.
300             $this->clientError(_m('Registration not allowed.'));
301             return;
302         }
303
304         $invite = null;
305
306         if (common_config('site', 'inviteonly')) {
307             $code = $_SESSION['invitecode'];
308             if (empty($code)) {
309                 // TRANS: Client error trying to register with registrations 'invite only'.
310                 $this->clientError(_m('Registration not allowed.'));
311                 return;
312             }
313
314             $invite = Invitation::staticGet($code);
315
316             if (empty($invite)) {
317                 // TRANS: Client error trying to register with an invalid invitation code.
318                 $this->clientError(_m('Not a valid invitation code.'));
319                 return;
320             }
321         }
322
323         $nickname = $this->trimmed('newname');
324
325         if (!Validate::string($nickname, array('min_length' => 1,
326                                                'max_length' => 64,
327                                                'format' => NICKNAME_FMT))) {
328             $this->showForm(_m('Nickname must have only lowercase letters and numbers and no spaces.'));
329             return;
330         }
331
332         if (!User::allowed_nickname($nickname)) {
333             $this->showForm(_m('Nickname not allowed.'));
334             return;
335         }
336
337         if (User::staticGet('nickname', $nickname)) {
338             $this->showForm(_m('Nickname already in use. Try another one.'));
339             return;
340         }
341
342         $args = array(
343             'nickname'        => $nickname,
344             'fullname'        => $this->fbuser['first_name']
345                 . ' ' . $this->fbuser['last_name'],
346             'email'           => $this->fbuser['email'],
347             'email_confirmed' => true,
348             'homepage'        => $this->fbuser['website'],
349             'bio'             => $this->fbuser['about'],
350             'location'        => $this->fbuser['location']['name']
351         );
352
353         if (!empty($invite)) {
354             $args['code'] = $invite->code;
355         }
356
357         $user = User::register($args);
358
359         $result = $this->flinkUser($user->id, $this->fbuid);
360
361         if (!$result) {
362             $this->serverError(_m('Error connecting user to Facebook.'));
363             return;
364         }
365
366         $this->setAvatar($user);
367
368         common_set_user($user);
369         common_real_login(true);
370
371         common_log(
372             LOG_INFO,
373             sprintf(
374                 'Registered new user %d from Facebook user %s',
375                 $user->id,
376                 $this->fbuid
377             ),
378             __FILE__
379         );
380
381         common_redirect(
382             common_local_url(
383                 'showstream',
384                 array('nickname' => $user->nickname)
385             ),
386             303
387         );
388     }
389
390     /*
391      * Attempt to download the user's Facebook picture and create a
392      * StatusNet avatar for the new user.
393      */
394     function setAvatar($user)
395     {
396         $picUrl = sprintf(
397             'http://graph.facebook.com/%s/picture?type=large',
398             $this->fbuid
399         );
400
401         // fetch the picture from Facebook
402         $client = new HTTPClient();
403
404         common_debug("status = $status - " . $finalUrl , __FILE__);
405
406         // fetch the actual picture
407         $response = $client->get($picUrl);
408
409         if ($response->isOk()) {
410
411             $finalUrl = $client->getUrl();
412             $filename = 'facebook-' . substr(strrchr($finalUrl, '/'), 1 );
413
414             common_debug("Filename = " . $filename, __FILE__);
415
416             $ok = file_put_contents(
417                 Avatar::path($filename),
418                 $response->getBody()
419             );
420
421             if (!$ok) {
422                 common_log(
423                     LOG_WARNING,
424                     sprintf(
425                         'Couldn\'t save Facebook avatar %s',
426                         $tmp
427                     ),
428                     __FILE__
429                 );
430
431             } else {
432
433                 $profile = $user->getProfile();
434
435                 if ($profile->setOriginal($filename)) {
436                     common_log(
437                         LOG_INFO,
438                         sprintf(
439                             'Saved avatar for %s (%d) from Facebook profile %s, filename = %s',
440                              $user->nickname,
441                              $user->id,
442                              $this->fbuid,
443                              $picture
444                         ),
445                         __FILE__
446                     );
447                 }
448             }
449         }
450     }
451
452     function connectNewUser()
453     {
454         $nickname = $this->trimmed('nickname');
455         $password = $this->trimmed('password');
456
457         if (!common_check_user($nickname, $password)) {
458             $this->showForm(_m('Invalid username or password.'));
459             return;
460         }
461
462         $user = User::staticGet('nickname', $nickname);
463
464         if (!empty($user)) {
465             common_debug('Facebook Connect Plugin - ' .
466                          "Legit user to connect to Facebook: $nickname");
467         }
468
469         $result = $this->flinkUser($user->id, $this->fbuid);
470
471         if (!$result) {
472             $this->serverError(_m('Error connecting user to Facebook.'));
473             return;
474         }
475
476         common_debug('Facebook Connnect Plugin - ' .
477                      "Connected Facebook user $this->fbuid to local user $user->id");
478
479         common_set_user($user);
480         common_real_login(true);
481
482         $this->goHome($user->nickname);
483     }
484
485     function connectUser()
486     {
487         $user = common_current_user();
488
489         $result = $this->flinkUser($user->id, $this->fbuid);
490
491         if (empty($result)) {
492             $this->serverError(_m('Error connecting user to Facebook.'));
493             return;
494         }
495
496         common_debug(
497             sprintf(
498                 'Connected Facebook user %s to local user %d',
499                 $this->fbuid,
500                 $user->id
501             ),
502             __FILE__
503         );
504
505         common_redirect(common_local_url('facebookfinishlogin'), 303);
506     }
507
508     function tryLogin()
509     {
510         common_debug(
511             sprintf(
512                 'Trying login for Facebook user %s',
513                 $this->fbuid
514             ),
515             __FILE__
516         );
517
518         $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
519
520         if (!empty($flink)) {
521             $user = $flink->getUser();
522
523             if (!empty($user)) {
524
525                 common_log(
526                     LOG_INFO,
527                     sprintf(
528                         'Logged in Facebook user %s as user %d (%s)',
529                         $this->fbuid,
530                         $user->nickname,
531                         $user->id
532                     ),
533                     __FILE__
534                 );
535
536                 common_set_user($user);
537                 common_real_login(true);
538                 $this->goHome($user->nickname);
539             }
540
541         } else {
542
543             common_debug(
544                 sprintf(
545                     'No flink found for fbuid: %s - new user',
546                     $this->fbuid
547                 ),
548                 __FILE__
549             );
550
551             $this->showForm(null, $this->bestNewNickname());
552         }
553     }
554
555     function goHome($nickname)
556     {
557         $url = common_get_returnto();
558         if ($url) {
559             // We don't have to return to it again
560             common_set_returnto(null);
561         } else {
562             $url = common_local_url('all',
563                                     array('nickname' =>
564                                           $nickname));
565         }
566
567         common_redirect($url, 303);
568     }
569
570     function flinkUser($user_id, $fbuid)
571     {
572         $flink = new Foreign_link();
573         $flink->user_id = $user_id;
574         $flink->foreign_id = $fbuid;
575         $flink->service = FACEBOOK_SERVICE;
576         
577         // Pull the access token from the Facebook cookies
578         $flink->credentials = $this->facebook->getAccessToken();
579
580         $flink->created = common_sql_now();
581
582         $flink_id = $flink->insert();
583
584         return $flink_id;
585     }
586
587     function bestNewNickname()
588     {
589         if (!empty($this->fbuser['name'])) {
590             $nickname = $this->nicknamize($this->fbuser['name']);
591             if ($this->isNewNickname($nickname)) {
592                 return $nickname;
593             }
594         }
595
596         // Try the full name
597
598         $fullname = trim($this->fbuser['firstname'] .
599             ' ' . $this->fbuser['lastname']);
600
601         if (!empty($fullname)) {
602             $fullname = $this->nicknamize($fullname);
603             if ($this->isNewNickname($fullname)) {
604                 return $fullname;
605             }
606         }
607
608         return null;
609     }
610
611      /**
612       * Given a string, try to make it work as a nickname
613       */
614      function nicknamize($str)
615      {
616          $str = preg_replace('/\W/', '', $str);
617          return strtolower($str);
618      }
619
620     function isNewNickname($str)
621     {
622         if (!Validate::string($str, array('min_length' => 1,
623                                           'max_length' => 64,
624                                           'format' => NICKNAME_FMT))) {
625             return false;
626         }
627         if (!User::allowed_nickname($str)) {
628             return false;
629         }
630         if (User::staticGet('nickname', $str)) {
631             return false;
632         }
633         return true;
634     }
635
636 }