]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/finishopenidlogin.php
5205ee90decf412241f1126f72dc51694ed2cfbd
[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($display, $canonical, $sreg) {
183                 common_ensure_session();
184                 return array($_SESSION['openid_display'],
185                                          $_SESSION['openid_canonical'],
186                                          $_SESSION['openid_sreg']);
187         }
188         
189         function create_new_login() {
190                 
191                 $nickname = $this->trimmed('newname');
192                 
193                 if (!Validate::string($nickname, array('min_length' => 1,
194                                                                                            'max_length' => 64,
195                                                                                            'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
196                         $this->show_form(_t('Nickname must have only letters and numbers and no spaces.'));
197                         return;
198                 }
199                 
200                 if (User::staticGet('nickname', $nickname)) {
201                         $this->show_form(_t('Nickname already in use. Try another one.'));
202                         return;
203                 }
204                 
205                 list($display, $canonical, $sreg) = $this->get_saved_values();
206                 
207                 if (!$display || !$canonical) {
208                         common_server_error(_t('Stored OpenID not found.'));
209                         return;
210                 }
211                 
212                 # Possible race condition... let's be paranoid
213                 
214                 $other = $this->get_user($canonical);
215                 
216                 if ($other) {
217                         common_server_error(_t('Creating new account for OpenID that already has a user.'));
218                         return;
219                 }
220                 
221                 $profile = new Profile();
222                 
223                 $profile->nickname = $nickname;
224                 
225                 if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
226                         $profile->fullname = $sreg['fullname'];
227                 }
228                 
229                 if ($sreg['country']) {
230                         if ($sreg['postcode']) {
231                                 # XXX: use postcode to get city and region
232                                 # XXX: also, store postcode somewhere -- it's valuable!
233                                 $profile->location = $sreg['postcode'] . ', ' . $sreg['country'];
234                         } else {
235                                 $profile->location = $sreg['country'];
236                         }
237                 }
238
239                 # XXX save language if it's passed
240                 # XXX save timezone if it's passed
241                 
242                 $profile->created = DB_DataObject_Cast::dateTime(); # current time
243                 
244                 $id = $profile->insert();
245                 if (!$id) {
246                         common_server_error(_t('Error saving the profile.'));
247                         return;
248                 }
249                 
250                 $user = new User();
251                 $user->id = $id;
252                 $user->nickname = $nickname;
253                 $user->uri = common_mint_tag('user:'.$id);
254                 
255                 if ($sreg['email'] && Validate::email($sreg['email'], true)) {
256                         $user->email = $sreg['email'];
257                 }
258                 
259                 $user->created = DB_DataObject_Cast::dateTime(); # current time
260                 
261                 $result = $user->insert();
262                 
263                 if (!$result) {
264                         # Try to clean up...
265                         $profile->delete();
266                 }
267
268                 $oid = new User_openid();
269                 $oid->display = $display;
270                 $oid->canonical = $canonical;
271                 $oid->user_id = $id;
272                 $oid->created = DB_DataObject_Cast::dateTime();
273                 
274                 $result = $oid->insert();
275
276                 if (!$result) {
277                         # Try to clean up...
278                         $user->delete();
279                         $profile->delete();
280                 }
281                 
282                 common_redirect(common_local_url('profilesettings'));
283         }
284         
285         function connect_user() {
286                 
287                 $nickname = $this->trimmed('nickname');
288                 $password = $this->trimmed('password');
289
290                 if (!common_check_user($nickname, $password)) {
291                         $this->show_form(_t('Invalid username or password.'));
292                         return;
293                 }
294
295                 # They're legit!
296                 
297                 $user = User::staticGet('nickname', $nickname);
298
299                 list($display, $canonical, $sreg) = $this->get_saved_values();
300
301                 if (!$display || !$canonical) {
302                         common_server_error(_t('Stored OpenID not found.'));
303                         return;
304                 }
305                 
306                 $oid = new User_openid();
307                 $oid->display = $display;
308                 $oid->canonical = $canonical;
309                 $oid->user_id = $user->id;
310                 $oid->created = DB_DataObject_Cast::dateTime();
311                 
312                 if (!$oid->insert()) {
313                         common_server_error(_t('Error connecting OpenID.'));
314                         return;
315                 }
316                 
317                 $this->update_user($user, $sreg);
318                 common_set_user($user->nickname);
319                 $this->go_home($user->nickname);
320         }
321         
322         function go_home($nickname) {
323                 $url = common_get_returnto();
324                 if ($url) {
325                         # We don't have to return to it again
326                         common_set_returnto(NULL);
327                 } else {
328                         $url = common_local_url('all',
329                                                                         array('nickname' =>
330                                                                                   $nickname));
331                 }
332                 common_redirect($url);
333         }
334         
335         function best_new_nickname($display, $sreg) {
336                 
337                 # Try the passed-in nickname
338                 
339                 if ($sreg['nickname'] && $this->is_new_nickname($sreg['nickname'])) {
340                         return $sreg['nickname'];
341                 }
342
343                 # Try the full name
344
345                 if ($sreg['fullname']) {
346                         $fullname = $this->nicknamize($sreg['fullname']);
347                         if ($this->is_new_nickname($fullname)) {
348                                 return $fullname;
349                         }
350                 }
351                 
352                 # Try the URL
353                 
354                 $from_url = $this->openid_to_nickname($display);
355                 
356                 if ($from_url && $this->is_new_nickname($from_url)) {
357                         return $from_url;
358                 }
359
360                 # XXX: others?
361
362                 return NULL;
363         }
364
365         function is_new_nickname($str) {
366                 if (!Validate::string($str, array('min_length' => 1,
367                                                                                   'max_length' => 64,
368                                                                                   'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
369                         return false;
370                 }
371                 if (User::staticGet('nickname', $str)) {
372                         return false;
373                 }
374                 return true;
375         }
376         
377         function openid_to_nickname($openid) {
378         if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
379                         return $this->xri_to_nickname($openid);
380                 } else {
381                         return $this->url_to_nickname($openid);
382                 }
383         }
384
385         # We try to use an OpenID URL as a legal Laconica user name in this order
386         # 1. Plain hostname, like http://evanp.myopenid.com/
387         # 2. One element in path, like http://profile.typekey.com/EvanProdromou/
388         #    or http://getopenid.com/evanprodromou
389
390     function url_to_nickname($openid) {
391                 static $bad = array('query', 'user', 'password', 'port', 'fragment');
392
393             $parts = parse_url($openid);
394
395                 # If any of these parts exist, this won't work
396
397                 foreach ($bad as $badpart) {
398                         if (array_key_exists($badpart, $parts)) {
399                                 return NULL;
400                         }
401                 }
402
403                 # We just have host and/or path
404
405                 # If it's just a host...
406                 if (array_key_exists('host', $parts) &&
407                         (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
408                 {
409                         $hostparts = explode('.', $parts['host']);
410
411                         # Try to catch common idiom of nickname.service.tld
412
413                         if ((count($hostparts) > 2) &&
414                                 (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
415                                 (strcmp($hostparts[0], 'www') != 0))
416                         {
417                                 return $this->nicknamize($hostparts[0]);
418                         } else {
419                                 # Do the whole hostname
420                                 return $this->nicknamize($parts['host']);
421                         }
422                 } else {
423                         if (array_key_exists('path', $parts)) {
424                                 # Strip starting, ending slashes
425                                 $path = preg_replace('@/$@', '', $parts['path']);
426                                 $path = preg_replace('@^/@', '', $path);
427                                 if (strpos($path, '/') === false) {
428                                         return $this->nicknamize($path);
429                                 }
430                         }
431                 }
432
433                 return NULL;
434         }
435
436         function xri_to_nickname($xri) {
437                 $base = $this->xri_base($xri);
438
439                 if (!$base) {
440                         return NULL;
441                 } else {
442                         # =evan.prodromou
443                         # or @gratis*evan.prodromou
444                         $parts = explode('*', substr($base, 1));
445                         return $this->nicknamize(array_pop($parts));
446                 }
447         }
448         
449         function xri_base($xri) {
450                 if (substr($xri, 0, 6) == 'xri://') {
451                         return substr($xri, 6);
452                 } else {
453                         return $xri;
454                 }
455         }
456
457         # Given a string, try to make it work as a nickname
458         
459         function nicknamize($str) {
460                 $str = preg_replace('/\W/', '', $str);
461                 return strtolower($str);
462         }
463 }