1<?php
2/**
3 * @package tikiwiki
4 */
5// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
6//
7// All Rights Reserved. See copyright.txt for details and a complete list of authors.
8// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
9// $Id$
10
11// As a side note beyond the standard heading. Most of the code in this file was taken
12// directly from the OpenID library example files. The code was modified to suit the
13// specific needs.
14require_once('tiki-setup.php');
15require_once "vendor_extra/pear/Auth/OpenID/HMAC.php";
16require_once "vendor_extra/pear/Auth/OpenID/Consumer.php";
17require_once "vendor_extra/pear/Auth/OpenID/FileStore.php";
18require_once "vendor_extra/pear/Auth/OpenID/Message.php";
19require_once "vendor_extra/pear/Auth/OpenID/BigMath.php";
20require_once "vendor_extra/pear/Auth/OpenID/Association.php";
21require_once "vendor_extra/pear/Auth/OpenID/SReg.php";
22require_once "vendor_extra/pear/Auth/OpenID/Discover.php";
23require_once "vendor_extra/pear/Auth/Yadis/XRI.php";
24require_once "vendor_extra/pear/Auth/Yadis/Misc.php";
25require_once "vendor_extra/pear/Auth/OpenID/URINorm.php";
26require_once "vendor_extra/pear/Auth/Yadis/XML.php";
27require_once "vendor_extra/pear/Auth/OpenID/Nonce.php";
28if ($prefs['auth_method'] != 'openid') {
29	$smarty->assign('msg', tra("Authentication method is not OpenID"));
30	$smarty->display("error.tpl");
31	die;
32}
33function setupFromAddress() // {{{
34{
35	global $url_scheme, $url_host, $url_port, $base_url;
36	// Remember where the page was requested from (from tiki-login.php)
37	if (! isset($_SESSION['loginfrom'])) {
38		$_SESSION['loginfrom'] = (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : $prefs['tikiIndex']);
39		if (! preg_match('/^http/', $_SESSION['loginfrom'])) {
40			if ($_SESSION['loginfrom'] {
41				0
42			} == '/') {
43				$_SESSION['loginfrom'] = $url_scheme . '://' . $url_host . (($url_port != '') ? ":$url_port" : '') . $_SESSION['loginfrom'];
44			} else {
45				$_SESSION['loginfrom'] = $base_url . $_SESSION['loginfrom'];
46			}
47		}
48	}
49	if (strpos($_SESSION['loginfrom'], 'openid') !== false) {
50		$_SESSION['loginfrom'] = $base_url;
51	}
52} // }}}
53/**
54 * @param $identifier
55 * @return array
56 */
57function getAccountsMatchingIdentifier($identifier) // {{{
58{
59	global $tikilib;
60	$result = $tikilib->query("SELECT login FROM users_users WHERE openid_url = ?", [$identifier]);
61	$userlist = [];
62	while ($row = $result->fetchRow()) {
63		$userlist[] = $row['login'];
64	}
65	return $userlist;
66} // }}}
67/**
68 * @param $identifier
69 */
70function loginUser($identifier) // {{{
71{
72	global $user_cookie_site;
73	$userlib = TikiLib::lib('user');
74	$userlib->update_lastlogin($identifier);
75	$userlib->update_expired_groups();
76	$_SESSION[$user_cookie_site] = $identifier;
77	header('location: ' . $_SESSION['loginfrom']);
78	unset($_SESSION['loginfrom']);
79	exit;
80} // }}}
81/**
82 * @param $data
83 * @param $messages
84 */
85function filterExistingInformation(&$data, &$messages) // {{{
86{
87	global $tikilib;
88	$result = $tikilib->query("SELECT COUNT(*) FROM users_users WHERE login = ?", [$data['nickname']]);
89	$count = reset($result->fetchRow());
90	if ($count > 0) {
91		$data['nickname'] = '';
92		$messages[] = tra('Your default nickname is already in use. A new one has to be selected.');
93	}
94} // }}}
95/**
96 * @param $data
97 * @param $messages
98 */
99function displayRegisatrationForms($data, $messages) // {{{
100{
101	global $prefs;
102	$userlib = TikiLib::lib('user');
103	$smarty = TikiLib::lib('smarty');
104	$registrationlib = TikiLib::lib('registration');
105
106	if (is_a($registrationlib->merged_prefs, "RegistrationError")) {
107		register_error($registrationlib->merged_prefs->msg);
108	}
109	$smarty->assign_by_ref('merged_prefs', $registrationlib->merged_prefs);
110
111
112	// Default values for the registration form
113	$smarty->assign('username', $data['nickname']);
114	$smarty->assign('email', $data['email']);
115	// Changing some system values to get the login box to display properly in the context
116	$smarty->assign('rememberme', 'disabled');
117	$smarty->assign('forgotPass', 'n');
118	$smarty->assign('allowRegister', ($prefs['allowRegister'] != 'y' || ($prefs['feature_intertiki'] == 'y' && ! empty($prefs['feature_intertiki_mymaster']))) ? 'n' : 'y');
119	$smarty->assign('change_password', 'n');
120	$smarty->assign('auth_method', 'tiki');
121	$smarty->assign('feature_switch_ssl_mode', 'n');
122
123	$listgroups = $userlib->get_groups(0, -1, 'groupName_asc', '', '', 'n');
124	$nbChoiceGroups = 0;
125	$mandatoryChoiceGroups = true;
126	foreach ($listgroups['data'] as $gr) {
127		if ($gr['registrationChoice'] == 'y') {
128			++$nbChoiceGroups;
129			$theChoiceGroup = $gr['groupName'];
130			if ($gr['groupName'] == 'Registered') {
131				$mandatoryChoiceGroups = false;
132			}
133		}
134	}
135	if ($nbChoiceGroups) {
136		$smarty->assign('listgroups', $listgroups['data']);
137		if ($nbChoiceGroups == 1) {
138			$smarty->assign_by_ref('theChoiceGroup', $theChoiceGroup);
139		}
140	}
141
142	// Display
143	$smarty->assign('mid', 'tiki-register.tpl');
144	$smarty->assign('openid_associate', 'y');
145	$smarty->assign('registration', 'y');
146	$smarty->display('tiki.tpl');
147	exit;
148} // }}}
149/**
150 * @param $data
151 * @param $messages
152 */
153function displaySelectionList($data, $messages) // {{{
154{
155	$smarty = TikiLib::lib('smarty');
156	// Display
157	$smarty->assign('mid', 'tiki-openid_select.tpl');
158	$smarty->display('tiki.tpl');
159	exit;
160} // }}}
161/**
162 * @param $message
163 */
164function displayError($message)
165{
166 // {{{
167	$smarty = TikiLib::lib('smarty');
168	$smarty->assign('msg', tra("Failure:") . " " . $message);
169	$smarty->assign('errortype', 'login');
170	$smarty->display("error.tpl");
171	die;
172} // }}}
173/**
174 * @return Auth_OpenID_FileStore
175 */
176function getStore()
177{
178 // {{{
179	$store_path = "temp/openid_consumer";
180	if (! file_exists($store_path) && ! mkdir($store_path)) {
181		print "Could not create the FileStore directory '$store_path'. " . " Please check the effective permissions.";
182		exit(0);
183	}
184	return new Auth_OpenID_FileStore($store_path);
185} // }}}
186/**
187 * @return Auth_OpenID_Consumer
188 */
189function getConsumer()
190{
191 // {{{
192
193	/**
194	 * Create a consumer object using the store object created
195	 * earlier.
196	 */
197	$store = getStore();
198	return new Auth_OpenID_Consumer($store);
199} // }}}
200function getOpenIDURL()
201{
202 // {{{
203	// Render a default page if we got a submission without an openid
204	// value.
205	if (empty($_GET['openid_url'])) {
206		displayError('Call the page properly');
207	}
208	return $_GET['openid_url'];
209} // }}}
210/**
211 * @return string
212 */
213function getScheme()
214{
215 // {{{
216	$scheme = 'http';
217	if (isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') {
218		$scheme .= 's';
219	}
220	return $scheme;
221} // }}}
222/**
223 * @return string
224 */
225function getReturnTo()
226{
227 // {{{
228	$path = str_replace('\\', '/', dirname($_SERVER['PHP_SELF']));
229	$string = sprintf("%s://%s:%s%s/tiki-login_openid.php?action=return", getScheme(), $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $path == '/' ? '' : $path);
230	if (isset($_GET['action']) && $_GET['action'] == 'force') {
231		$string .= '&force=true';
232	}
233	return $string;
234} // }}}
235/**
236 * @return string
237 */
238function getTrustRoot()
239{
240 // {{{
241	return sprintf("%s://%s:%s%s", getScheme(), $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], str_replace('\\', '/', dirname($_SERVER['PHP_SELF'])));
242} // }}}
243function runAuth()
244{
245 // {{{
246	setupFromAddress();
247	$openid = getOpenIDURL();
248	$consumer = getConsumer();
249	// Begin the OpenID authentication process.
250	$auth_request = $consumer->begin($openid);
251	// No auth request means we can't begin OpenID. Usually this is because the OpenID is invalid. Sometimes this is because the OpenID server's certificate isn't trusted.
252	if (! $auth_request) {
253		displayError(tra("Authentication error; probably not a valid OpenID."));
254	}
255	$sreg_request = Auth_OpenID_SRegRequest::build(
256		// Required
257		[],
258		// Optional
259		['nickname', 'email']
260	);
261	if ($sreg_request) {
262		$auth_request->addExtension($sreg_request);
263	}
264	// Redirect the user to the OpenID server for authentication.
265	// Store the token for this authentication so we can verify the
266	// response.
267	// For OpenID 1, send a redirect.  For OpenID 2, use a JavaScript
268	// form to send a POST request to the server.
269	if ($auth_request->shouldSendRedirect()) {
270		$redirect_url = $auth_request->redirectURL(getTrustRoot(), getReturnTo());
271		// If the redirect URL can't be built, display an error
272		// message.
273		if (Auth_OpenID::isFailure($redirect_url)) {
274			displayError(tra("Could not redirect to server: ") . $redirect_url->message);
275		} else {
276			// Send redirect.
277			header("Location: " . $redirect_url);
278		}
279	} else {
280		// Generate form markup and render it.
281		$form_id = 'openid_message';
282		$form_html = $auth_request->htmlMarkup(getTrustRoot(), getReturnTo(), false, ['id' => $form_id]);
283		// Display an error if the form markup couldn't be generated;
284		// otherwise, render the HTML.
285		if (Auth_OpenID::isFailure($form_html)) {
286			displayError(tra("Could not redirect to server: ") . $form_html->message);
287		} else {
288			print $form_html;
289		}
290	}
291} // }}}
292function runFinish()
293{
294 // {{{
295	$smarty = TikiLib::lib('smarty');
296	$consumer = getConsumer();
297	// Complete the authentication process using the server's
298	// response.
299	$response = $consumer->complete(getReturnTo());
300	// Check the response status.
301	if ($response->status == Auth_OpenID_CANCEL) {
302		// This means the authentication was cancelled.
303		displayError(tra('Verification cancelled.'));
304	} elseif ($response->status == Auth_OpenID_FAILURE) {
305		// Authentication failed; display the error message.
306		displayError(tra("OpenID authentication failed: ") . $response->message);
307	} elseif ($response->status == Auth_OpenID_SUCCESS) {
308		// This means the authentication succeeded; extract the
309		// identity URL and Simple Registration data (if it was
310		// returned).
311		$data = ['identifier' => $response->identity_url, 'email' => '', 'fullname' => '', 'nickname' => '',];
312		$sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
313		$sreg = $sreg_resp->contents();
314		// Sanitize identifier. Just consider slashes at the end are never good.
315		if (substr($data['identifier'], -1) == '/') {
316			$data['identifier'] = substr($data['identifier'], 0, -1);
317		}
318		if (@$sreg['email']) {
319			$data['email'] = $sreg['email'];
320		}
321		if (@$sreg['nickname']) {
322			$data['nickname'] = $sreg['nickname'];
323		}
324		$_SESSION['openid_url'] = $data['identifier'];
325		// If OpenID identifier exists in the database
326		$list = getAccountsMatchingIdentifier($data['identifier']);
327		$_SESSION['openid_userlist'] = $list;
328		$smarty->assign('openid_userlist', $list);
329		if (count($list) > 0 && ! isset($_GET['force'])) {
330			// If Single account
331			if (count($list) == 1) {
332				// Login the user
333				loginUser($list[0]);
334			} else {
335			// Else Multiple account
336				// Display user selection list
337				displaySelectionList($list);
338			}
339		} else {
340			$messages = [];
341			// Check for entries that already exist in the database and filter them out
342			filterExistingInformation($data, $messages);
343			// Display register and attach forms
344			displayRegisatrationForms($data, $messages);
345		}
346	}
347} // }}}
348function runSelect() // {{{
349{
350	setupFromAddress();
351	$user = $_GET['select'];
352	if (in_array($user, $_SESSION['openid_userlist'])) {
353		loginUser($user);
354	} else {
355		displayError(tra('The selected account is not associated with your identity.'));
356	}
357} // }}}
358if (isset($_GET['action'])) {
359	if ($_GET['action'] == 'return') {
360		runFinish();
361	} elseif ($_GET['action'] == 'select' && isset($_GET['select'])) {
362		runSelect();
363	} elseif ($_GET['action'] == 'force') {
364		runAuth();
365	} else {
366		displayError(tra('unknown action'));
367	}
368} else {
369	runAuth();
370}
371