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