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

finishopenidlogin.php 11.4 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
						   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
57
		}
58
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
59

60
	function show_form($error=NULL, $username=NULL) {
61
		common_show_header(_('OpenID Account Setup'), NULL, $error,
62 63
						   array($this, 'show_top'));

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

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

Evan Prodromou's avatar
Evan Prodromou committed
98 99 100 101 102
		$consumer = oid_consumer();

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

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

Evan Prodromou's avatar
Evan Prodromou committed
124
			if ($user) {
Evan Prodromou's avatar
Evan Prodromou committed
125
				oid_set_last($display);
126 127 128
				# 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
129
				common_set_user($user->nickname);
130
				common_real_login(true);
Evan Prodromou's avatar
Evan Prodromou committed
131 132 133 134 135 136 137 138 139
				$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) {
140
		common_show_header(_('OpenID Login'));
Evan Prodromou's avatar
Evan Prodromou committed
141 142 143
		common_element('p', NULL, $msg);
		common_show_footer();
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
144

Evan Prodromou's avatar
Evan Prodromou committed
145 146 147
	function save_values($display, $canonical, $sreg) {
		common_ensure_session();
		$_SESSION['openid_display'] = $display;
Evan Prodromou's avatar
method  
Evan Prodromou committed
148 149
		$_SESSION['openid_canonical'] = $canonical;
		$_SESSION['openid_sreg'] = $sreg;
Evan Prodromou's avatar
Evan Prodromou committed
150 151
	}

152
	function get_saved_values() {
Evan Prodromou's avatar
Evan Prodromou committed
153 154 155 156
		return array($_SESSION['openid_display'],
					 $_SESSION['openid_canonical'],
					 $_SESSION['openid_sreg']);
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
157

Evan Prodromou's avatar
Evan Prodromou committed
158
	function create_new_user() {
Evan Prodromou's avatar
method  
Evan Prodromou committed
159

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

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

		if (!User::allowed_nickname($nickname)) {
170
			$this->show_form(_('Nickname not allowed.'));
171 172
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
173

Evan Prodromou's avatar
Evan Prodromou committed
174
		if (User::staticGet('nickname', $nickname)) {
175
			$this->show_form(_('Nickname already in use. Try another one.'));
Evan Prodromou's avatar
Evan Prodromou committed
176 177
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
178

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

Evan Prodromou's avatar
Evan Prodromou committed
181
		if (!$display || !$canonical) {
182
			common_server_error(_('Stored OpenID not found.'));
Evan Prodromou's avatar
Evan Prodromou committed
183 184
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
185

Evan Prodromou's avatar
Evan Prodromou committed
186
		# Possible race condition... let's be paranoid
Evan Prodromou's avatar
method  
Evan Prodromou committed
187

Evan Prodromou's avatar
Evan Prodromou committed
188
		$other = oid_get_user($canonical);
Evan Prodromou's avatar
method  
Evan Prodromou committed
189

Evan Prodromou's avatar
Evan Prodromou committed
190
		if ($other) {
191
			common_server_error(_('Creating new account for OpenID that already has a user.'));
Evan Prodromou's avatar
Evan Prodromou committed
192 193
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
194

Evan Prodromou's avatar
Evan Prodromou committed
195 196 197 198
		if ($sreg['country']) {
			if ($sreg['postcode']) {
				# XXX: use postcode to get city and region
				# XXX: also, store postcode somewhere -- it's valuable!
199
				$location = $sreg['postcode'] . ', ' . $sreg['country'];
Evan Prodromou's avatar
Evan Prodromou committed
200
			} else {
201
				$location = $sreg['country'];
Evan Prodromou's avatar
Evan Prodromou committed
202 203
			}
		}
204 205 206
		
		if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
			$fullname = $sreg['fullname'];
Evan Prodromou's avatar
Evan Prodromou committed
207
		}
208
		
Evan Prodromou's avatar
Evan Prodromou committed
209
		if ($sreg['email'] && Validate::email($sreg['email'], true)) {
210
			$email = $sreg['email'];
Evan Prodromou's avatar
Evan Prodromou committed
211
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
212

213 214 215 216 217 218 219
		# XXX: add language
		# XXX: add timezone
		
		$user = User::register(array('nickname' => $nickname, 
									 'email' => $email,
									 'fullname' => $fullname, 
									 'location' => $location));
Evan Prodromou's avatar
Evan Prodromou committed
220

221
		$result = oid_link_user($user->id, $canonical, $display);
222 223
		
		oid_set_last($display);							   
Evan Prodromou's avatar
Evan Prodromou committed
224
		common_set_user($user->nickname);
225
		common_real_login(true);
Evan Prodromou's avatar
Evan Prodromou committed
226
		common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)));
Evan Prodromou's avatar
Evan Prodromou committed
227
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
228

Evan Prodromou's avatar
Evan Prodromou committed
229
	function connect_user() {
Evan Prodromou's avatar
method  
Evan Prodromou committed
230

Evan Prodromou's avatar
Evan Prodromou committed
231 232 233 234
		$nickname = $this->trimmed('nickname');
		$password = $this->trimmed('password');

		if (!common_check_user($nickname, $password)) {
235
			$this->show_form(_('Invalid username or password.'));
Evan Prodromou's avatar
Evan Prodromou committed
236 237 238 239
			return;
		}

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

Evan Prodromou's avatar
Evan Prodromou committed
241 242 243 244 245
		$user = User::staticGet('nickname', $nickname);

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

		if (!$display || !$canonical) {
246
			common_server_error(_('Stored OpenID not found.'));
Evan Prodromou's avatar
Evan Prodromou committed
247 248
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
249

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

252
		if (!$result) {
253
			common_server_error(_('Error connecting user to OpenID.'));
Evan Prodromou's avatar
Evan Prodromou committed
254 255
			return;
		}
Evan Prodromou's avatar
method  
Evan Prodromou committed
256

257
		oid_update_user($user, $sreg);
Evan Prodromou's avatar
Evan Prodromou committed
258
		oid_set_last($display);
Evan Prodromou's avatar
Evan Prodromou committed
259
		common_set_user($user->nickname);
260
		common_real_login(true);
Evan Prodromou's avatar
Evan Prodromou committed
261 262
		$this->go_home($user->nickname);
	}
Evan Prodromou's avatar
method  
Evan Prodromou committed
263

Evan Prodromou's avatar
Evan Prodromou committed
264 265 266 267 268 269 270 271 272 273 274 275
	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
276

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

Evan Prodromou's avatar
Evan Prodromou committed
279
		# Try the passed-in nickname
Evan Prodromou's avatar
Evan Prodromou committed
280 281 282 283 284 285 286


		if ($sreg['nickname']) {
			$nickname = $this->nicknamize($sreg['nickname']);
			if ($this->is_new_nickname($nickname)) {
				return $nickname;
			}
Evan Prodromou's avatar
Evan Prodromou committed
287 288 289 290 291 292 293 294 295 296
		}

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

Evan Prodromou's avatar
Evan Prodromou committed
298
		# Try the URL
Evan Prodromou's avatar
method  
Evan Prodromou committed
299

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

Evan Prodromou's avatar
Evan Prodromou committed
302 303 304 305 306 307 308 309 310 311 312 313 314 315
		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
316
		}
317
	if (!User::allowed_nickname($str)) {
318 319
			return false;
		}
Evan Prodromou's avatar
Evan Prodromou committed
320 321 322 323 324
		if (User::staticGet('nickname', $str)) {
			return false;
		}
		return true;
	}
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 340 341 342 343 344 345 346 347 348 349 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
	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
397

Evan Prodromou's avatar
Evan Prodromou committed
398 399 400 401 402 403 404 405 406
	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
407

Evan Prodromou's avatar
Evan Prodromou committed
408 409 410 411 412
	function nicknamize($str) {
		$str = preg_replace('/\W/', '', $str);
		return strtolower($str);
	}
}