3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2008, 2009, StatusNet, Inc.
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.
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.
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/>.
20 if (!defined('STATUSNET')) {
24 require_once INSTALLDIR.'/plugins/OpenID/openid.php';
26 class FinishopenidloginAction extends Action
35 if (common_is_real_login()) {
36 // TRANS: Client error message trying to log on with OpenID while already logged on.
37 $this->clientError(_m('Already logged in.'));
38 } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
39 $token = $this->trimmed('token');
40 if (!$token || $token != common_session_token()) {
41 // TRANS: Message given when there is a problem with the user's session token.
42 $this->showForm(_m('There was a problem with your session token. Try again, please.'));
45 if ($this->arg('create')) {
46 if (!$this->boolean('license')) {
47 // TRANS: Message given if user does not agree with the site's license.
48 $this->showForm(_m('You cannot register if you do not agree to the license.'),
49 $this->trimmed('newname'));
52 $this->createNewUser();
53 } else if ($this->arg('connect')) {
56 // TRANS: Messag given on an unknown error.
57 $this->showForm(_m('An unknown error has occured.'),
58 $this->trimmed('newname'));
65 function showPageNotice()
68 $this->element('div', array('class' => 'error'), $this->error);
70 $this->element('div', 'instructions',
71 // TRANS: Instructions given after a first successful logon using OpenID.
72 // TRANS: %s is the site name.
73 sprintf(_m('This is the first time you have 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')));
80 return _m('TITLE','OpenID Account Setup');
83 function showForm($error=null, $username=null)
85 $this->error = $error;
86 $this->username = $username;
92 * @fixme much of this duplicates core code, which is very fragile.
93 * Should probably be replaced with an extensible mini version of
94 * the core registration form.
96 function showContent()
98 if (!empty($this->message_text)) {
99 $this->element('div', array('class' => 'error'), $this->message_text);
103 // We don't recognize this OpenID, so we're going to give the user
104 // two options, each in its own mini-form.
106 // First, they can create a new account using their OpenID auth
107 // info. The profile will be pre-populated with whatever name,
108 // email, and location we can get from the OpenID provider, so
109 // all we ask for is the license confirmation.
110 $this->elementStart('form', array('method' => 'post',
111 'id' => 'account_create',
112 'class' => 'form_settings',
113 'action' => common_local_url('finishopenidlogin')));
114 $this->hidden('token', common_session_token());
115 $this->elementStart('fieldset', array('id' => 'form_openid_createaccount'));
116 $this->element('legend', null,
117 // TRANS: Fieldset legend.
118 _m('Create new account'));
119 $this->element('p', null,
120 // TRANS: Form guide.
121 _m('Create a new user with this nickname.'));
122 $this->elementStart('ul', 'form_data');
124 // Hook point for captcha etc
125 Event::handle('StartRegistrationFormData', array($this));
127 $this->elementStart('li');
128 // TRANS: Field label.
129 $this->input('newname', _m('New nickname'),
130 ($this->username) ? $this->username : '',
131 // TRANS: Field title.
132 _m('1-64 lowercase letters or numbers, no punctuation or spaces.'));
133 $this->elementEnd('li');
134 $this->elementStart('li');
135 // TRANS: Field label.
136 $this->input('email', _m('Email'), $this->getEmail(),
137 // TRANS: Field title.
138 _m('Used only for updates, announcements, '.
139 'and password recovery.'));
140 $this->elementEnd('li');
142 // Hook point for captcha etc
143 Event::handle('EndRegistrationFormData', array($this));
145 $this->elementStart('li');
146 $this->element('input', array('type' => 'checkbox',
148 'class' => 'checkbox',
151 $this->elementStart('label', array('for' => 'license',
152 'class' => 'checkbox'));
153 // TRANS: OpenID plugin link text.
154 // TRANS: %s is a link to a license with the license name as link text.
155 $message = _m('My text and files are available under %s ' .
156 'except this private data: password, ' .
157 'email address, IM address, and phone number.');
158 $link = '<a href="' .
159 htmlspecialchars(common_config('license', 'url')) .
161 htmlspecialchars(common_config('license', 'title')) .
163 $this->raw(sprintf(htmlspecialchars($message), $link));
164 $this->elementEnd('label');
165 $this->elementEnd('li');
166 $this->elementEnd('ul');
167 // TRANS: Button label in form in which to create a new user on the site for an OpenID.
168 $this->submit('create', _m('BUTTON', 'Create'));
169 $this->elementEnd('fieldset');
170 $this->elementEnd('form');
172 // The second option is to attach this OpenID to an existing account
173 // on the local system, which they need to provide a password for.
174 $this->elementStart('form', array('method' => 'post',
175 'id' => 'account_connect',
176 'class' => 'form_settings',
177 'action' => common_local_url('finishopenidlogin')));
178 $this->hidden('token', common_session_token());
179 $this->elementStart('fieldset', array('id' => 'form_openid_createaccount'));
180 $this->element('legend', null,
181 // TRANS: Used as form legend for form in which to connect an OpenID to an existing user on the site.
182 _m('Connect existing account'));
183 $this->element('p', null,
184 // TRANS: User instructions for form in which to connect an OpenID to an existing user on the site.
185 _m('If you already have an account, login with your username and password to connect it to your OpenID.'));
186 $this->elementStart('ul', 'form_data');
187 $this->elementStart('li');
188 // TRANS: Field label in form in which to connect an OpenID to an existing user on the site.
189 $this->input('nickname', _m('Existing nickname'));
190 $this->elementEnd('li');
191 $this->elementStart('li');
192 // TRANS: Field label in form in which to connect an OpenID to an existing user on the site.
193 $this->password('password', _m('Password'));
194 $this->elementEnd('li');
195 $this->elementEnd('ul');
196 // TRANS: Button text in form in which to connect an OpenID to an existing user on the site.
197 $this->submit('connect', _m('BUTTON', 'Connect'));
198 $this->elementEnd('fieldset');
199 $this->elementEnd('form');
203 * Get specified e-mail from the form, or the OpenID sreg info, or the
210 $email = $this->trimmed('email');
211 if (!empty($email)) {
215 // Pull from openid thingy
216 list($display, $canonical, $sreg) = $this->getSavedValues();
217 if (!empty($sreg['email'])) {
218 return $sreg['email'];
221 // Terrible hack for invites...
222 if (common_config('site', 'inviteonly')) {
223 $code = $_SESSION['invitecode'];
225 $invite = Invitation::getKV($code);
227 if ($invite && $invite->address_type == 'email') {
228 return $invite->address;
237 $consumer = oid_consumer();
239 $response = $consumer->complete(common_local_url('finishopenidlogin'));
241 if ($response->status == Auth_OpenID_CANCEL) {
242 // TRANS: Status message in case the response from the OpenID provider is that the logon attempt was cancelled.
243 $this->message(_m('OpenID authentication cancelled.'));
245 } else if ($response->status == Auth_OpenID_FAILURE) {
246 // TRANS: OpenID authentication failed; display the error message. %s is the error message.
247 $this->message(sprintf(_m('OpenID authentication failed: %s.'), $response->message));
248 } else if ($response->status == Auth_OpenID_SUCCESS) {
249 // This means the authentication succeeded; extract the
250 // identity URL and Simple Registration data (if it was
252 $display = $response->getDisplayIdentifier();
253 $canonical = ($response->endpoint->canonicalID) ?
254 $response->endpoint->canonicalID : $response->getDisplayIdentifier();
256 oid_assert_allowed($display);
257 oid_assert_allowed($canonical);
259 $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
262 $sreg = $sreg_resp->contents();
265 // Launchpad teams extension
266 if (!oid_check_teams($response)) {
267 // TRANS: Message displayed when OpenID authentication is aborted.
268 $this->message(_m('OpenID authentication aborted: You are not allowed to login to this site.'));
272 $user = oid_get_user($canonical);
275 oid_set_last($display);
276 // XXX: commented out at @edd's request until better
277 // control over how data flows from OpenID provider.
278 // oid_update_user($user, $sreg);
279 common_set_user($user);
280 common_real_login(true);
281 if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
282 common_rememberme($user);
284 unset($_SESSION['openid_rememberme']);
285 $this->goHome($user->nickname);
287 $this->saveValues($display, $canonical, $sreg);
288 $this->showForm(null, $this->bestNewNickname($display, $sreg));
293 function message($msg)
295 $this->message_text = $msg;
299 function saveValues($display, $canonical, $sreg)
301 common_ensure_session();
302 $_SESSION['openid_display'] = $display;
303 $_SESSION['openid_canonical'] = $canonical;
304 $_SESSION['openid_sreg'] = $sreg;
307 function getSavedValues()
309 return array($_SESSION['openid_display'],
310 $_SESSION['openid_canonical'],
311 $_SESSION['openid_sreg']);
314 function createNewUser()
316 // FIXME: save invite code before redirect, and check here
318 if (!Event::handle('StartRegistrationTry', array($this))) {
322 if (common_config('site', 'closed')) {
323 // TRANS: OpenID plugin message. No new user registration is allowed on the site.
324 $this->clientError(_m('Registration not allowed.'));
329 if (common_config('site', 'inviteonly')) {
330 $code = $_SESSION['invitecode'];
332 // TRANS: OpenID plugin message. No new user registration is allowed on the site without an invitation code, and none was provided.
333 $this->clientError(_m('Registration not allowed.'));
336 $invite = Invitation::getKV($code);
338 if (empty($invite)) {
339 // TRANS: OpenID plugin message. No new user registration is allowed on the site without an invitation code, and the one provided was not valid.
340 $this->clientError(_m('Not a valid invitation code.'));
345 $nickname = Nickname::normalize($this->trimmed('newname'), true);
346 } catch (NicknameException $e) {
347 $this->showForm($e->getMessage());
351 list($display, $canonical, $sreg) = $this->getSavedValues();
353 if (!$display || !$canonical) {
354 // TRANS: OpenID plugin server error. A stored OpenID cannot be retrieved.
355 $this->serverError(_m('Stored OpenID not found.'));
358 // Possible race condition... let's be paranoid
360 $other = oid_get_user($canonical);
363 // TRANS: OpenID plugin server error.
364 $this->serverError(_m('Creating new account for OpenID that already has a user.'));
367 Event::handle('StartOpenIDCreateNewUser', array($canonical, &$sreg));
370 if (!empty($sreg['country'])) {
371 if ($sreg['postcode']) {
372 // XXX: use postcode to get city and region
373 // XXX: also, store postcode somewhere -- it's valuable!
374 $location = $sreg['postcode'] . ', ' . $sreg['country'];
376 $location = $sreg['country'];
380 if (!empty($sreg['fullname']) && mb_strlen($sreg['fullname']) <= 255) {
381 $fullname = $sreg['fullname'];
386 $email = $this->getEmail();
391 $args = array('nickname' => $nickname,
393 'fullname' => $fullname,
394 'location' => $location);
396 if (!empty($invite)) {
397 $args['code'] = $invite->code;
400 $user = User::register($args);
402 $result = oid_link_user($user->id, $canonical, $display);
404 Event::handle('EndOpenIDCreateNewUser', array($user, $canonical, $sreg));
406 oid_set_last($display);
407 common_set_user($user);
408 common_real_login(true);
409 if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
410 common_rememberme($user);
412 unset($_SESSION['openid_rememberme']);
414 Event::handle('EndRegistrationTry', array($this));
416 common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), 303);
419 function connectUser()
421 $nickname = $this->trimmed('nickname');
422 $password = $this->trimmed('password');
424 if (!common_check_user($nickname, $password)) {
425 // TRANS: OpenID plugin message.
426 $this->showForm(_m('Invalid username or password.'));
432 $user = User::getKV('nickname', $nickname);
434 list($display, $canonical, $sreg) = $this->getSavedValues();
436 if (!$display || !$canonical) {
437 // TRANS: OpenID plugin server error. A stored OpenID cannot be found.
438 $this->serverError(_m('Stored OpenID not found.'));
441 $result = oid_link_user($user->id, $canonical, $display);
444 // TRANS: OpenID plugin server error. The user or user profile could not be saved.
445 $this->serverError(_m('Error connecting user to OpenID.'));
448 if (Event::handle('StartOpenIDUpdateUser', array($user, $canonical, &$sreg))) {
449 oid_update_user($user, $sreg);
451 Event::handle('EndOpenIDUpdateUser', array($user, $canonical, $sreg));
453 oid_set_last($display);
454 common_set_user($user);
455 common_real_login(true);
456 if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
457 common_rememberme($user);
459 unset($_SESSION['openid_rememberme']);
460 $this->goHome($user->nickname);
463 function goHome($nickname)
465 $url = common_get_returnto();
467 // We don't have to return to it again
468 common_set_returnto(null);
469 $url = common_inject_session($url);
471 $url = common_local_url('all',
475 common_redirect($url, 303);
478 function bestNewNickname($display, $sreg)
480 // Try the passed-in nickname
482 if (!empty($sreg['nickname'])) {
483 $nickname = common_nicknamize($sreg['nickname']);
484 if (Nickname::isValid($nickname, true)) {
491 if (!empty($sreg['fullname'])) {
492 $fullname = common_nicknamize($sreg['fullname']);
493 if (Nickname::isValid($fullname, true)) {
500 $from_url = $this->openidToNickname($display);
502 if ($from_url && Nickname::isValid($from_url, true)) {
511 function openidToNickname($openid)
513 if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
514 return $this->xriToNickname($openid);
516 return $this->urlToNickname($openid);
520 // We try to use an OpenID URL as a legal StatusNet user name in this order
521 // 1. Plain hostname, like http://evanp.myopenid.com/
522 // 2. One element in path, like http://profile.typekey.com/EvanProdromou/
523 // or http://getopenid.com/evanprodromou
524 function urlToNickname($openid)
526 return common_url_to_nickname($openid);
529 function xriToNickname($xri)
531 $base = $this->xriBase($xri);
537 // or @gratis*evan.prodromou
538 $parts = explode('*', substr($base, 1));
539 return common_nicknamize(array_pop($parts));
543 function xriBase($xri)
545 if (substr($xri, 0, 6) == 'xri://') {
546 return substr($xri, 6);