openid.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <?php
  2. /*
  3. Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
  4. This file is part of the Fat-Free Framework (http://fatfree.sf.net).
  5. THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
  6. ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  7. IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  8. PURPOSE.
  9. Please see the license.txt file for more information.
  10. */
  11. namespace Web;
  12. //! OpenID consumer
  13. class OpenID extends \Magic {
  14. protected
  15. //! OpenID provider endpoint URL
  16. $url,
  17. //! HTTP request parameters
  18. $args=array();
  19. /**
  20. * Determine OpenID provider
  21. * @return string|FALSE
  22. * @param $proxy string
  23. **/
  24. protected function discover($proxy) {
  25. // Normalize
  26. if (!preg_match('/https?:\/\//i',$this->args['identity']))
  27. $this->args['identity']='http://'.$this->args['identity'];
  28. $url=parse_url($this->args['identity']);
  29. // Remove fragment; reconnect parts
  30. $this->args['identity']=$url['scheme'].'://'.
  31. (isset($url['user'])?
  32. ($url['user'].
  33. (isset($url['pass'])?(':'.$url['pass']):'').'@'):'').
  34. strtolower($url['host']).(isset($url['path'])?$url['path']:'/').
  35. (isset($url['query'])?('?'.$url['query']):'');
  36. // HTML-based discovery of OpenID provider
  37. $req=\Web::instance()->
  38. request($this->args['identity'],array('proxy'=>$proxy));
  39. if (!$req)
  40. return FALSE;
  41. $type=array_values(preg_grep('/Content-Type:/',$req['headers']));
  42. if ($type &&
  43. preg_match('/application\/xrds\+xml|text\/xml/',$type[0]) &&
  44. ($sxml=simplexml_load_string($req['body'])) &&
  45. ($xrds=json_decode(json_encode($sxml),TRUE)) &&
  46. isset($xrds['XRD'])) {
  47. // XRDS document
  48. $svc=$xrds['XRD']['Service'];
  49. if (isset($svc[0]))
  50. $svc=$svc[0];
  51. if (preg_grep('/http:\/\/specs\.openid\.net\/auth\/2.0\/'.
  52. '(?:server|signon)/',$svc['Type'])) {
  53. $this->args['provider']=$svc['URI'];
  54. if (isset($svc['LocalID']))
  55. $this->args['localidentity']=$svc['LocalID'];
  56. elseif (isset($svc['CanonicalID']))
  57. $this->args['localidentity']=$svc['CanonicalID'];
  58. }
  59. $this->args['server']=$svc['URI'];
  60. if (isset($svc['Delegate']))
  61. $this->args['delegate']=$svc['Delegate'];
  62. }
  63. else {
  64. $len=strlen($req['body']);
  65. $ptr=0;
  66. // Parse document
  67. while ($ptr<$len)
  68. if (preg_match(
  69. '/^<link\b((?:\h+\w+\h*=\h*'.
  70. '(?:"(?:.+?)"|\'(?:.+?)\'))*)\h*\/?>/is',
  71. substr($req['body'],$ptr),$parts)) {
  72. if ($parts[1] &&
  73. // Process attributes
  74. preg_match_all('/\b(rel|href)\h*=\h*'.
  75. '(?:"(.+?)"|\'(.+?)\')/s',$parts[1],$attr,
  76. PREG_SET_ORDER)) {
  77. $node=array();
  78. foreach ($attr as $kv)
  79. $node[$kv[1]]=isset($kv[2])?$kv[2]:$kv[3];
  80. if (isset($node['rel']) &&
  81. preg_match('/openid2?\.(\w+)/',
  82. $node['rel'],$var) &&
  83. isset($node['href']))
  84. $this->args[$var[1]]=$node['href'];
  85. }
  86. $ptr+=strlen($parts[0]);
  87. }
  88. else
  89. $ptr++;
  90. }
  91. // Get OpenID provider's endpoint URL
  92. if (isset($this->args['provider'])) {
  93. // OpenID 2.0
  94. $this->args['ns']='http://specs.openid.net/auth/2.0';
  95. if (isset($this->args['localidentity']))
  96. $this->args['identity']=$this->args['localidentity'];
  97. if (isset($this->args['trust_root']))
  98. $this->args['realm']=$this->args['trust_root'];
  99. }
  100. elseif (isset($this->args['server'])) {
  101. // OpenID 1.1
  102. $this->args['ns']='http://openid.net/signon/1.1';
  103. if (isset($this->args['delegate']))
  104. $this->args['identity']=$this->args['delegate'];
  105. }
  106. if (isset($this->args['provider'])) {
  107. // OpenID 2.0
  108. if (empty($this->args['claimed_id']))
  109. $this->args['claimed_id']=$this->args['identity'];
  110. return $this->args['provider'];
  111. }
  112. elseif (isset($this->args['server']))
  113. // OpenID 1.1
  114. return $this->args['server'];
  115. else
  116. return FALSE;
  117. }
  118. /**
  119. * Initiate OpenID authentication sequence; Return FALSE on failure
  120. * or redirect to OpenID provider URL
  121. * @return bool
  122. * @param $proxy string
  123. * @param $attr array
  124. * @param $reqd string|array
  125. **/
  126. function auth($proxy=NULL,$attr=array(),array $reqd=NULL) {
  127. $fw=\Base::instance();
  128. $root=$fw->get('SCHEME').'://'.$fw->get('HOST');
  129. if (empty($this->args['trust_root']))
  130. $this->args['trust_root']=$root.$fw->get('BASE').'/';
  131. if (empty($this->args['return_to']))
  132. $this->args['return_to']=$root.$_SERVER['REQUEST_URI'];
  133. $this->args['mode']='checkid_setup';
  134. if ($this->url=$this->discover($proxy)) {
  135. if ($attr) {
  136. $this->args['ns.ax']='http://openid.net/srv/ax/1.0';
  137. $this->args['ax.mode']='fetch_request';
  138. foreach ($attr as $key=>$val)
  139. $this->args['ax.type.'.$key]=$val;
  140. $this->args['ax.required']=is_string($reqd)?
  141. $reqd:implode(',',$reqd);
  142. }
  143. $var=array();
  144. foreach ($this->args as $key=>$val)
  145. $var['openid.'.$key]=$val;
  146. $fw->reroute($this->url.'?'.http_build_query($var));
  147. }
  148. return FALSE;
  149. }
  150. /**
  151. * Return TRUE if OpenID verification was successful
  152. * @return bool
  153. * @param $proxy string
  154. **/
  155. function verified($proxy=NULL) {
  156. preg_match_all('/(?<=^|&)openid\.([^=]+)=([^&]+)/',
  157. $_SERVER['QUERY_STRING'],$matches,PREG_SET_ORDER);
  158. foreach ($matches as $match)
  159. $this->args[$match[1]]=urldecode($match[2]);
  160. if (isset($this->args['mode']) &&
  161. $this->args['mode']!='error' &&
  162. $this->url=$this->discover($proxy)) {
  163. $this->args['mode']='check_authentication';
  164. $var=array();
  165. foreach ($this->args as $key=>$val)
  166. $var['openid.'.$key]=$val;
  167. $req=\Web::instance()->request(
  168. $this->url,
  169. array(
  170. 'method'=>'POST',
  171. 'content'=>http_build_query($var),
  172. 'proxy'=>$proxy
  173. )
  174. );
  175. return (bool)preg_match('/is_valid:true/i',$req['body']);
  176. }
  177. return FALSE;
  178. }
  179. /**
  180. * Return OpenID response fields
  181. * @return array
  182. **/
  183. function response() {
  184. return $this->args;
  185. }
  186. /**
  187. * Return TRUE if OpenID request parameter exists
  188. * @return bool
  189. * @param $key string
  190. **/
  191. function exists($key) {
  192. return isset($this->args[$key]);
  193. }
  194. /**
  195. * Bind value to OpenID request parameter
  196. * @return string
  197. * @param $key string
  198. * @param $val string
  199. **/
  200. function set($key,$val) {
  201. return $this->args[$key]=$val;
  202. }
  203. /**
  204. * Return value of OpenID request parameter
  205. * @return mixed
  206. * @param $key string
  207. **/
  208. function get($key) {
  209. return isset($this->args[$key])?$this->args[$key]:NULL;
  210. }
  211. /**
  212. * Remove OpenID request parameter
  213. * @return NULL
  214. * @param $key
  215. **/
  216. function clear($key) {
  217. unset($this->args[$key]);
  218. }
  219. }