]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/Auth/OpenID/Discover.php
Merge remote-tracking branch 'upstream/master'
[quix0rs-gnu-social.git] / extlib / 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