ILIAS  release_4-3 Revision
 All Data Structures Namespaces Files Functions Variables Groups Pages
Discover.php
Go to the documentation of this file.
1 <?php
2 
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 
26 {
27  return array(Auth_OpenID_TYPE_2_0_IDP,
32 }
33 
35 {
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) {
47  return 'OpenID 2.0 IDP';
49  return 'OpenID 2.0';
51  return 'OpenID 1.2';
53  return 'OpenID 1.1';
55  return 'OpenID 1.0';
57  return 'OpenID relying party';
58  }
59 }
60 
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  }
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  }
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  }
143  function compatibilityMode()
144  {
145  return $this->preferredNamespace() != Auth_OpenID_OPENID2_NS;
146  }
148  function isOPIdentifier()
149  {
150  return in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris);
151  }
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  }
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  }
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()) {
249  $discoveryResult->normalized_uri,
250  $discoveryResult->response_text);
251  } else {
253  $discoveryResult->normalized_uri,
254  $discoveryResult->response_text);
255  }
256  }
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  }
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 }
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',
317 
318  $service->parser->registerNamespace('xrd',
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 }
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 }
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 }
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 }
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,
437 
438  if ($op_services) {
439  return $op_services;
440  } else {
441  return $openid_services;
442  }
443 }
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 }
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()) {
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 }
526 function Auth_OpenID_discoverURI($uri, $fetcher)
527 {
528  $uri = Auth_OpenID::normalizeUrl($uri);
529  return Auth_OpenID_discoverWithYadis($uri, $fetcher);
530 }
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 }
551 function Auth_OpenID_discoverXRI($iname, $fetcher)
552 {
553  $resolver = new Auth_Yadis_ProxyResolver($fetcher);
554  list($canonicalID, $yadis_services) =
555  $resolver->query($iname,
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 }
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