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

finishopenidlogin.php 12.1 KB
Newer Older
Evan Prodromou's avatar
Evan Prodromou committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
<?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()) {
29
			common_user_error(_('Already logged in.'));
Evan Prodromou's avatar
Evan Prodromou committed
30
		} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
31
			if ($this->arg('create')) {
32
				if (!$this->boolean('license')) {
33
					$this->show_form(_('You can\'t register if you don\'t agree to the license.'),
34 35 36
									 $this->trimmed('newname'));
					return;
				}
Evan Prodromou's avatar
Evan Prodromou committed
37
				$this->create_new_user();
38
			} else if ($this->arg('connect')) {
Evan Prodromou's avatar
Evan Prodromou committed
39 40
				$this->connect_user();
			} else {
Evan Prodromou's avatar
Evan Prodromou committed
41
				common_debug(print_r($this->args, true), __FILE__);
42
				$this->show_form(_('Something weird happened.'),
Evan Prodromou's avatar
Evan Prodromou committed
43 44 45 46 47 48 49
								 $this->trimmed('newname'));
			}
		} else {
			$this->try_login();
		}
	}

50
	function show_top($error=NULL) {
Evan Prodromou's avatar
Evan Prodromou committed
51 52 53 54 55
		if ($error) {
			common_element('div', array('class' => 'error'), $error);
		} else {
			global $config;
			common_element('div', 'instructions',
56 57 58 59 60
						   sprintf(_('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.'
                                    ), $config['site']['name']));
Evan Prodromou's avatar
method  
Evan Prodromou committed
61
		}
62
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
63

64 65 66 67
	function show_form($error=NULL, $username=NULL) {
		common_show_header(_t('OpenID Account Setup'), NULL, $error,
						   array($this, 'show_top'));

Evan Prodromou's avatar
method  
Evan Prodromou committed
68
		common_element_start('form', array('method' => 'post',
Evan Prodromou's avatar
Evan Prodromou committed
69 70 71
										   'id' => 'account_connect',
										   'action' => common_local_url('finishopenidlogin')));
		common_element('h2', NULL,
72
					   _('Create new account'));
Evan Prodromou's avatar
Evan Prodromou committed
73
		common_element('p', NULL,
74 75
					   _('Create a new user with this nickname.'));
		common_input('newname', _('New nickname'),
Evan Prodromou's avatar
Evan Prodromou committed
76
					 ($username) ? $username : '',
77
					 _('1-64 lowercase letters or numbers, no punctuation or spaces'));
78 79 80 81 82
		common_element_start('p');
		common_element('input', array('type' => 'checkbox',
									  'id' => 'license',
									  'name' => 'license',
									  'value' => 'true'));
83
		common_text(_('My text and files are available under '));
84 85
		common_element('a', array(href => common_config('license', 'url')),
					   common_config('license', 'title'));
86
		common_text(_(' except this private data: password, email address, IM address, phone number.'));
87
		common_element_end('p');
88
		common_submit('create', _('Create'));
Evan Prodromou's avatar
Evan Prodromou committed
89
		common_element('h2', NULL,
90
					   _('Connect existing account'));
Evan Prodromou's avatar
Evan Prodromou committed
91
		common_element('p', NULL,
92
					   _('If you already have an account, login with your username and password '.
Evan Prodromou's avatar
Evan Prodromou committed
93
						  'to connect it to your OpenID.'));
94 95 96
		common_input('nickname', _('Existing nickname'));
		common_password('password', _('Password'));
		common_submit('connect', _('Connect'));
Evan Prodromou's avatar
Evan Prodromou committed
97 98 99 100 101
		common_element_end('form');
		common_show_footer();
	}

	function try_login() {
Evan Prodromou's avatar
method  
Evan Prodromou committed
102

Evan Prodromou's avatar
Evan Prodromou committed
103 104 105 106 107
		$consumer = oid_consumer();

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

		if ($response->status == Auth_OpenID_CANCEL) {
108
			$this->message(_('OpenID authentication cancelled.'));
Evan Prodromou's avatar
Evan Prodromou committed
109 110 111
			return;
		} else if ($response->status == Auth_OpenID_FAILURE) {
			// Authentication failed; display the error message.
112
			$this->message(sprintf(_('OpenID authentication failed: %s'), $response->message));
Evan Prodromou's avatar
Evan Prodromou committed
113 114 115 116 117 118 119 120 121 122 123 124 125 126
		} 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();
			}

Evan Prodromou's avatar
Evan Prodromou committed
127
			$user = oid_get_user($canonical);
Evan Prodromou's avatar
method  
Evan Prodromou committed
128

Evan Prodromou's avatar
Evan Prodromou committed
129
			if ($user) {
Evan Prodromou's avatar
Evan Prodromou committed
130
				oid_set_last($display);
131 132 133
				# XXX: commented out at @edd's request until better
				# control over how data flows from OpenID provider.
				# oid_update_user($user, $sreg);
Evan Prodromou's avatar
Evan Prodromou committed
134
				common_set_user($user->nickname);
135
				common_real_login(true);
Evan Prodromou's avatar
Evan Prodromou committed
136 137 138 139 140 141 142 143 144
				$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) {
145
		common_show_header(_('OpenID Login'));
Evan Prodromou's avatar
Evan Prodromou committed
146 147 148
		common_element('p', NULL, $msg);
		common_show_footer();
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
149

Evan Prodromou's avatar
Evan Prodromou committed
150 151 152
	function save_values($display, $canonical, $sreg) {
		common_ensure_session();
		$_SESSION['openid_display'] = $display;
Evan Prodromou's avatar
method  
Evan Prodromou committed
153 154
		$_SESSION['openid_canonical'] = $canonical;
		$_SESSION['openid_sreg'] = $sreg;
Evan Prodromou's avatar
Evan Prodromou committed
155 156
	}

157
	function get_saved_values() {
Evan Prodromou's avatar
Evan Prodromou committed
158 159 160 161
		return array($_SESSION['openid_display'],
					 $_SESSION['openid_canonical'],
					 $_SESSION['openid_sreg']);
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
162

Evan Prodromou's avatar
Evan Prodromou committed
163
	function create_new_user() {
Evan Prodromou's avatar
method  
Evan Prodromou committed
164

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

Evan Prodromou's avatar
Evan Prodromou committed
167 168 169
		if (!Validate::string($nickname, array('min_length' => 1,
											   'max_length' => 64,
											   'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
170
			$this->show_form(_('Nickname must have only letters and numbers and no spaces.'));
Evan Prodromou's avatar
Evan Prodromou committed
171 172
			return;
		}
173 174

		if (!User::allowed_nickname($nickname)) {
175
			$this->show_form(_('Nickname not allowed.'));
176 177
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
178

Evan Prodromou's avatar
Evan Prodromou committed
179
		if (User::staticGet('nickname', $nickname)) {
180
			$this->show_form(_('Nickname already in use. Try another one.'));
Evan Prodromou's avatar
Evan Prodromou committed
181 182
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
183

Evan Prodromou's avatar
Evan Prodromou committed
184
		list($display, $canonical, $sreg) = $this->get_saved_values();
Evan Prodromou's avatar
method  
Evan Prodromou committed
185

Evan Prodromou's avatar
Evan Prodromou committed
186
		if (!$display || !$canonical) {
187
			common_server_error(_('Stored OpenID not found.'));
Evan Prodromou's avatar
Evan Prodromou committed
188 189
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
190

Evan Prodromou's avatar
Evan Prodromou committed
191
		# Possible race condition... let's be paranoid
Evan Prodromou's avatar
method  
Evan Prodromou committed
192

Evan Prodromou's avatar
Evan Prodromou committed
193
		$other = oid_get_user($canonical);
Evan Prodromou's avatar
method  
Evan Prodromou committed
194

Evan Prodromou's avatar
Evan Prodromou committed
195
		if ($other) {
196
			common_server_error(_('Creating new account for OpenID that already has a user.'));
Evan Prodromou's avatar
Evan Prodromou committed
197 198
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
199

Evan Prodromou's avatar
Evan Prodromou committed
200
		$profile = new Profile();
Evan Prodromou's avatar
method  
Evan Prodromou committed
201

Evan Prodromou's avatar
Evan Prodromou committed
202
		$profile->nickname = $nickname;
Evan Prodromou's avatar
method  
Evan Prodromou committed
203

Evan Prodromou's avatar
Evan Prodromou committed
204 205 206
		if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
			$profile->fullname = $sreg['fullname'];
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
207

Evan Prodromou's avatar
Evan Prodromou committed
208 209 210 211 212 213 214 215 216 217 218 219
		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
Evan Prodromou's avatar
method  
Evan Prodromou committed
220

221
		$profile->profileurl = common_profile_url($nickname);
Evan Prodromou's avatar
method  
Evan Prodromou committed
222

Evan Prodromou's avatar
Evan Prodromou committed
223
		$profile->created = DB_DataObject_Cast::dateTime(); # current time
Evan Prodromou's avatar
method  
Evan Prodromou committed
224

Evan Prodromou's avatar
Evan Prodromou committed
225 226
		$id = $profile->insert();
		if (!$id) {
227
			common_server_error(_('Error saving the profile.'));
Evan Prodromou's avatar
Evan Prodromou committed
228 229
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
230

Evan Prodromou's avatar
Evan Prodromou committed
231 232 233
		$user = new User();
		$user->id = $id;
		$user->nickname = $nickname;
234
		$user->uri = common_user_uri($user);
Evan Prodromou's avatar
method  
Evan Prodromou committed
235

Evan Prodromou's avatar
Evan Prodromou committed
236 237 238
		if ($sreg['email'] && Validate::email($sreg['email'], true)) {
			$user->email = $sreg['email'];
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
239

Evan Prodromou's avatar
Evan Prodromou committed
240
		$user->created = DB_DataObject_Cast::dateTime(); # current time
Evan Prodromou's avatar
method  
Evan Prodromou committed
241

Evan Prodromou's avatar
Evan Prodromou committed
242
		$result = $user->insert();
Evan Prodromou's avatar
method  
Evan Prodromou committed
243

Evan Prodromou's avatar
Evan Prodromou committed
244 245 246 247 248
		if (!$result) {
			# Try to clean up...
			$profile->delete();
		}

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

251
		if (!$result) {
Evan Prodromou's avatar
Evan Prodromou committed
252 253 254 255
			# Try to clean up...
			$user->delete();
			$profile->delete();
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
256

Evan Prodromou's avatar
Evan Prodromou committed
257
		oid_set_last($display);
Evan Prodromou's avatar
Evan Prodromou committed
258
		common_set_user($user->nickname);
259
		common_real_login(true);
Evan Prodromou's avatar
Evan Prodromou committed
260
		common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)));
Evan Prodromou's avatar
Evan Prodromou committed
261
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
262

Evan Prodromou's avatar
Evan Prodromou committed
263
	function connect_user() {
Evan Prodromou's avatar
method  
Evan Prodromou committed
264

Evan Prodromou's avatar
Evan Prodromou committed
265 266 267 268
		$nickname = $this->trimmed('nickname');
		$password = $this->trimmed('password');

		if (!common_check_user($nickname, $password)) {
269
			$this->show_form(_('Invalid username or password.'));
Evan Prodromou's avatar
Evan Prodromou committed
270 271 272 273
			return;
		}

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

Evan Prodromou's avatar
Evan Prodromou committed
275 276 277 278 279
		$user = User::staticGet('nickname', $nickname);

		list($display, $canonical, $sreg) = $this->get_saved_values();

		if (!$display || !$canonical) {
280
			common_server_error(_('Stored OpenID not found.'));
Evan Prodromou's avatar
Evan Prodromou committed
281 282
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
283

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

286
		if (!$result) {
287
			common_server_error(_('Error connecting user to OpenID.'));
Evan Prodromou's avatar
Evan Prodromou committed
288 289
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
290

291
		oid_update_user($user, $sreg);
Evan Prodromou's avatar
Evan Prodromou committed
292
		oid_set_last($display);
Evan Prodromou's avatar
Evan Prodromou committed
293
		common_set_user($user->nickname);
294
		common_real_login(true);
Evan Prodromou's avatar
Evan Prodromou committed
295 296
		$this->go_home($user->nickname);
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
297

Evan Prodromou's avatar
Evan Prodromou committed
298 299 300 301 302 303 304 305 306 307 308 309
	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);
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
310

Evan Prodromou's avatar
Evan Prodromou committed
311
	function best_new_nickname($display, $sreg) {
Evan Prodromou's avatar
method  
Evan Prodromou committed
312

Evan Prodromou's avatar
Evan Prodromou committed
313
		# Try the passed-in nickname
Evan Prodromou's avatar
Evan Prodromou committed
314 315 316 317 318 319 320


		if ($sreg['nickname']) {
			$nickname = $this->nicknamize($sreg['nickname']);
			if ($this->is_new_nickname($nickname)) {
				return $nickname;
			}
Evan Prodromou's avatar
Evan Prodromou committed
321 322 323 324 325 326 327 328 329 330
		}

		# Try the full name

		if ($sreg['fullname']) {
			$fullname = $this->nicknamize($sreg['fullname']);
			if ($this->is_new_nickname($fullname)) {
				return $fullname;
			}
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
331

Evan Prodromou's avatar
Evan Prodromou committed
332
		# Try the URL
Evan Prodromou's avatar
method  
Evan Prodromou committed
333

Evan Prodromou's avatar
Evan Prodromou committed
334
		$from_url = $this->openid_to_nickname($display);
Evan Prodromou's avatar
method  
Evan Prodromou committed
335

Evan Prodromou's avatar
Evan Prodromou committed
336 337 338 339 340 341 342 343 344 345 346 347 348 349
		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;
Evan Prodromou's avatar
method  
Evan Prodromou committed
350
		}
351
	if (!User::allowed_nickname($str)) {
352 353
			return false;
		}
Evan Prodromou's avatar
Evan Prodromou committed
354 355 356 357 358
		if (User::staticGet('nickname', $str)) {
			return false;
		}
		return true;
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
359

Evan Prodromou's avatar
Evan Prodromou committed
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
	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));
		}
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
431

Evan Prodromou's avatar
Evan Prodromou committed
432 433 434 435 436 437 438 439 440
	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
Evan Prodromou's avatar
method  
Evan Prodromou committed
441

Evan Prodromou's avatar
Evan Prodromou committed
442 443 444 445 446
	function nicknamize($str) {
		$str = preg_replace('/\W/', '', $str);
		return strtolower($str);
	}
}