]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/finishopenidlogin.php
change Laconica and Control Yourself to StatusNet in PHP files
[quix0rs-gnu-social.git] / actions / finishopenidlogin.php
1 <?php
2 /*
3  * StatusNet - a 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('LACONICA')) { exit(1); }
21
22 require_once(INSTALLDIR.'/lib/openid.php');
23
24 class FinishopenidloginAction extends Action
25 {
26     var $error = null;
27     var $username = null;
28     var $message = null;
29
30     function handle($args)
31     {
32         parent::handle($args);
33         if (!common_config('openid', 'enabled')) {
34             common_redirect(common_local_url('login'));
35         } else if (common_is_real_login()) {
36             $this->clientError(_('Already logged in.'));
37         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
38             $token = $this->trimmed('token');
39             if (!$token || $token != common_session_token()) {
40                 $this->showForm(_('There was a problem with your session token. Try again, please.'));
41                 return;
42             }
43             if ($this->arg('create')) {
44                 if (!$this->boolean('license')) {
45                     $this->showForm(_('You can\'t register if you don\'t agree to the license.'),
46                                     $this->trimmed('newname'));
47                     return;
48                 }
49                 $this->createNewUser();
50             } else if ($this->arg('connect')) {
51                 $this->connectUser();
52             } else {
53                 common_debug(print_r($this->args, true), __FILE__);
54                 $this->showForm(_('Something weird happened.'),
55                                 $this->trimmed('newname'));
56             }
57         } else {
58             $this->tryLogin();
59         }
60     }
61
62     function showPageNotice()
63     {
64         if ($this->error) {
65             $this->element('div', array('class' => 'error'), $this->error);
66         } else {
67             $this->element('div', 'instructions',
68                            sprintf(_('This is the first time you\'ve logged into %s so we must connect your OpenID to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
69         }
70     }
71
72     function title()
73     {
74         return _('OpenID Account Setup');
75     }
76
77     function showForm($error=null, $username=null)
78     {
79         $this->error = $error;
80         $this->username = $username;
81
82         $this->showPage();
83     }
84
85     function showContent()
86     {
87         if (!empty($this->message_text)) {
88             $this->element('div', array('class' => 'error'), $this->message_text);
89             return;
90         }
91
92         $this->elementStart('form', array('method' => 'post',
93                                           'id' => 'account_connect',
94                                           'action' => common_local_url('finishopenidlogin')));
95         $this->hidden('token', common_session_token());
96         $this->element('h2', null,
97                        _('Create new account'));
98         $this->element('p', null,
99                        _('Create a new user with this nickname.'));
100         $this->input('newname', _('New nickname'),
101                      ($this->username) ? $this->username : '',
102                      _('1-64 lowercase letters or numbers, no punctuation or spaces'));
103         $this->elementStart('p');
104         $this->element('input', array('type' => 'checkbox',
105                                       'id' => 'license',
106                                       'name' => 'license',
107                                       'value' => 'true'));
108         $this->text(_('My text and files are available under '));
109         $this->element('a', array('href' => common_config('license', 'url')),
110                        common_config('license', 'title'));
111         $this->text(_(' except this private data: password, email address, IM address, phone number.'));
112         $this->elementEnd('p');
113         $this->submit('create', _('Create'));
114         $this->element('h2', null,
115                        _('Connect existing account'));
116         $this->element('p', null,
117                        _('If you already have an account, login with your username and password to connect it to your OpenID.'));
118         $this->input('nickname', _('Existing nickname'));
119         $this->password('password', _('Password'));
120         $this->submit('connect', _('Connect'));
121         $this->elementEnd('form');
122     }
123
124     function tryLogin()
125     {
126         $consumer = oid_consumer();
127
128         $response = $consumer->complete(common_local_url('finishopenidlogin'));
129
130         if ($response->status == Auth_OpenID_CANCEL) {
131             $this->message(_('OpenID authentication cancelled.'));
132             return;
133         } else if ($response->status == Auth_OpenID_FAILURE) {
134             // Authentication failed; display the error message.
135             $this->message(sprintf(_('OpenID authentication failed: %s'), $response->message));
136         } else if ($response->status == Auth_OpenID_SUCCESS) {
137             // This means the authentication succeeded; extract the
138             // identity URL and Simple Registration data (if it was
139             // returned).
140             $display = $response->getDisplayIdentifier();
141             $canonical = ($response->endpoint->canonicalID) ?
142               $response->endpoint->canonicalID : $response->getDisplayIdentifier();
143
144             $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
145
146             if ($sreg_resp) {
147                 $sreg = $sreg_resp->contents();
148             }
149
150             $user = oid_get_user($canonical);
151
152             if ($user) {
153                 oid_set_last($display);
154                 # XXX: commented out at @edd's request until better
155                 # control over how data flows from OpenID provider.
156                 # oid_update_user($user, $sreg);
157                 common_set_user($user);
158                 common_real_login(true);
159                 if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
160                     common_rememberme($user);
161                 }
162                 unset($_SESSION['openid_rememberme']);
163                 $this->goHome($user->nickname);
164             } else {
165                 $this->saveValues($display, $canonical, $sreg);
166                 $this->showForm(null, $this->bestNewNickname($display, $sreg));
167             }
168         }
169     }
170
171     function message($msg)
172     {
173         $this->message_text = $msg;
174         $this->showPage();
175     }
176
177     function saveValues($display, $canonical, $sreg)
178     {
179         common_ensure_session();
180         $_SESSION['openid_display'] = $display;
181         $_SESSION['openid_canonical'] = $canonical;
182         $_SESSION['openid_sreg'] = $sreg;
183     }
184
185     function getSavedValues()
186     {
187         return array($_SESSION['openid_display'],
188                      $_SESSION['openid_canonical'],
189                      $_SESSION['openid_sreg']);
190     }
191
192     function createNewUser()
193     {
194         # FIXME: save invite code before redirect, and check here
195
196         if (common_config('site', 'closed')) {
197             $this->clientError(_('Registration not allowed.'));
198             return;
199         }
200
201         $invite = null;
202
203         if (common_config('site', 'inviteonly')) {
204             $code = $_SESSION['invitecode'];
205             if (empty($code)) {
206                 $this->clientError(_('Registration not allowed.'));
207                 return;
208             }
209
210             $invite = Invitation::staticGet($code);
211
212             if (empty($invite)) {
213                 $this->clientError(_('Not a valid invitation code.'));
214                 return;
215             }
216         }
217
218         $nickname = $this->trimmed('newname');
219
220         if (!Validate::string($nickname, array('min_length' => 1,
221                                                'max_length' => 64,
222                                                'format' => NICKNAME_FMT))) {
223             $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
224             return;
225         }
226
227         if (!User::allowed_nickname($nickname)) {
228             $this->showForm(_('Nickname not allowed.'));
229             return;
230         }
231
232         if (User::staticGet('nickname', $nickname)) {
233             $this->showForm(_('Nickname already in use. Try another one.'));
234             return;
235         }
236
237         list($display, $canonical, $sreg) = $this->getSavedValues();
238
239         if (!$display || !$canonical) {
240             $this->serverError(_('Stored OpenID not found.'));
241             return;
242         }
243
244         # Possible race condition... let's be paranoid
245
246         $other = oid_get_user($canonical);
247
248         if ($other) {
249             $this->serverError(_('Creating new account for OpenID that already has a user.'));
250             return;
251         }
252
253         $location = '';
254         if (!empty($sreg['country'])) {
255             if ($sreg['postcode']) {
256                 # XXX: use postcode to get city and region
257                 # XXX: also, store postcode somewhere -- it's valuable!
258                 $location = $sreg['postcode'] . ', ' . $sreg['country'];
259             } else {
260                 $location = $sreg['country'];
261             }
262         }
263
264         if (!empty($sreg['fullname']) && mb_strlen($sreg['fullname']) <= 255) {
265             $fullname = $sreg['fullname'];
266         } else {
267             $fullname = '';
268         }
269
270         if (!empty($sreg['email']) && Validate::email($sreg['email'], true)) {
271             $email = $sreg['email'];
272         } else {
273             $email = '';
274         }
275
276         # XXX: add language
277         # XXX: add timezone
278
279         $args = array('nickname' => $nickname,
280                       'email' => $email,
281                       'fullname' => $fullname,
282                       'location' => $location);
283
284         if (!empty($invite)) {
285             $args['code'] = $invite->code;
286         }
287
288         $user = User::register($args);
289
290         $result = oid_link_user($user->id, $canonical, $display);
291
292         oid_set_last($display);
293         common_set_user($user);
294         common_real_login(true);
295         if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
296             common_rememberme($user);
297         }
298         unset($_SESSION['openid_rememberme']);
299         common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
300                         303);
301     }
302
303     function connectUser()
304     {
305         $nickname = $this->trimmed('nickname');
306         $password = $this->trimmed('password');
307
308         if (!common_check_user($nickname, $password)) {
309             $this->showForm(_('Invalid username or password.'));
310             return;
311         }
312
313         # They're legit!
314
315         $user = User::staticGet('nickname', $nickname);
316
317         list($display, $canonical, $sreg) = $this->getSavedValues();
318
319         if (!$display || !$canonical) {
320             $this->serverError(_('Stored OpenID not found.'));
321             return;
322         }
323
324         $result = oid_link_user($user->id, $canonical, $display);
325
326         if (!$result) {
327             $this->serverError(_('Error connecting user to OpenID.'));
328             return;
329         }
330
331         oid_update_user($user, $sreg);
332         oid_set_last($display);
333         common_set_user($user);
334         common_real_login(true);
335         if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
336             common_rememberme($user);
337         }
338         unset($_SESSION['openid_rememberme']);
339         $this->goHome($user->nickname);
340     }
341
342     function goHome($nickname)
343     {
344         $url = common_get_returnto();
345         if ($url) {
346             # We don't have to return to it again
347             common_set_returnto(null);
348         } else {
349             $url = common_local_url('all',
350                                     array('nickname' =>
351                                           $nickname));
352         }
353         common_redirect($url, 303);
354     }
355
356     function bestNewNickname($display, $sreg)
357     {
358
359         # Try the passed-in nickname
360
361         if (!empty($sreg['nickname'])) {
362             $nickname = $this->nicknamize($sreg['nickname']);
363             if ($this->isNewNickname($nickname)) {
364                 return $nickname;
365             }
366         }
367
368         # Try the full name
369
370         if (!empty($sreg['fullname'])) {
371             $fullname = $this->nicknamize($sreg['fullname']);
372             if ($this->isNewNickname($fullname)) {
373                 return $fullname;
374             }
375         }
376
377         # Try the URL
378
379         $from_url = $this->openidToNickname($display);
380
381         if ($from_url && $this->isNewNickname($from_url)) {
382             return $from_url;
383         }
384
385         # XXX: others?
386
387         return null;
388     }
389
390     function isNewNickname($str)
391     {
392         if (!Validate::string($str, array('min_length' => 1,
393                                           'max_length' => 64,
394                                           'format' => NICKNAME_FMT))) {
395             return false;
396         }
397         if (!User::allowed_nickname($str)) {
398             return false;
399         }
400         if (User::staticGet('nickname', $str)) {
401             return false;
402         }
403         return true;
404     }
405
406     function openidToNickname($openid)
407     {
408         if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
409             return $this->xriToNickname($openid);
410         } else {
411             return $this->urlToNickname($openid);
412         }
413     }
414
415     # We try to use an OpenID URL as a legal StatusNet user name in this order
416     # 1. Plain hostname, like http://evanp.myopenid.com/
417     # 2. One element in path, like http://profile.typekey.com/EvanProdromou/
418     #    or http://getopenid.com/evanprodromou
419
420     function urlToNickname($openid)
421     {
422         static $bad = array('query', 'user', 'password', 'port', 'fragment');
423
424         $parts = parse_url($openid);
425
426         # If any of these parts exist, this won't work
427
428         foreach ($bad as $badpart) {
429             if (array_key_exists($badpart, $parts)) {
430                 return null;
431             }
432         }
433
434         # We just have host and/or path
435
436         # If it's just a host...
437         if (array_key_exists('host', $parts) &&
438             (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
439         {
440             $hostparts = explode('.', $parts['host']);
441
442             # Try to catch common idiom of nickname.service.tld
443
444             if ((count($hostparts) > 2) &&
445                 (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
446                 (strcmp($hostparts[0], 'www') != 0))
447             {
448                 return $this->nicknamize($hostparts[0]);
449             } else {
450                 # Do the whole hostname
451                 return $this->nicknamize($parts['host']);
452             }
453         } else {
454             if (array_key_exists('path', $parts)) {
455                 # Strip starting, ending slashes
456                 $path = preg_replace('@/$@', '', $parts['path']);
457                 $path = preg_replace('@^/@', '', $path);
458                 if (strpos($path, '/') === false) {
459                     return $this->nicknamize($path);
460                 }
461             }
462         }
463
464         return null;
465     }
466
467     function xriToNickname($xri)
468     {
469         $base = $this->xriBase($xri);
470
471         if (!$base) {
472             return null;
473         } else {
474             # =evan.prodromou
475             # or @gratis*evan.prodromou
476             $parts = explode('*', substr($base, 1));
477             return $this->nicknamize(array_pop($parts));
478         }
479     }
480
481     function xriBase($xri)
482     {
483         if (substr($xri, 0, 6) == 'xri://') {
484             return substr($xri, 6);
485         } else {
486             return $xri;
487         }
488     }
489
490     # Given a string, try to make it work as a nickname
491
492     function nicknamize($str)
493     {
494         $str = preg_replace('/\W/', '', $str);
495         return strtolower($str);
496     }
497 }