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