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

finishopenidlogin.php 12.3 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 32 33 34 35
			$token = $this->trimmed('token');
			if (!$token || $token != common_session_token()) {
				$this->show_form(_('There was a problem with your session token. Try again, please.'));
				return;
			}
36
			if ($this->arg('create')) {
37
				if (!$this->boolean('license')) {
38
					$this->show_form(_('You can\'t register if you don\'t agree to the license.'),
39 40 41
									 $this->trimmed('newname'));
					return;
				}
Evan Prodromou's avatar
Evan Prodromou committed
42
				$this->create_new_user();
43
			} else if ($this->arg('connect')) {
Evan Prodromou's avatar
Evan Prodromou committed
44 45
				$this->connect_user();
			} else {
Evan Prodromou's avatar
Evan Prodromou committed
46
				common_debug(print_r($this->args, true), __FILE__);
47
				$this->show_form(_('Something weird happened.'),
Evan Prodromou's avatar
Evan Prodromou committed
48 49 50 51 52 53 54
								 $this->trimmed('newname'));
			}
		} else {
			$this->try_login();
		}
	}

55
	function show_top($error=NULL) {
Evan Prodromou's avatar
Evan Prodromou committed
56 57 58 59 60
		if ($error) {
			common_element('div', array('class' => 'error'), $error);
		} else {
			global $config;
			common_element('div', 'instructions',
61
						   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
62
		}
63
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
64

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

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

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

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

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

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

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

Evan Prodromou's avatar
Evan Prodromou committed
155 156 157
	function save_values($display, $canonical, $sreg) {
		common_ensure_session();
		$_SESSION['openid_display'] = $display;
Evan Prodromou's avatar
method  
Evan Prodromou committed
158 159
		$_SESSION['openid_canonical'] = $canonical;
		$_SESSION['openid_sreg'] = $sreg;
Evan Prodromou's avatar
Evan Prodromou committed
160 161
	}

162
	function get_saved_values() {
Evan Prodromou's avatar
Evan Prodromou committed
163 164 165 166
		return array($_SESSION['openid_display'],
					 $_SESSION['openid_canonical'],
					 $_SESSION['openid_sreg']);
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
167

Evan Prodromou's avatar
Evan Prodromou committed
168
	function create_new_user() {
Evan Prodromou's avatar
method  
Evan Prodromou committed
169

170 171 172 173 174 175 176
        # FIXME: save invite code before redirect, and check here

		if (common_config('site', 'closed') || common_config('site', 'inviteonly')) {
			common_user_error(_('Registration not allowed.'));
            return;
        }

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

Evan Prodromou's avatar
Evan Prodromou committed
179 180 181
		if (!Validate::string($nickname, array('min_length' => 1,
											   'max_length' => 64,
											   'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
182
			$this->show_form(_('Nickname must have only lowercase letters and numbers and no spaces.'));
Evan Prodromou's avatar
Evan Prodromou committed
183 184
			return;
		}
185 186

		if (!User::allowed_nickname($nickname)) {
187
			$this->show_form(_('Nickname not allowed.'));
188 189
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
190

Evan Prodromou's avatar
Evan Prodromou committed
191
		if (User::staticGet('nickname', $nickname)) {
192
			$this->show_form(_('Nickname already in use. Try another one.'));
Evan Prodromou's avatar
Evan Prodromou committed
193 194
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
195

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

Evan Prodromou's avatar
Evan Prodromou committed
198
		if (!$display || !$canonical) {
199
			common_server_error(_('Stored OpenID not found.'));
Evan Prodromou's avatar
Evan Prodromou committed
200 201
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
202

Evan Prodromou's avatar
Evan Prodromou committed
203
		# Possible race condition... let's be paranoid
Evan Prodromou's avatar
method  
Evan Prodromou committed
204

Evan Prodromou's avatar
Evan Prodromou committed
205
		$other = oid_get_user($canonical);
Evan Prodromou's avatar
method  
Evan Prodromou committed
206

Evan Prodromou's avatar
Evan Prodromou committed
207
		if ($other) {
208
			common_server_error(_('Creating new account for OpenID that already has a user.'));
Evan Prodromou's avatar
Evan Prodromou committed
209 210
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
211

Evan Prodromou's avatar
Evan Prodromou committed
212 213 214 215
		if ($sreg['country']) {
			if ($sreg['postcode']) {
				# XXX: use postcode to get city and region
				# XXX: also, store postcode somewhere -- it's valuable!
216
				$location = $sreg['postcode'] . ', ' . $sreg['country'];
Evan Prodromou's avatar
Evan Prodromou committed
217
			} else {
218
				$location = $sreg['country'];
Evan Prodromou's avatar
Evan Prodromou committed
219 220
			}
		}
Evan Prodromou's avatar
Evan Prodromou committed
221

222 223
		if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
			$fullname = $sreg['fullname'];
Evan Prodromou's avatar
Evan Prodromou committed
224
		}
Evan Prodromou's avatar
Evan Prodromou committed
225

Evan Prodromou's avatar
Evan Prodromou committed
226
		if ($sreg['email'] && Validate::email($sreg['email'], true)) {
227
			$email = $sreg['email'];
Evan Prodromou's avatar
Evan Prodromou committed
228
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
229

230 231
		# XXX: add language
		# XXX: add timezone
Evan Prodromou's avatar
Evan Prodromou committed
232 233

		$user = User::register(array('nickname' => $nickname,
234
									 'email' => $email,
Evan Prodromou's avatar
Evan Prodromou committed
235
									 'fullname' => $fullname,
236
									 'location' => $location));
Evan Prodromou's avatar
Evan Prodromou committed
237

238
		$result = oid_link_user($user->id, $canonical, $display);
Evan Prodromou's avatar
Evan Prodromou committed
239 240 241

		oid_set_last($display);
		common_set_user($user);
242
		common_real_login(true);
243
        if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
244 245
			common_rememberme($user);
		}
246
        unset($_SESSION['openid_rememberme']);
Evan Prodromou's avatar
Evan Prodromou committed
247
		common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)));
Evan Prodromou's avatar
Evan Prodromou committed
248
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
249

Evan Prodromou's avatar
Evan Prodromou committed
250
	function connect_user() {
Evan Prodromou's avatar
method  
Evan Prodromou committed
251

Evan Prodromou's avatar
Evan Prodromou committed
252 253 254 255
		$nickname = $this->trimmed('nickname');
		$password = $this->trimmed('password');

		if (!common_check_user($nickname, $password)) {
256
			$this->show_form(_('Invalid username or password.'));
Evan Prodromou's avatar
Evan Prodromou committed
257 258 259 260
			return;
		}

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

Evan Prodromou's avatar
Evan Prodromou committed
262 263 264 265 266
		$user = User::staticGet('nickname', $nickname);

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

		if (!$display || !$canonical) {
267
			common_server_error(_('Stored OpenID not found.'));
Evan Prodromou's avatar
Evan Prodromou committed
268 269
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
270

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

273
		if (!$result) {
274
			common_server_error(_('Error connecting user to OpenID.'));
Evan Prodromou's avatar
Evan Prodromou committed
275 276
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
277

278
		oid_update_user($user, $sreg);
Evan Prodromou's avatar
Evan Prodromou committed
279
		oid_set_last($display);
280
		common_set_user($user);
281
		common_real_login(true);
282
        if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
283 284 285
			common_rememberme($user);
		}
		unset($_SESSION['openid_rememberme']);
Evan Prodromou's avatar
Evan Prodromou committed
286 287
		$this->go_home($user->nickname);
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
288

Evan Prodromou's avatar
Evan Prodromou committed
289 290 291 292 293 294 295 296 297 298 299 300
	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
301

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

Evan Prodromou's avatar
Evan Prodromou committed
304
		# Try the passed-in nickname
Evan Prodromou's avatar
Evan Prodromou committed
305 306 307 308 309 310

		if ($sreg['nickname']) {
			$nickname = $this->nicknamize($sreg['nickname']);
			if ($this->is_new_nickname($nickname)) {
				return $nickname;
			}
Evan Prodromou's avatar
Evan Prodromou committed
311 312 313 314 315 316 317 318 319 320
		}

		# 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
321

Evan Prodromou's avatar
Evan Prodromou committed
322
		# Try the URL
Evan Prodromou's avatar
method  
Evan Prodromou committed
323

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

Evan Prodromou's avatar
Evan Prodromou committed
326 327 328 329 330 331 332 333 334 335 336 337 338 339
		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
340
		}
341
	if (!User::allowed_nickname($str)) {
342 343
			return false;
		}
Evan Prodromou's avatar
Evan Prodromou committed
344 345 346 347 348
		if (User::staticGet('nickname', $str)) {
			return false;
		}
		return true;
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
349

Evan Prodromou's avatar
Evan Prodromou committed
350 351 352 353 354 355 356 357 358 359 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
	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
421

Evan Prodromou's avatar
Evan Prodromou committed
422 423 424 425 426 427 428 429 430
	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
431

Evan Prodromou's avatar
Evan Prodromou committed
432 433 434 435 436
	function nicknamize($str) {
		$str = preg_replace('/\W/', '', $str);
		return strtolower($str);
	}
}