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