openid.php 6.7 KB

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