File: OpenIDConsumer.php

package info (click to toggle)
simplesamlphp 1.13.1-2%2Bdeb8u1
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 11,304 kB
  • sloc: php: 65,124; xml: 629; python: 376; sh: 193; perl: 185; makefile: 43
file content (349 lines) | stat: -rw-r--r-- 10,519 bytes parent folder | download | duplicates (2)
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
<?php

/*
 * Disable strict error reporting, since the OpenID library
 * used is PHP4-compatible, and not PHP5 strict-standards compatible.
 */
SimpleSAML_Utilities::maskErrors(E_STRICT);
if (defined('E_DEPRECATED')) {
	/* PHP 5.3 also has E_DEPRECATED. */
	SimpleSAML_Utilities::maskErrors(constant('E_DEPRECATED'));
}

/* Add the OpenID library search path. */
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(dirname(dirname(dirname(dirname(dirname(__FILE__)))))) . '/lib');

require_once('Auth/OpenID/AX.php');
require_once('Auth/OpenID/SReg.php');
require_once('Auth/OpenID/Server.php');
require_once('Auth/OpenID/ServerRequest.php');


/**
 * Authentication module which acts as an OpenID Consumer
 *
 * @author Andreas Åkre Solberg, <andreas.solberg@uninett.no>, UNINETT AS.
 * @package simpleSAMLphp
 */
class sspmod_openid_Auth_Source_OpenIDConsumer extends SimpleSAML_Auth_Source {

	/**
	 * Static openid target to use.
	 *
	 * @var string|NULL
	 */
	private $target;

	/**
	 * Custom realm to use.
	 *
	 * @var string|NULL
	 */
	private $realm;

	/**
	 * List of optional attributes.
	 */
	private $optionalAttributes;
	private $optionalAXAttributes;


	/**
	 * List of required attributes.
	 */
	private $requiredAttributes;
	private $requiredAXAttributes;

	/**
	 * Validate SReg responses.
	 */
	private $validateSReg;

	/**
	 * List of custom extension args
	 */
	private $extensionArgs;

	/**
	 * Prefer HTTP Redirect over HTML Form Redirection (POST)
	 */
	private $preferHttpRedirect;

	/**
	 * Constructor for this authentication source.
	 *
	 * @param array $info  Information about this authentication source.
	 * @param array $config  Configuration.
	 */
	public function __construct($info, $config) {

		/* Call the parent constructor first, as required by the interface. */
		parent::__construct($info, $config);

		$cfgParse = SimpleSAML_Configuration::loadFromArray($config,
			'Authentication source ' . var_export($this->authId, TRUE));

		$this->target = $cfgParse->getString('target', NULL);
		$this->realm = $cfgParse->getString('realm', NULL);

		$this->optionalAttributes = $cfgParse->getArray('attributes.optional', array());
		$this->requiredAttributes = $cfgParse->getArray('attributes.required', array());

		$this->optionalAXAttributes = $cfgParse->getArray('attributes.ax_optional', array());
		$this->requiredAXAttributes = $cfgParse->getArray('attributes.ax_required', array());

		$this->validateSReg = $cfgParse->getBoolean('sreg.validate',TRUE);

		$this->extensionArgs = $cfgParse->getArray('extension.args', array());

		$this->preferHttpRedirect = $cfgParse->getBoolean('prefer_http_redirect', FALSE);
	}


	/**
	 * Initiate authentication. Redirecting the user to the consumer endpoint 
	 * with a state Auth ID.
	 *
	 * @param array &$state  Information about the current authentication.
	 */
	public function authenticate(&$state) {
		assert('is_array($state)');

		$state['openid:AuthId'] = $this->authId;

		if ($this->target !== NULL) {
			/* We know our OpenID target URL. Skip the page where we ask for it. */
			$this->doAuth($state, $this->target);

			/* doAuth() never returns. */
			assert('FALSE');
		}

		$id = SimpleSAML_Auth_State::saveState($state, 'openid:init');

		$url = SimpleSAML_Module::getModuleURL('openid/consumer.php');
		SimpleSAML_Utilities::redirectTrustedURL($url, array('AuthState' => $id));
	}


	/**
	 * Retrieve the Auth_OpenID_Consumer instance.
	 *
	 * @param array &$state  The state array we are currently working with.
	 * @return Auth_OpenID_Consumer  The Auth_OpenID_Consumer instance.
	 */
	private function getConsumer(array &$state) {
		$store = new sspmod_openid_StateStore($state);
		$session = new sspmod_openid_SessionStore();
		return new Auth_OpenID_Consumer($store, $session);
	}


	/**
	 * Retrieve the URL we should return to after successful authentication.
	 *
	 * @return string  The URL we should return to after successful authentication.
	 */
	private function getReturnTo($stateId) {
		assert('is_string($stateId)');

		return SimpleSAML_Module::getModuleURL('openid/linkback.php', array(
			'AuthState' => $stateId,
		));
	}


	/**
	 * Retrieve the trust root for this openid site.
	 *
	 * @return string  The trust root.
	 */
	private function getTrustRoot() {
		if (!empty($this->realm)) {
			return $this->realm;
		} else {
			return SimpleSAML_Utilities::selfURLhost();
		}
	}


	/**
	 * Send an authentication request to the OpenID provider.
	 *
	 * @param array &$state  The state array.
	 * @param string $openid  The OpenID we should try to authenticate with.
	 */
	public function doAuth(array &$state, $openid) {
		assert('is_string($openid)');

		$stateId = SimpleSAML_Auth_State::saveState($state, 'openid:auth');

		$consumer = $this->getConsumer($state);

		// Begin the OpenID authentication process.
		$auth_request = $consumer->begin($openid);

		// No auth request means we can't begin OpenID.
		if (!$auth_request) {
			throw new SimpleSAML_Error_BadRequest('Not a valid OpenID: ' . var_export($openid, TRUE));
		}

		$sreg_request = Auth_OpenID_SRegRequest::build(
			$this->requiredAttributes,
			$this->optionalAttributes
		);

		if ($sreg_request) {
			$auth_request->addExtension($sreg_request);
		}

		// Create attribute request object
		$ax_attribute = array();

		foreach($this->requiredAXAttributes as $attr) {
			$ax_attribute[] = Auth_OpenID_AX_AttrInfo::make($attr,1,true);
		}

		foreach($this->optionalAXAttributes as $attr) {
			$ax_attribute[] = Auth_OpenID_AX_AttrInfo::make($attr,1,false);
		}

		if (count($ax_attribute) > 0) {

			// Create AX fetch request
			$ax_request = new Auth_OpenID_AX_FetchRequest;

			// Add attributes to AX fetch request
			foreach($ax_attribute as $attr){
				$ax_request->add($attr);
			}

			// Add AX fetch request to authentication request
			$auth_request->addExtension($ax_request);

		}

		foreach($this->extensionArgs as $ext_ns => $ext_arg) {
			if (is_array($ext_arg)) {
				foreach($ext_arg as $ext_key => $ext_value) {
					$auth_request->addExtensionArg($ext_ns, $ext_key, $ext_value);
				}
			}
		}

		// Redirect the user to the OpenID server for authentication.
		// Store the token for this authentication so we can verify the
		// response.

		// For OpenID 1, send a redirect.  For OpenID 2, use a Javascript form
		// to send a POST request to the server or use redirect if
		// prefer_http_redirect is enabled and redirect URL size
		// is less than 2049
		$should_send_redirect = $auth_request->shouldSendRedirect();
		if ($this->preferHttpRedirect || $should_send_redirect) {
			$redirect_url = $auth_request->redirectURL($this->getTrustRoot(), $this->getReturnTo($stateId));

			// If the redirect URL can't be built, display an error message.
			if (Auth_OpenID::isFailure($redirect_url)) {
				throw new SimpleSAML_Error_AuthSource($this->authId, 'Could not redirect to server: ' . var_export($redirect_url->message, TRUE));
			}

			// For OpenID 2 failover to POST if redirect URL is longer than 2048
			if ($should_send_redirect || strlen($redirect_url) <= 2048) {
				SimpleSAML_Utilities::redirectTrustedURL($redirect_url);
				assert('FALSE');
			}
		}

		// Generate form markup and render it.
		$form_id = 'openid_message';
		$form_html = $auth_request->formMarkup($this->getTrustRoot(), $this->getReturnTo($stateId), FALSE, array('id' => $form_id));

		// Display an error if the form markup couldn't be generated; otherwise, render the HTML.
		if (Auth_OpenID::isFailure($form_html)) {
			throw new SimpleSAML_Error_AuthSource($this->authId, 'Could not redirect to server: ' . var_export($form_html->message, TRUE));
		} else {
			echo '<html><head><title>OpenID transaction in progress</title></head>
				<body onload=\'document.getElementById("' . $form_id . '").submit()\'>' .
				$form_html . '</body></html>';
			exit;
		}
	}


	/**
	 * Process an authentication response.
	 *
	 * @param array &$state  The state array.
	 */
	public function postAuth(array &$state) {

		$consumer = $this->getConsumer($state);

		$return_to = SimpleSAML_Utilities::selfURL();

		// Complete the authentication process using the server's
		// response.
		$response = $consumer->complete($return_to);

		// Check the response status.
		if ($response->status == Auth_OpenID_CANCEL) {
			// This means the authentication was cancelled.
			throw new SimpleSAML_Error_UserAborted();
		} else if ($response->status == Auth_OpenID_FAILURE) {
			// Authentication failed; display the error message.
			throw new SimpleSAML_Error_AuthSource($this->authId, 'Authentication failed: ' . var_export($response->message, TRUE));
		} else if ($response->status != Auth_OpenID_SUCCESS) {
			throw new SimpleSAML_Error_AuthSource($this->authId, 'General error. Try again.');
		}

		// This means the authentication succeeded; extract the
		// identity URL and Simple Registration data (if it was
		// returned).
		$openid = $response->identity_url;

		$attributes = array('openid' => array($openid));
		$attributes['openid.server_url'] = array($response->endpoint->server_url);

		if ($response->endpoint->canonicalID) {
			$attributes['openid.canonicalID'] = array($response->endpoint->canonicalID);
		}

		if ($response->endpoint->local_id) {
				$attributes['openid.local_id'] = array($response->endpoint->local_id);
		}

		$sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response, $this->validateSReg);
		$sregresponse = $sreg_resp->contents();

		if (is_array($sregresponse) && count($sregresponse) > 0) {
			$attributes['openid.sregkeys'] = array_keys($sregresponse);
			foreach ($sregresponse AS $sregkey => $sregvalue) {
				$attributes['openid.sreg.' . $sregkey] = array($sregvalue);
			}
		}

		// Get AX response information
		$ax = new Auth_OpenID_AX_FetchResponse();
		$ax_resp = $ax->fromSuccessResponse($response);

		if (($ax_resp instanceof Auth_OpenID_AX_FetchResponse) && (!empty($ax_resp->data))) {
			$axresponse = $ax_resp->data;

			$attributes['openid.axkeys'] = array_keys($axresponse);
			foreach ($axresponse AS $axkey => $axvalue) {
				if (preg_match("/^\w+:/",$axkey)) {
					$attributes[$axkey] = (is_array($axvalue)) ? $axvalue : array($axvalue);
				} else {
					SimpleSAML_Logger::warning('Invalid attribute name in AX response: ' . var_export($axkey, TRUE));
				}
			}
		}

		SimpleSAML_Logger::debug('OpenID Returned Attributes: '. implode(", ",array_keys($attributes)));

		$state['Attributes'] = $attributes;
		SimpleSAML_Auth_Source::completeAuth($state);
	}

}