gnu-social/actions/finishopenidlogin.php
Evan Prodromou 15a09c5d69 change from using tag uris to http urls for identifiers
Weirdly, I got in an argument with Tim Berners-Lee in #swig about the
tag URIs I was using in FOAF documents. Eventually, I was convinced
that it's a better thing to use HTTP URLs instead. So, now we have
HTTP URLs.

The tricky thing was for users. Since they can change their names, we
can't use their profile URL, since it includes the name. Instead, I
made up a new action, which simply redirects from a user ID to their
current profile URL. This should be sufficiently long-term.

darcs-hash:20080620071700-84dde-c6145243dc45dd2dff621aff421375d05796057e.gz
2008-06-20 03:17:00 -04:00

434 lines
12 KiB
PHP

<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
*
* 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/>.
*/
if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/openid.php');
class FinishopenidloginAction extends Action {
function handle($args) {
parent::handle($args);
if (common_logged_in()) {
common_user_error(_t('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('create')) {
if (!$this->boolean('license')) {
$this->show_form(_t('You can\'t register if you don\'t agree to the license.'),
$this->trimmed('newname'));
return;
}
$this->create_new_user();
} else if ($this->arg('connect')) {
$this->connect_user();
} else {
common_debug(print_r($this->args, true), __FILE__);
$this->show_form(_t('Something weird happened.'),
$this->trimmed('newname'));
}
} else {
$this->try_login();
}
}
function show_top($error=NULL) {
if ($error) {
common_element('div', array('class' => 'error'), $error);
} else {
global $config;
common_element('div', 'instructions',
_t('This is the first time you\'ve logged into ') .
$config['site']['name'] .
_t(' 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.'));
}
}
function show_form($error=NULL, $username=NULL) {
common_show_header(_t('OpenID Account Setup'), NULL, $error,
array($this, 'show_top'));
common_element_start('form', array('method' => 'POST',
'id' => 'account_connect',
'action' => common_local_url('finishopenidlogin')));
common_element('h2', NULL,
'Create new account');
common_element('p', NULL,
_t('Create a new user with this nickname.'));
common_input('newname', _t('New nickname'),
($username) ? $username : '',
_t('1-64 lowercase letters or numbers, no punctuation or spaces'));
common_element_start('p');
common_element('input', array('type' => 'checkbox',
'id' => 'license',
'name' => 'license',
'value' => 'true'));
common_text(_t('My text and files are available under '));
common_element('a', array(href => common_config('license', 'url')),
common_config('license', 'title'));
common_text(_t(' except this private data: password, email address, IM address, phone number.'));
common_element_end('p');
common_submit('create', _t('Create'));
common_element('h2', NULL,
'Connect existing account');
common_element('p', NULL,
_t('If you already have an account, login with your username and password '.
'to connect it to your OpenID.'));
common_input('nickname', _t('Existing nickname'));
common_password('password', _t('Password'));
common_submit('connect', _t('Connect'));
common_element_end('form');
common_show_footer();
}
function try_login() {
$consumer = oid_consumer();
$response = $consumer->complete(common_local_url('finishopenidlogin'));
if ($response->status == Auth_OpenID_CANCEL) {
$this->message(_t('OpenID authentication cancelled.'));
return;
} else if ($response->status == Auth_OpenID_FAILURE) {
// Authentication failed; display the error message.
$this->message(_t('OpenID authentication failed: ') . $response->message);
} 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();
$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);
oid_update_user($user, $sreg);
common_set_user($user->nickname);
$this->go_home($user->nickname);
} else {
$this->save_values($display, $canonical, $sreg);
$this->show_form(NULL, $this->best_new_nickname($display, $sreg));
}
}
}
function message($msg) {
common_show_header(_t('OpenID Login'));
common_element('p', NULL, $msg);
common_show_footer();
}
function save_values($display, $canonical, $sreg) {
common_ensure_session();
$_SESSION['openid_display'] = $display;
$_SESSION['openid_canonical'] = $canonical;
$_SESSION['openid_sreg'] = $sreg;
}
function get_saved_values() {
return array($_SESSION['openid_display'],
$_SESSION['openid_canonical'],
$_SESSION['openid_sreg']);
}
function create_new_user() {
$nickname = $this->trimmed('newname');
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
$this->show_form(_t('Nickname must have only letters and numbers and no spaces.'));
return;
}
if (User::staticGet('nickname', $nickname)) {
$this->show_form(_t('Nickname already in use. Try another one.'));
return;
}
list($display, $canonical, $sreg) = $this->get_saved_values();
if (!$display || !$canonical) {
common_server_error(_t('Stored OpenID not found.'));
return;
}
# Possible race condition... let's be paranoid
$other = oid_get_user($canonical);
if ($other) {
common_server_error(_t('Creating new account for OpenID that already has a user.'));
return;
}
$profile = new Profile();
$profile->nickname = $nickname;
if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
$profile->fullname = $sreg['fullname'];
}
if ($sreg['country']) {
if ($sreg['postcode']) {
# XXX: use postcode to get city and region
# XXX: also, store postcode somewhere -- it's valuable!
$profile->location = $sreg['postcode'] . ', ' . $sreg['country'];
} else {
$profile->location = $sreg['country'];
}
}
# XXX save language if it's passed
# XXX save timezone if it's passed
$profile->profileurl = common_profile_url($nickname);
$profile->created = DB_DataObject_Cast::dateTime(); # current time
$id = $profile->insert();
if (!$id) {
common_server_error(_t('Error saving the profile.'));
return;
}
$user = new User();
$user->id = $id;
$user->nickname = $nickname;
$user->uri = common_user_uri($user);
if ($sreg['email'] && Validate::email($sreg['email'], true)) {
$user->email = $sreg['email'];
}
$user->created = DB_DataObject_Cast::dateTime(); # current time
$result = $user->insert();
if (!$result) {
# Try to clean up...
$profile->delete();
}
$result = oid_link_user($user->id, $canonical, $display);
if (!$result) {
# Try to clean up...
$user->delete();
$profile->delete();
}
oid_set_last($display);
common_set_user($user->nickname);
common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)));
}
function connect_user() {
$nickname = $this->trimmed('nickname');
$password = $this->trimmed('password');
if (!common_check_user($nickname, $password)) {
$this->show_form(_t('Invalid username or password.'));
return;
}
# They're legit!
$user = User::staticGet('nickname', $nickname);
list($display, $canonical, $sreg) = $this->get_saved_values();
if (!$display || !$canonical) {
common_server_error(_t('Stored OpenID not found.'));
return;
}
$result = oid_link_user($user->id, $canonical, $display);
if (!$result) {
common_server_error(_t('Error connecting user to OpenID.'));
return;
}
oid_update_user($user, $sreg);
oid_set_last($display);
common_set_user($user->nickname);
$this->go_home($user->nickname);
}
function go_home($nickname) {
$url = common_get_returnto();
if ($url) {
# We don't have to return to it again
common_set_returnto(NULL);
} else {
$url = common_local_url('all',
array('nickname' =>
$nickname));
}
common_redirect($url);
}
function best_new_nickname($display, $sreg) {
# Try the passed-in nickname
if ($sreg['nickname']) {
$nickname = $this->nicknamize($sreg['nickname']);
if ($this->is_new_nickname($nickname)) {
return $nickname;
}
}
# Try the full name
if ($sreg['fullname']) {
$fullname = $this->nicknamize($sreg['fullname']);
if ($this->is_new_nickname($fullname)) {
return $fullname;
}
}
# Try the URL
$from_url = $this->openid_to_nickname($display);
if ($from_url && $this->is_new_nickname($from_url)) {
return $from_url;
}
# XXX: others?
return NULL;
}
function is_new_nickname($str) {
if (!Validate::string($str, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
return false;
}
if (User::staticGet('nickname', $str)) {
return false;
}
return true;
}
function openid_to_nickname($openid) {
if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
return $this->xri_to_nickname($openid);
} else {
return $this->url_to_nickname($openid);
}
}
# We try to use an OpenID URL as a legal Laconica user name in this order
# 1. Plain hostname, like http://evanp.myopenid.com/
# 2. One element in path, like http://profile.typekey.com/EvanProdromou/
# or http://getopenid.com/evanprodromou
function url_to_nickname($openid) {
static $bad = array('query', 'user', 'password', 'port', 'fragment');
$parts = parse_url($openid);
# If any of these parts exist, this won't work
foreach ($bad as $badpart) {
if (array_key_exists($badpart, $parts)) {
return NULL;
}
}
# We just have host and/or path
# If it's just a host...
if (array_key_exists('host', $parts) &&
(!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
{
$hostparts = explode('.', $parts['host']);
# Try to catch common idiom of nickname.service.tld
if ((count($hostparts) > 2) &&
(strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
(strcmp($hostparts[0], 'www') != 0))
{
return $this->nicknamize($hostparts[0]);
} else {
# Do the whole hostname
return $this->nicknamize($parts['host']);
}
} else {
if (array_key_exists('path', $parts)) {
# Strip starting, ending slashes
$path = preg_replace('@/$@', '', $parts['path']);
$path = preg_replace('@^/@', '', $path);
if (strpos($path, '/') === false) {
return $this->nicknamize($path);
}
}
}
return NULL;
}
function xri_to_nickname($xri) {
$base = $this->xri_base($xri);
if (!$base) {
return NULL;
} else {
# =evan.prodromou
# or @gratis*evan.prodromou
$parts = explode('*', substr($base, 1));
return $this->nicknamize(array_pop($parts));
}
}
function xri_base($xri) {
if (substr($xri, 0, 6) == 'xri://') {
return substr($xri, 6);
} else {
return $xri;
}
}
# Given a string, try to make it work as a nickname
function nicknamize($str) {
$str = preg_replace('/\W/', '', $str);
return strtolower($str);
}
}