7b0c640c5893768ea9c8537380f42599ffe7d94d
[Packages/TYPO3.CMS.git] / typo3 / sysext / openid / lib / php-openid / Auth / OpenID / Discover.php
1 <?php
2
3 /**
4 * The OpenID and Yadis discovery implementation for OpenID 1.2.
5 */
6
7 require_once "Auth/OpenID.php";
8 require_once "Auth/OpenID/Parse.php";
9 require_once "Auth/OpenID/Message.php";
10 require_once "Auth/Yadis/XRIRes.php";
11 require_once "Auth/Yadis/Yadis.php";
12
13 // XML namespace value
14 define('Auth_OpenID_XMLNS_1_0', 'http://openid.net/xmlns/1.0');
15
16 // Yadis service types
17 define('Auth_OpenID_TYPE_1_2', 'http://openid.net/signon/1.2');
18 define('Auth_OpenID_TYPE_1_1', 'http://openid.net/signon/1.1');
19 define('Auth_OpenID_TYPE_1_0', 'http://openid.net/signon/1.0');
20 define('Auth_OpenID_TYPE_2_0_IDP', 'http://specs.openid.net/auth/2.0/server');
21 define('Auth_OpenID_TYPE_2_0', 'http://specs.openid.net/auth/2.0/signon');
22 define('Auth_OpenID_RP_RETURN_TO_URL_TYPE',
23 'http://specs.openid.net/auth/2.0/return_to');
24
25 function Auth_OpenID_getOpenIDTypeURIs()
26 {
27 return array(Auth_OpenID_TYPE_2_0_IDP,
28 Auth_OpenID_TYPE_2_0,
29 Auth_OpenID_TYPE_1_2,
30 Auth_OpenID_TYPE_1_1,
31 Auth_OpenID_TYPE_1_0);
32 }
33
34 function Auth_OpenID_getOpenIDConsumerTypeURIs()
35 {
36 return array(Auth_OpenID_RP_RETURN_TO_URL_TYPE);
37 }
38
39
40 /*
41 * Provides a user-readable interpretation of a type uri.
42 * Useful for error messages.
43 */
44 function Auth_OpenID_getOpenIDTypeName($type_uri) {
45 switch ($type_uri) {
46 case Auth_OpenID_TYPE_2_0_IDP:
47 return 'OpenID 2.0 IDP';
48 case Auth_OpenID_TYPE_2_0:
49 return 'OpenID 2.0';
50 case Auth_OpenID_TYPE_1_2:
51 return 'OpenID 1.2';
52 case Auth_OpenID_TYPE_1_1:
53 return 'OpenID 1.1';
54 case Auth_OpenID_TYPE_1_0:
55 return 'OpenID 1.0';
56 case Auth_OpenID_RP_RETURN_TO_URL_TYPE:
57 return 'OpenID relying party';
58 }
59 }
60
61 /**
62 * Object representing an OpenID service endpoint.
63 */
64 class Auth_OpenID_ServiceEndpoint {
65 function Auth_OpenID_ServiceEndpoint()
66 {
67 $this->claimed_id = null;
68 $this->server_url = null;
69 $this->type_uris = array();
70 $this->local_id = null;
71 $this->canonicalID = null;
72 $this->used_yadis = false; // whether this came from an XRDS
73 $this->display_identifier = null;
74 }
75
76 function getDisplayIdentifier()
77 {
78 if ($this->display_identifier) {
79 return $this->display_identifier;
80 }
81 if (! $this->claimed_id) {
82 return $this->claimed_id;
83 }
84 $parsed = parse_url($this->claimed_id);
85 $scheme = $parsed['scheme'];
86 $host = $parsed['host'];
87 $path = $parsed['path'];
88 if (array_key_exists('query', $parsed)) {
89 $query = $parsed['query'];
90 $no_frag = "$scheme://$host$path?$query";
91 } else {
92 $no_frag = "$scheme://$host$path";
93 }
94 return $no_frag;
95 }
96
97 function usesExtension($extension_uri)
98 {
99 return in_array($extension_uri, $this->type_uris);
100 }
101
102 function preferredNamespace()
103 {
104 if (in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris) ||
105 in_array(Auth_OpenID_TYPE_2_0, $this->type_uris)) {
106 return Auth_OpenID_OPENID2_NS;
107 } else {
108 return Auth_OpenID_OPENID1_NS;
109 }
110 }
111
112 /*
113 * Query this endpoint to see if it has any of the given type
114 * URIs. This is useful for implementing other endpoint classes
115 * that e.g. need to check for the presence of multiple versions
116 * of a single protocol.
117 *
118 * @param $type_uris The URIs that you wish to check
119 *
120 * @return all types that are in both in type_uris and
121 * $this->type_uris
122 */
123 function matchTypes($type_uris)
124 {
125 $result = array();
126 foreach ($type_uris as $test_uri) {
127 if ($this->supportsType($test_uri)) {
128 $result[] = $test_uri;
129 }
130 }
131
132 return $result;
133 }
134
135 function supportsType($type_uri)
136 {
137 // Does this endpoint support this type?
138 return ((in_array($type_uri, $this->type_uris)) ||
139 (($type_uri == Auth_OpenID_TYPE_2_0) &&
140 $this->isOPIdentifier()));
141 }
142
143 function compatibilityMode()
144 {
145 return $this->preferredNamespace() != Auth_OpenID_OPENID2_NS;
146 }
147
148 function isOPIdentifier()
149 {
150 return in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris);
151 }
152
153 static function fromOPEndpointURL($op_endpoint_url)
154 {
155 // Construct an OP-Identifier OpenIDServiceEndpoint object for
156 // a given OP Endpoint URL
157 $obj = new Auth_OpenID_ServiceEndpoint();
158 $obj->server_url = $op_endpoint_url;
159 $obj->type_uris = array(Auth_OpenID_TYPE_2_0_IDP);
160 return $obj;
161 }
162
163 function parseService($yadis_url, $uri, $type_uris, $service_element)
164 {
165 // Set the state of this object based on the contents of the
166 // service element. Return true if successful, false if not
167 // (if findOPLocalIdentifier returns false).
168 $this->type_uris = $type_uris;
169 $this->server_url = $uri;
170 $this->used_yadis = true;
171
172 if (!$this->isOPIdentifier()) {
173 $this->claimed_id = $yadis_url;
174 $this->local_id = Auth_OpenID_findOPLocalIdentifier(
175 $service_element,
176 $this->type_uris);
177 if ($this->local_id === false) {
178 return false;
179 }
180 }
181
182 return true;
183 }
184
185 function getLocalID()
186 {
187 // Return the identifier that should be sent as the
188 // openid.identity_url parameter to the server.
189 if ($this->local_id === null && $this->canonicalID === null) {
190 return $this->claimed_id;
191 } else {
192 if ($this->local_id) {
193 return $this->local_id;
194 } else {
195 return $this->canonicalID;
196 }
197 }
198 }
199
200 /*
201 * Parse the given document as XRDS looking for OpenID consumer services.
202 *
203 * @return array of Auth_OpenID_ServiceEndpoint or null if the
204 * document cannot be parsed.
205 */
206 function consumerFromXRDS($uri, $xrds_text)
207 {
208 $xrds =& Auth_Yadis_XRDS::parseXRDS($xrds_text);
209
210 if ($xrds) {
211 $yadis_services =
212 $xrds->services(array('filter_MatchesAnyOpenIDConsumerType'));
213 return Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services);
214 }
215
216 return null;
217 }
218
219 /*
220 * Parse the given document as XRDS looking for OpenID services.
221 *
222 * @return array of Auth_OpenID_ServiceEndpoint or null if the
223 * document cannot be parsed.
224 */
225 static function fromXRDS($uri, $xrds_text)
226 {
227 $xrds = Auth_Yadis_XRDS::parseXRDS($xrds_text);
228
229 if ($xrds) {
230 $yadis_services =
231 $xrds->services(array('filter_MatchesAnyOpenIDType'));
232 return Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services);
233 }
234
235 return null;
236 }
237
238 /*
239 * Create endpoints from a DiscoveryResult.
240 *
241 * @param discoveryResult Auth_Yadis_DiscoveryResult
242 * @return array of Auth_OpenID_ServiceEndpoint or null if
243 * endpoints cannot be created.
244 */
245 static function fromDiscoveryResult($discoveryResult)
246 {
247 if ($discoveryResult->isXRDS()) {
248 return Auth_OpenID_ServiceEndpoint::fromXRDS(
249 $discoveryResult->normalized_uri,
250 $discoveryResult->response_text);
251 } else {
252 return Auth_OpenID_ServiceEndpoint::fromHTML(
253 $discoveryResult->normalized_uri,
254 $discoveryResult->response_text);
255 }
256 }
257
258 static function fromHTML($uri, $html)
259 {
260 $discovery_types = array(
261 array(Auth_OpenID_TYPE_2_0,
262 'openid2.provider', 'openid2.local_id'),
263 array(Auth_OpenID_TYPE_1_1,
264 'openid.server', 'openid.delegate')
265 );
266
267 $services = array();
268
269 foreach ($discovery_types as $triple) {
270 list($type_uri, $server_rel, $delegate_rel) = $triple;
271
272 $urls = Auth_OpenID_legacy_discover($html, $server_rel,
273 $delegate_rel);
274
275 if ($urls === false) {
276 continue;
277 }
278
279 list($delegate_url, $server_url) = $urls;
280
281 $service = new Auth_OpenID_ServiceEndpoint();
282 $service->claimed_id = $uri;
283 $service->local_id = $delegate_url;
284 $service->server_url = $server_url;
285 $service->type_uris = array($type_uri);
286
287 $services[] = $service;
288 }
289
290 return $services;
291 }
292
293 function copy()
294 {
295 $x = new Auth_OpenID_ServiceEndpoint();
296
297 $x->claimed_id = $this->claimed_id;
298 $x->server_url = $this->server_url;
299 $x->type_uris = $this->type_uris;
300 $x->local_id = $this->local_id;
301 $x->canonicalID = $this->canonicalID;
302 $x->used_yadis = $this->used_yadis;
303
304 return $x;
305 }
306 }
307
308 function Auth_OpenID_findOPLocalIdentifier($service, $type_uris)
309 {
310 // Extract a openid:Delegate value from a Yadis Service element.
311 // If no delegate is found, returns null. Returns false on
312 // discovery failure (when multiple delegate/localID tags have
313 // different values).
314
315 $service->parser->registerNamespace('openid',
316 Auth_OpenID_XMLNS_1_0);
317
318 $service->parser->registerNamespace('xrd',
319 Auth_Yadis_XMLNS_XRD_2_0);
320
321 $parser = $service->parser;
322
323 $permitted_tags = array();
324
325 if (in_array(Auth_OpenID_TYPE_1_1, $type_uris) ||
326 in_array(Auth_OpenID_TYPE_1_0, $type_uris)) {
327 $permitted_tags[] = 'openid:Delegate';
328 }
329
330 if (in_array(Auth_OpenID_TYPE_2_0, $type_uris)) {
331 $permitted_tags[] = 'xrd:LocalID';
332 }
333
334 $local_id = null;
335
336 foreach ($permitted_tags as $tag_name) {
337 $tags = $service->getElements($tag_name);
338
339 foreach ($tags as $tag) {
340 $content = $parser->content($tag);
341
342 if ($local_id === null) {
343 $local_id = $content;
344 } else if ($local_id != $content) {
345 return false;
346 }
347 }
348 }
349
350 return $local_id;
351 }
352
353 function filter_MatchesAnyOpenIDType($service)
354 {
355 $uris = $service->getTypes();
356
357 foreach ($uris as $uri) {
358 if (in_array($uri, Auth_OpenID_getOpenIDTypeURIs())) {
359 return true;
360 }
361 }
362
363 return false;
364 }
365
366 function filter_MatchesAnyOpenIDConsumerType(&$service)
367 {
368 $uris = $service->getTypes();
369
370 foreach ($uris as $uri) {
371 if (in_array($uri, Auth_OpenID_getOpenIDConsumerTypeURIs())) {
372 return true;
373 }
374 }
375
376 return false;
377 }
378
379 function Auth_OpenID_bestMatchingService($service, $preferred_types)
380 {
381 // Return the index of the first matching type, or something
382 // higher if no type matches.
383 //
384 // This provides an ordering in which service elements that
385 // contain a type that comes earlier in the preferred types list
386 // come before service elements that come later. If a service
387 // element has more than one type, the most preferred one wins.
388
389 foreach ($preferred_types as $index => $typ) {
390 if (in_array($typ, $service->type_uris)) {
391 return $index;
392 }
393 }
394
395 return count($preferred_types);
396 }
397
398 function Auth_OpenID_arrangeByType($service_list, $preferred_types)
399 {
400 // Rearrange service_list in a new list so services are ordered by
401 // types listed in preferred_types. Return the new list.
402
403 // Build a list with the service elements in tuples whose
404 // comparison will prefer the one with the best matching service
405 $prio_services = array();
406 foreach ($service_list as $index => $service) {
407 $prio_services[] = array(Auth_OpenID_bestMatchingService($service,
408 $preferred_types),
409 $index, $service);
410 }
411
412 sort($prio_services);
413
414 // Now that the services are sorted by priority, remove the sort
415 // keys from the list.
416 foreach ($prio_services as $index => $s) {
417 $prio_services[$index] = $prio_services[$index][2];
418 }
419
420 return $prio_services;
421 }
422
423 // Extract OP Identifier services. If none found, return the rest,
424 // sorted with most preferred first according to
425 // OpenIDServiceEndpoint.openid_type_uris.
426 //
427 // openid_services is a list of OpenIDServiceEndpoint objects.
428 //
429 // Returns a list of OpenIDServiceEndpoint objects."""
430 function Auth_OpenID_getOPOrUserServices($openid_services)
431 {
432 $op_services = Auth_OpenID_arrangeByType($openid_services,
433 array(Auth_OpenID_TYPE_2_0_IDP));
434
435 $openid_services = Auth_OpenID_arrangeByType($openid_services,
436 Auth_OpenID_getOpenIDTypeURIs());
437
438 if ($op_services) {
439 return $op_services;
440 } else {
441 return $openid_services;
442 }
443 }
444
445 function Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services)
446 {
447 $s = array();
448
449 if (!$yadis_services) {
450 return $s;
451 }
452
453 foreach ($yadis_services as $service) {
454 $type_uris = $service->getTypes();
455 $uris = $service->getURIs();
456
457 // If any Type URIs match and there is an endpoint URI
458 // specified, then this is an OpenID endpoint
459 if ($type_uris &&
460 $uris) {
461 foreach ($uris as $service_uri) {
462 $openid_endpoint = new Auth_OpenID_ServiceEndpoint();
463 if ($openid_endpoint->parseService($uri,
464 $service_uri,
465 $type_uris,
466 $service)) {
467 $s[] = $openid_endpoint;
468 }
469 }
470 }
471 }
472
473 return $s;
474 }
475
476 function Auth_OpenID_discoverWithYadis($uri, $fetcher,
477 $endpoint_filter='Auth_OpenID_getOPOrUserServices',
478 $discover_function=null)
479 {
480 // Discover OpenID services for a URI. Tries Yadis and falls back
481 // on old-style <link rel='...'> discovery if Yadis fails.
482
483 // Might raise a yadis.discover.DiscoveryFailure if no document
484 // came back for that URI at all. I don't think falling back to
485 // OpenID 1.0 discovery on the same URL will help, so don't bother
486 // to catch it.
487 if ($discover_function === null) {
488 $discover_function = array('Auth_Yadis_Yadis', 'discover');
489 }
490
491 $openid_services = array();
492
493 $response = call_user_func_array($discover_function,
494 array($uri, $fetcher));
495
496 $yadis_url = $response->normalized_uri;
497 $yadis_services = array();
498
499 if ($response->isFailure() && !$response->isXRDS()) {
500 return array($uri, array());
501 }
502
503 $openid_services = Auth_OpenID_ServiceEndpoint::fromXRDS(
504 $yadis_url,
505 $response->response_text);
506
507 if (!$openid_services) {
508 if ($response->isXRDS()) {
509 return Auth_OpenID_discoverWithoutYadis($uri,
510 $fetcher);
511 }
512
513 // Try to parse the response as HTML to get OpenID 1.0/1.1
514 // <link rel="...">
515 $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML(
516 $yadis_url,
517 $response->response_text);
518 }
519
520 $openid_services = call_user_func_array($endpoint_filter,
521 array($openid_services));
522
523 return array($yadis_url, $openid_services);
524 }
525
526 function Auth_OpenID_discoverURI($uri, $fetcher)
527 {
528 $uri = Auth_OpenID::normalizeUrl($uri);
529 return Auth_OpenID_discoverWithYadis($uri, $fetcher);
530 }
531
532 function Auth_OpenID_discoverWithoutYadis($uri, $fetcher)
533 {
534 $http_resp = @$fetcher->get($uri);
535
536 if ($http_resp->status != 200 and $http_resp->status != 206) {
537 return array($uri, array());
538 }
539
540 $identity_url = $http_resp->final_url;
541
542 // Try to parse the response as HTML to get OpenID 1.0/1.1 <link
543 // rel="...">
544 $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML(
545 $identity_url,
546 $http_resp->body);
547
548 return array($identity_url, $openid_services);
549 }
550
551 function Auth_OpenID_discoverXRI($iname, $fetcher)
552 {
553 $resolver = new Auth_Yadis_ProxyResolver($fetcher);
554 list($canonicalID, $yadis_services) =
555 $resolver->query($iname,
556 Auth_OpenID_getOpenIDTypeURIs(),
557 array('filter_MatchesAnyOpenIDType'));
558
559 $openid_services = Auth_OpenID_makeOpenIDEndpoints($iname,
560 $yadis_services);
561
562 $openid_services = Auth_OpenID_getOPOrUserServices($openid_services);
563
564 for ($i = 0; $i < count($openid_services); $i++) {
565 $openid_services[$i]->canonicalID = $canonicalID;
566 $openid_services[$i]->claimed_id = $canonicalID;
567 $openid_services[$i]->display_identifier = $iname;
568 }
569
570 // FIXME: returned xri should probably be in some normal form
571 return array($iname, $openid_services);
572 }
573
574 function Auth_OpenID_discover($uri, $fetcher)
575 {
576 // If the fetcher (i.e., PHP) doesn't support SSL, we can't do
577 // discovery on an HTTPS URL.
578 if ($fetcher->isHTTPS($uri) && !$fetcher->supportsSSL()) {
579 return array($uri, array());
580 }
581
582 if (Auth_Yadis_identifierScheme($uri) == 'XRI') {
583 $result = Auth_OpenID_discoverXRI($uri, $fetcher);
584 } else {
585 $result = Auth_OpenID_discoverURI($uri, $fetcher);
586 }
587
588 // If the fetcher doesn't support SSL, we can't interact with
589 // HTTPS server URLs; remove those endpoints from the list.
590 if (!$fetcher->supportsSSL()) {
591 $http_endpoints = array();
592 list($new_uri, $endpoints) = $result;
593
594 foreach ($endpoints as $e) {
595 if (!$fetcher->isHTTPS($e->server_url)) {
596 $http_endpoints[] = $e;
597 }
598 }
599
600 $result = array($new_uri, $http_endpoints);
601 }
602
603 return $result;
604 }
605
606