We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

finishopenidlogin.php 15.9 KB
Newer Older
Evan Prodromou's avatar
Evan Prodromou committed
1 2
<?php
/*
3
 * StatusNet - the distributed open-source microblogging tool
4
 * Copyright (C) 2008, 2009, StatusNet, Inc.
Evan Prodromou's avatar
Evan Prodromou committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

20
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
Evan Prodromou's avatar
Evan Prodromou committed
21

22
require_once INSTALLDIR.'/plugins/OpenID/openid.php';
Evan Prodromou's avatar
Evan Prodromou committed
23

24 25
class FinishopenidloginAction extends Action
{
Evan Prodromou's avatar
Evan Prodromou committed
26 27 28
    var $error = null;
    var $username = null;
    var $message = null;
Evan Prodromou's avatar
Evan Prodromou committed
29

30 31
    function handle($args)
    {
32
        parent::handle($args);
33
        if (common_is_real_login()) {
34
            $this->clientError(_m('Already logged in.'));
35 36 37
        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
            $token = $this->trimmed('token');
            if (!$token || $token != common_session_token()) {
38
                $this->showForm(_m('There was a problem with your session token. Try again, please.'));
39 40 41 42
                return;
            }
            if ($this->arg('create')) {
                if (!$this->boolean('license')) {
43
                    $this->showForm(_m('You can\'t register if you don\'t agree to the license.'),
Evan Prodromou's avatar
Evan Prodromou committed
44
                                    $this->trimmed('newname'));
45 46
                    return;
                }
Evan Prodromou's avatar
Evan Prodromou committed
47
                $this->createNewUser();
48
            } else if ($this->arg('connect')) {
Evan Prodromou's avatar
Evan Prodromou committed
49
                $this->connectUser();
50
            } else {
51
                $this->showForm(_m('Something weird happened.'),
Evan Prodromou's avatar
Evan Prodromou committed
52
                                $this->trimmed('newname'));
53 54
            }
        } else {
Evan Prodromou's avatar
Evan Prodromou committed
55
            $this->tryLogin();
56 57 58
        }
    }

Evan Prodromou's avatar
Evan Prodromou committed
59
    function showPageNotice()
60
    {
Evan Prodromou's avatar
Evan Prodromou committed
61 62
        if ($this->error) {
            $this->element('div', array('class' => 'error'), $this->error);
63
        } else {
64
            $this->element('div', 'instructions',
65
                           sprintf(_m('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')));
66 67 68
        }
    }

Evan Prodromou's avatar
Evan Prodromou committed
69
    function title()
70
    {
71
        return _m('OpenID Account Setup');
Evan Prodromou's avatar
Evan Prodromou committed
72 73 74 75 76 77 78 79 80 81
    }

    function showForm($error=null, $username=null)
    {
        $this->error = $error;
        $this->username = $username;

        $this->showPage();
    }

82 83 84 85 86
    /**
     * @fixme much of this duplicates core code, which is very fragile.
     * Should probably be replaced with an extensible mini version of
     * the core registration form.
     */
Evan Prodromou's avatar
Evan Prodromou committed
87 88
    function showContent()
    {
89
        if (!empty($this->message_text)) {
90
            $this->element('div', array('class' => 'error'), $this->message_text);
Evan Prodromou's avatar
Evan Prodromou committed
91 92
            return;
        }
93

94
        $this->elementStart('form', array('method' => 'post',
Evan Prodromou's avatar
Evan Prodromou committed
95
                                          'id' => 'account_connect',
Sarven Capadisli's avatar
Sarven Capadisli committed
96
                                          'class' => 'form_settings',
Evan Prodromou's avatar
Evan Prodromou committed
97
                                          'action' => common_local_url('finishopenidlogin')));
98
        $this->hidden('token', common_session_token());
Sarven Capadisli's avatar
Sarven Capadisli committed
99 100
        $this->elementStart('fieldset', array('id' => 'form_openid_createaccount'));
        $this->element('legend', null,
101
                       _m('Create new account'));
102
        $this->element('p', null,
103
                       _m('Create a new user with this nickname.'));
Sarven Capadisli's avatar
Sarven Capadisli committed
104 105
        $this->elementStart('ul', 'form_data');
        $this->elementStart('li');
106
        $this->input('newname', _m('New nickname'),
Evan Prodromou's avatar
Evan Prodromou committed
107
                     ($this->username) ? $this->username : '',
108
                     _m('1-64 lowercase letters or numbers, no punctuation or spaces'));
Sarven Capadisli's avatar
Sarven Capadisli committed
109 110
        $this->elementEnd('li');
        $this->elementStart('li');
111
        $this->element('input', array('type' => 'checkbox',
112
                                      'id' => 'license',
Sarven Capadisli's avatar
Sarven Capadisli committed
113
                                      'class' => 'checkbox',
114 115
                                      'name' => 'license',
                                      'value' => 'true'));
Sarven Capadisli's avatar
Sarven Capadisli committed
116 117
        $this->elementStart('label', array('for' => 'license',
                                          'class' => 'checkbox'));
118 119 120 121 122 123 124 125 126
        $message = _('My text and files are available under %s ' .
                     'except this private data: password, ' .
                     'email address, IM address, and phone number.');
        $link = '<a href="' .
                htmlspecialchars(common_config('license', 'url')) .
                '">' .
                htmlspecialchars(common_config('license', 'title')) .
                '</a>';
        $this->raw(sprintf(htmlspecialchars($message), $link));
Sarven Capadisli's avatar
Sarven Capadisli committed
127 128 129
        $this->elementEnd('label');
        $this->elementEnd('li');
        $this->elementEnd('ul');
130
        $this->submit('create', _m('Create'));
Sarven Capadisli's avatar
Sarven Capadisli committed
131 132 133 134
        $this->elementEnd('fieldset');

        $this->elementStart('fieldset', array('id' => 'form_openid_createaccount'));
        $this->element('legend', null,
135
                       _m('Connect existing account'));
136
        $this->element('p', null,
137
                       _m('If you already have an account, login with your username and password to connect it to your OpenID.'));
Sarven Capadisli's avatar
Sarven Capadisli committed
138 139
        $this->elementStart('ul', 'form_data');
        $this->elementStart('li');
140
        $this->input('nickname', _m('Existing nickname'));
Sarven Capadisli's avatar
Sarven Capadisli committed
141 142
        $this->elementEnd('li');
        $this->elementStart('li');
143
        $this->password('password', _m('Password'));
Sarven Capadisli's avatar
Sarven Capadisli committed
144 145
        $this->elementEnd('li');
        $this->elementEnd('ul');
146
        $this->submit('connect', _m('Connect'));
Sarven Capadisli's avatar
Sarven Capadisli committed
147
        $this->elementEnd('fieldset');
148
        $this->elementEnd('form');
149 150
    }

Evan Prodromou's avatar
Evan Prodromou committed
151
    function tryLogin()
152
    {
153 154 155 156 157
        $consumer = oid_consumer();

        $response = $consumer->complete(common_local_url('finishopenidlogin'));

        if ($response->status == Auth_OpenID_CANCEL) {
158
            $this->message(_m('OpenID authentication cancelled.'));
159 160 161
            return;
        } else if ($response->status == Auth_OpenID_FAILURE) {
            // Authentication failed; display the error message.
162
            $this->message(sprintf(_m('OpenID authentication failed: %s'), $response->message));
163 164 165 166 167 168 169 170
        } else if ($response->status == Auth_OpenID_SUCCESS) {
            // This means the authentication succeeded; extract the
            // identity URL and Simple Registration data (if it was
            // returned).
            $display = $response->getDisplayIdentifier();
            $canonical = ($response->endpoint->canonicalID) ?
              $response->endpoint->canonicalID : $response->getDisplayIdentifier();

171 172 173
            oid_assert_allowed($display);
            oid_assert_allowed($canonical);

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
            $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);

            if ($sreg_resp) {
                $sreg = $sreg_resp->contents();
            }

            $user = oid_get_user($canonical);

            if ($user) {
                oid_set_last($display);
                # XXX: commented out at @edd's request until better
                # control over how data flows from OpenID provider.
                # oid_update_user($user, $sreg);
                common_set_user($user);
                common_real_login(true);
                if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
                    common_rememberme($user);
                }
192
                unset($_SESSION['openid_rememberme']);
Evan Prodromou's avatar
Evan Prodromou committed
193
                $this->goHome($user->nickname);
194
            } else {
Evan Prodromou's avatar
Evan Prodromou committed
195 196
                $this->saveValues($display, $canonical, $sreg);
                $this->showForm(null, $this->bestNewNickname($display, $sreg));
197 198 199 200
            }
        }
    }

201 202
    function message($msg)
    {
Evan Prodromou's avatar
Evan Prodromou committed
203 204
        $this->message_text = $msg;
        $this->showPage();
205 206
    }

Evan Prodromou's avatar
Evan Prodromou committed
207
    function saveValues($display, $canonical, $sreg)
208
    {
209 210 211 212 213 214
        common_ensure_session();
        $_SESSION['openid_display'] = $display;
        $_SESSION['openid_canonical'] = $canonical;
        $_SESSION['openid_sreg'] = $sreg;
    }

Evan Prodromou's avatar
Evan Prodromou committed
215
    function getSavedValues()
216
    {
217 218 219 220 221
        return array($_SESSION['openid_display'],
                     $_SESSION['openid_canonical'],
                     $_SESSION['openid_sreg']);
    }

Evan Prodromou's avatar
Evan Prodromou committed
222
    function createNewUser()
223
    {
224 225
        # FIXME: save invite code before redirect, and check here

226
        if (common_config('site', 'closed')) {
227
            $this->clientError(_m('Registration not allowed.'));
228 229 230
            return;
        }

231 232 233 234 235
        $invite = null;

        if (common_config('site', 'inviteonly')) {
            $code = $_SESSION['invitecode'];
            if (empty($code)) {
236
                $this->clientError(_m('Registration not allowed.'));
237 238 239 240 241 242
                return;
            }

            $invite = Invitation::staticGet($code);

            if (empty($invite)) {
243
                $this->clientError(_m('Not a valid invitation code.'));
244 245 246 247
                return;
            }
        }

248
        $nickname = $this->trimmed('newname');
Evan Prodromou's avatar
method  
Evan Prodromou committed
249

250 251
        if (!Validate::string($nickname, array('min_length' => 1,
                                               'max_length' => 64,
252
                                               'format' => NICKNAME_FMT))) {
253
            $this->showForm(_m('Nickname must have only lowercase letters and numbers and no spaces.'));
254 255
            return;
        }
256

257
        if (!User::allowed_nickname($nickname)) {
258
            $this->showForm(_m('Nickname not allowed.'));
259 260
            return;
        }
Evan Prodromou's avatar
method  
Evan Prodromou committed
261

262
        if (User::staticGet('nickname', $nickname)) {
263
            $this->showForm(_m('Nickname already in use. Try another one.'));
264 265
            return;
        }
Evan Prodromou's avatar
method  
Evan Prodromou committed
266

Evan Prodromou's avatar
Evan Prodromou committed
267
        list($display, $canonical, $sreg) = $this->getSavedValues();
Evan Prodromou's avatar
method  
Evan Prodromou committed
268

269
        if (!$display || !$canonical) {
270
            $this->serverError(_m('Stored OpenID not found.'));
271 272
            return;
        }
Evan Prodromou's avatar
method  
Evan Prodromou committed
273

274
        # Possible race condition... let's be paranoid
Evan Prodromou's avatar
method  
Evan Prodromou committed
275

276
        $other = oid_get_user($canonical);
Evan Prodromou's avatar
method  
Evan Prodromou committed
277

278
        if ($other) {
279
            $this->serverError(_m('Creating new account for OpenID that already has a user.'));
280 281
            return;
        }
Evan Prodromou's avatar
method  
Evan Prodromou committed
282

283 284
        $location = '';
        if (!empty($sreg['country'])) {
285 286 287 288 289 290 291 292
            if ($sreg['postcode']) {
                # XXX: use postcode to get city and region
                # XXX: also, store postcode somewhere -- it's valuable!
                $location = $sreg['postcode'] . ', ' . $sreg['country'];
            } else {
                $location = $sreg['country'];
            }
        }
Evan Prodromou's avatar
Evan Prodromou committed
293

294
        if (!empty($sreg['fullname']) && mb_strlen($sreg['fullname']) <= 255) {
295
            $fullname = $sreg['fullname'];
296 297
        } else {
            $fullname = '';
298
        }
Evan Prodromou's avatar
Evan Prodromou committed
299

300
        if (!empty($sreg['email']) && Validate::email($sreg['email'], common_config('email', 'check_domain'))) {
301
            $email = $sreg['email'];
302 303
        } else {
            $email = '';
304
        }
Evan Prodromou's avatar
method  
Evan Prodromou committed
305

306 307
        # XXX: add language
        # XXX: add timezone
Evan Prodromou's avatar
Evan Prodromou committed
308

309 310 311 312 313 314 315 316 317 318
        $args = array('nickname' => $nickname,
                      'email' => $email,
                      'fullname' => $fullname,
                      'location' => $location);

        if (!empty($invite)) {
            $args['code'] = $invite->code;
        }

        $user = User::register($args);
Evan Prodromou's avatar
Evan Prodromou committed
319

320
        $result = oid_link_user($user->id, $canonical, $display);
Evan Prodromou's avatar
Evan Prodromou committed
321

322 323 324
        oid_set_last($display);
        common_set_user($user);
        common_real_login(true);
325
        if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
326 327
            common_rememberme($user);
        }
328
        unset($_SESSION['openid_rememberme']);
329 330
        common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
                        303);
331
    }
Evan Prodromou's avatar
method  
Evan Prodromou committed
332

Evan Prodromou's avatar
Evan Prodromou committed
333
    function connectUser()
334
    {
335 336
        $nickname = $this->trimmed('nickname');
        $password = $this->trimmed('password');
Evan Prodromou's avatar
Evan Prodromou committed
337

338
        if (!common_check_user($nickname, $password)) {
339
            $this->showForm(_m('Invalid username or password.'));
340 341
            return;
        }
Evan Prodromou's avatar
Evan Prodromou committed
342

343
        # They're legit!
Evan Prodromou's avatar
method  
Evan Prodromou committed
344

345
        $user = User::staticGet('nickname', $nickname);
Evan Prodromou's avatar
Evan Prodromou committed
346

Evan Prodromou's avatar
Evan Prodromou committed
347
        list($display, $canonical, $sreg) = $this->getSavedValues();
Evan Prodromou's avatar
Evan Prodromou committed
348

349
        if (!$display || !$canonical) {
350
            $this->serverError(_m('Stored OpenID not found.'));
351 352
            return;
        }
Evan Prodromou's avatar
method  
Evan Prodromou committed
353

354
        $result = oid_link_user($user->id, $canonical, $display);
Evan Prodromou's avatar
method  
Evan Prodromou committed
355

356
        if (!$result) {
357
            $this->serverError(_m('Error connecting user to OpenID.'));
358 359
            return;
        }
Evan Prodromou's avatar
method  
Evan Prodromou committed
360

361 362 363 364
        oid_update_user($user, $sreg);
        oid_set_last($display);
        common_set_user($user);
        common_real_login(true);
365
        if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
366 367 368
            common_rememberme($user);
        }
        unset($_SESSION['openid_rememberme']);
Evan Prodromou's avatar
Evan Prodromou committed
369
        $this->goHome($user->nickname);
370 371
    }

Evan Prodromou's avatar
Evan Prodromou committed
372
    function goHome($nickname)
373
    {
374 375
        $url = common_get_returnto();
        if ($url) {
376
            # We don't have to return to it again
Evan Prodromou's avatar
Evan Prodromou committed
377
            common_set_returnto(null);
378
	    $url = common_inject_session($url);
379 380 381 382 383
        } else {
            $url = common_local_url('all',
                                    array('nickname' =>
                                          $nickname));
        }
384
        common_redirect($url, 303);
385 386
    }

Evan Prodromou's avatar
Evan Prodromou committed
387
    function bestNewNickname($display, $sreg)
388
    {
389 390 391

        # Try the passed-in nickname

392
        if (!empty($sreg['nickname'])) {
393
            $nickname = $this->nicknamize($sreg['nickname']);
Evan Prodromou's avatar
Evan Prodromou committed
394
            if ($this->isNewNickname($nickname)) {
395 396 397 398 399 400
                return $nickname;
            }
        }

        # Try the full name

401
        if (!empty($sreg['fullname'])) {
402
            $fullname = $this->nicknamize($sreg['fullname']);
Evan Prodromou's avatar
Evan Prodromou committed
403
            if ($this->isNewNickname($fullname)) {
404 405 406 407 408 409
                return $fullname;
            }
        }

        # Try the URL

Evan Prodromou's avatar
Evan Prodromou committed
410
        $from_url = $this->openidToNickname($display);
411

Evan Prodromou's avatar
Evan Prodromou committed
412
        if ($from_url && $this->isNewNickname($from_url)) {
413 414 415 416 417
            return $from_url;
        }

        # XXX: others?

Evan Prodromou's avatar
Evan Prodromou committed
418
        return null;
419 420
    }

Evan Prodromou's avatar
Evan Prodromou committed
421
    function isNewNickname($str)
422
    {
423 424
        if (!Validate::string($str, array('min_length' => 1,
                                          'max_length' => 64,
425
                                          'format' => NICKNAME_FMT))) {
426 427
            return false;
        }
Evan Prodromou's avatar
Evan Prodromou committed
428
        if (!User::allowed_nickname($str)) {
429 430 431 432 433 434 435 436
            return false;
        }
        if (User::staticGet('nickname', $str)) {
            return false;
        }
        return true;
    }

Evan Prodromou's avatar
Evan Prodromou committed
437
    function openidToNickname($openid)
438
    {
Evan Prodromou's avatar
Evan Prodromou committed
439
        if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
Evan Prodromou's avatar
Evan Prodromou committed
440
            return $this->xriToNickname($openid);
441
        } else {
Evan Prodromou's avatar
Evan Prodromou committed
442
            return $this->urlToNickname($openid);
443 444
        }
    }
Evan Prodromou's avatar
Evan Prodromou committed
445

446
    # We try to use an OpenID URL as a legal StatusNet user name in this order
447 448 449
    # 1. Plain hostname, like http://evanp.myopenid.com/
    # 2. One element in path, like http://profile.typekey.com/EvanProdromou/
    #    or http://getopenid.com/evanprodromou
Evan Prodromou's avatar
Evan Prodromou committed
450

Evan Prodromou's avatar
Evan Prodromou committed
451
    function urlToNickname($openid)
452
    {
453
        return common_url_to_nickname($openid);
454 455
    }

Evan Prodromou's avatar
Evan Prodromou committed
456
    function xriToNickname($xri)
457
    {
Evan Prodromou's avatar
Evan Prodromou committed
458
        $base = $this->xriBase($xri);
459 460

        if (!$base) {
Evan Prodromou's avatar
Evan Prodromou committed
461
            return null;
462 463 464 465 466 467 468 469
        } else {
            # =evan.prodromou
            # or @gratis*evan.prodromou
            $parts = explode('*', substr($base, 1));
            return $this->nicknamize(array_pop($parts));
        }
    }

Evan Prodromou's avatar
Evan Prodromou committed
470
    function xriBase($xri)
471
    {
472 473 474 475 476 477 478 479 480
        if (substr($xri, 0, 6) == 'xri://') {
            return substr($xri, 6);
        } else {
            return $xri;
        }
    }

    # Given a string, try to make it work as a nickname

481 482
    function nicknamize($str)
    {
483
        return common_nicknamize($str);
484
    }
Evan Prodromou's avatar
Evan Prodromou committed
485
}