<?php |
<?php |
/** |
/** |
* This class provides a simple interface for OpenID (1.1 and 2.0) authentication. |
* This class provides a simple interface for OpenID (1.1 and 2.0) authentication. |
* Supports Yadis discovery. |
* Supports Yadis discovery. |
* The authentication process is stateless/dumb. |
* The authentication process is stateless/dumb. |
* |
* |
* Usage: |
* Usage: |
* Sign-on with OpenID is a two step process: |
* Sign-on with OpenID is a two step process: |
* Step one is authentication with the provider: |
* Step one is authentication with the provider: |
* <code> |
* <code> |
* $openid = new LightOpenID('my-host.example.org'); |
* $openid = new LightOpenID('my-host.example.org'); |
* $openid->identity = 'ID supplied by user'; |
* $openid->identity = 'ID supplied by user'; |
* header('Location: ' . $openid->authUrl()); |
* header('Location: ' . $openid->authUrl()); |
* </code> |
* </code> |
* The provider then sends various parameters via GET, one of them is openid_mode. |
* The provider then sends various parameters via GET, one of them is openid_mode. |
* Step two is verification: |
* Step two is verification: |
* <code> |
* <code> |
* if ($this->data['openid_mode']) { |
* if ($this->data['openid_mode']) { |
* $openid = new LightOpenID('my-host.example.org'); |
* $openid = new LightOpenID('my-host.example.org'); |
* echo $openid->validate() ? 'Logged in.' : 'Failed'; |
* echo $openid->validate() ? 'Logged in.' : 'Failed'; |
* } |
* } |
* </code> |
* </code> |
* |
* |
* Change the 'my-host.example.org' to your domain name. Do NOT use $_SERVER['HTTP_HOST'] |
* Change the 'my-host.example.org' to your domain name. Do NOT use $_SERVER['HTTP_HOST'] |
* for that, unless you know what you are doing. |
* for that, unless you know what you are doing. |
* |
* |
* Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias). |
* Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias). |
* The default values for those are: |
* The default values for those are: |
* $openid->realm = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; |
* $openid->realm = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; |
* $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI']; |
* $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI']; |
* If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess. |
* If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess. |
* |
* |
* AX and SREG extensions are supported. |
* AX and SREG extensions are supported. |
* To use them, specify $openid->required and/or $openid->optional before calling $openid->authUrl(). |
* To use them, specify $openid->required and/or $openid->optional before calling $openid->authUrl(). |
* These are arrays, with values being AX schema paths (the 'path' part of the URL). |
* These are arrays, with values being AX schema paths (the 'path' part of the URL). |
* For example: |
* For example: |
* $openid->required = array('namePerson/friendly', 'contact/email'); |
* $openid->required = array('namePerson/friendly', 'contact/email'); |
* $openid->optional = array('namePerson/first'); |
* $openid->optional = array('namePerson/first'); |
* If the server supports only SREG or OpenID 1.1, these are automaticaly |
* If the server supports only SREG or OpenID 1.1, these are automaticaly |
* mapped to SREG names, so that user doesn't have to know anything about the server. |
* mapped to SREG names, so that user doesn't have to know anything about the server. |
* |
* |
* To get the values, use $openid->getAttributes(). |
* To get the values, use $openid->getAttributes(). |
* |
* |
* |
* |
* The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled. |
* The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled. |
* @author Mewp |
* @author Mewp |
* @copyright Copyright (c) 2010, Mewp |
* @copyright Copyright (c) 2010, Mewp |
* @license http://www.opensource.org/licenses/mit-license.php MIT |
* @license http://www.opensource.org/licenses/mit-license.php MIT |
*/ |
*/ |
class LightOpenID |
class LightOpenID |
{ |
{ |
public $returnUrl |
public $returnUrl |
, $required = array() |
, $required = array() |
, $optional = array() |
, $optional = array() |
, $verify_peer = null |
, $verify_peer = null |
, $capath = null |
, $capath = null |
, $cainfo = null |
, $cainfo = null |
, $data; |
, $data; |
private $identity, $claimed_id; |
private $identity, $claimed_id; |
protected $server, $version, $trustRoot, $aliases, $identifier_select = false |
protected $server, $version, $trustRoot, $aliases, $identifier_select = false |
, $ax = false, $sreg = false, $setup_url = null; |
, $ax = false, $sreg = false, $setup_url = null; |
static protected $ax_to_sreg = array( |
static protected $ax_to_sreg = array( |
'namePerson/friendly' => 'nickname', |
'namePerson/friendly' => 'nickname', |
'contact/email' => 'email', |
'contact/email' => 'email', |
'namePerson' => 'fullname', |
'namePerson' => 'fullname', |
'birthDate' => 'dob', |
'birthDate' => 'dob', |
'person/gender' => 'gender', |
'person/gender' => 'gender', |
'contact/postalCode/home' => 'postcode', |
'contact/postalCode/home' => 'postcode', |
'contact/country/home' => 'country', |
'contact/country/home' => 'country', |
'pref/language' => 'language', |
'pref/language' => 'language', |
'pref/timezone' => 'timezone', |
'pref/timezone' => 'timezone', |
); |
); |
|
|
function __construct($host) |
function __construct($host) |
{ |
{ |
$this->trustRoot = (strpos($host, '://') ? $host : 'http://' . $host); |
$this->trustRoot = (strpos($host, '://') ? $host : 'http://' . $host); |
if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') |
if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') |
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) |
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) |
&& $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') |
&& $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') |
) { |
) { |
$this->trustRoot = (strpos($host, '://') ? $host : 'https://' . $host); |
$this->trustRoot = (strpos($host, '://') ? $host : 'https://' . $host); |
} |
} |
|
|
if(($host_end = strpos($this->trustRoot, '/', 8)) !== false) { |
if(($host_end = @strpos($this->trustRoot, '/', 8)) !== false) { |
$this->trustRoot = substr($this->trustRoot, 0, $host_end); |
$this->trustRoot = substr($this->trustRoot, 0, $host_end); |
} |
} |
|
|
$uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?'); |
$uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?'); |
$this->returnUrl = $this->trustRoot . $uri; |
$this->returnUrl = $this->trustRoot . $uri; |
|
|
$this->data = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET; |
$this->data = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET; |
|
|
if(!function_exists('curl_init') && !in_array('https', stream_get_wrappers())) { |
if(!function_exists('curl_init') && !in_array('https', stream_get_wrappers())) { |
throw new ErrorException('You must have either https wrappers or curl enabled.'); |
throw new ErrorException('You must have either https wrappers or curl enabled.'); |
} |
} |
} |
} |
|
|
function __set($name, $value) |
function __set($name, $value) |
{ |
{ |
switch ($name) { |
switch ($name) { |
case 'identity': |
case 'identity': |
if (strlen($value = trim((String) $value))) { |
if (strlen($value = trim((String) $value))) { |
if (preg_match('#^xri:/*#i', $value, $m)) { |
if (preg_match('#^xri:/*#i', $value, $m)) { |
$value = substr($value, strlen($m[0])); |
$value = substr($value, strlen($m[0])); |
} elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) { |
} elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) { |
$value = "http://$value"; |
$value = "http://$value"; |
} |
} |
if (preg_match('#^https?://[^/]+$#i', $value, $m)) { |
if (preg_match('#^https?://[^/]+$#i', $value, $m)) { |
$value .= '/'; |
$value .= '/'; |
} |
} |
} |
} |
$this->$name = $this->claimed_id = $value; |
$this->$name = $this->claimed_id = $value; |
break; |
break; |
case 'trustRoot': |
case 'trustRoot': |
case 'realm': |
case 'realm': |
$this->trustRoot = trim($value); |
$this->trustRoot = trim($value); |
} |
} |
} |
} |
|
|
function __get($name) |
function __get($name) |
{ |
{ |
switch ($name) { |
switch ($name) { |
case 'identity': |
case 'identity': |
# We return claimed_id instead of identity, |
# We return claimed_id instead of identity, |
# because the developer should see the claimed identifier, |
# because the developer should see the claimed identifier, |
# i.e. what he set as identity, not the op-local identifier (which is what we verify) |
# i.e. what he set as identity, not the op-local identifier (which is what we verify) |
return $this->claimed_id; |
return $this->claimed_id; |
case 'trustRoot': |
case 'trustRoot': |
case 'realm': |
case 'realm': |
return $this->trustRoot; |
return $this->trustRoot; |
case 'mode': |
case 'mode': |
return empty($this->data['openid_mode']) ? null : $this->data['openid_mode']; |
return empty($this->data['openid_mode']) ? null : $this->data['openid_mode']; |
} |
} |
} |
} |
|
|
/** |
/** |
* Checks if the server specified in the url exists. |
* Checks if the server specified in the url exists. |
* |
* |
* @param $url url to check |
* @param $url url to check |
* @return true, if the server exists; false otherwise |
* @return true, if the server exists; false otherwise |
*/ |
*/ |
function hostExists($url) |
function hostExists($url) |
{ |
{ |
if (strpos($url, '/') === false) { |
if (strpos($url, '/') === false) { |
$server = $url; |
$server = $url; |
} else { |
} else { |
$server = @parse_url($url, PHP_URL_HOST); |
$server = @parse_url($url, PHP_URL_HOST); |
} |
} |
|
|
if (!$server) { |
if (!$server) { |
return false; |
return false; |
} |
} |
|
|
return !!gethostbynamel($server); |
return !!gethostbynamel($server); |
} |
} |
|
|
protected function request_curl($url, $method='GET', $params=array()) |
protected function request_curl($url, $method='GET', $params=array()) |
{ |
{ |
$params = http_build_query($params, '', '&'); |
$params = http_build_query($params, '', '&'); |
$curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : '')); |
$curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : '')); |
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); |
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); |
curl_setopt($curl, CURLOPT_HEADER, false); |
curl_setopt($curl, CURLOPT_HEADER, false); |
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); |
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); |
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*')); |
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*')); |
|
|
if($this->verify_peer !== null) { |
if($this->verify_peer !== null) { |
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); |
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); |
if($this->capath) { |
if($this->capath) { |
curl_setopt($curl, CURLOPT_CAPATH, $this->capath); |
curl_setopt($curl, CURLOPT_CAPATH, $this->capath); |
} |
} |
|
|
if($this->cainfo) { |
if($this->cainfo) { |
curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo); |
curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo); |
} |
} |
} |
} |
|
|
if ($method == 'POST') { |
if ($method == 'POST') { |
curl_setopt($curl, CURLOPT_POST, true); |
curl_setopt($curl, CURLOPT_POST, true); |
curl_setopt($curl, CURLOPT_POSTFIELDS, $params); |
curl_setopt($curl, CURLOPT_POSTFIELDS, $params); |
} elseif ($method == 'HEAD') { |
} elseif ($method == 'HEAD') { |
curl_setopt($curl, CURLOPT_HEADER, true); |
curl_setopt($curl, CURLOPT_HEADER, true); |
curl_setopt($curl, CURLOPT_NOBODY, true); |
curl_setopt($curl, CURLOPT_NOBODY, true); |
} else { |
} else { |
curl_setopt($curl, CURLOPT_HTTPGET, true); |
curl_setopt($curl, CURLOPT_HTTPGET, true); |
} |
} |
$response = curl_exec($curl); |
$response = curl_exec($curl); |
|
|
if($method == 'HEAD') { |
if($method == 'HEAD') { |
$headers = array(); |
$headers = array(); |
foreach(explode("\n", $response) as $header) { |
foreach(explode("\n", $response) as $header) { |
$pos = strpos($header,':'); |
$pos = strpos($header,':'); |
$name = strtolower(trim(substr($header, 0, $pos))); |
$name = strtolower(trim(substr($header, 0, $pos))); |
$headers[$name] = trim(substr($header, $pos+1)); |
$headers[$name] = trim(substr($header, $pos+1)); |
} |
} |
|
|
# Updating claimed_id in case of redirections. |
# Updating claimed_id in case of redirections. |
$effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); |
$effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); |
if($effective_url != $url) { |
if($effective_url != $url) { |
$this->identity = $this->claimed_id = $effective_url; |
$this->identity = $this->claimed_id = $effective_url; |
} |
} |
|
|
return $headers; |
return $headers; |
} |
} |
|
|
if (curl_errno($curl)) { |
if (curl_errno($curl)) { |
throw new ErrorException(curl_error($curl), curl_errno($curl)); |
throw new ErrorException(curl_error($curl), curl_errno($curl)); |
} |
} |
|
|
return $response; |
return $response; |
} |
} |
|
|
protected function request_streams($url, $method='GET', $params=array()) |
protected function request_streams($url, $method='GET', $params=array()) |
{ |
{ |
if(!$this->hostExists($url)) { |
if(!$this->hostExists($url)) { |
throw new ErrorException("Could not connect to $url.", 404); |
throw new ErrorException("Could not connect to $url.", 404); |
} |
} |
|
|
$params = http_build_query($params, '', '&'); |
$params = http_build_query($params, '', '&'); |
switch($method) { |
switch($method) { |
case 'GET': |
case 'GET': |
$opts = array( |
$opts = array( |
'http' => array( |
'http' => array( |
'method' => 'GET', |
'method' => 'GET', |
'header' => 'Accept: application/xrds+xml, */*', |
'header' => 'Accept: application/xrds+xml, */*', |
'ignore_errors' => true, |
'ignore_errors' => true, |
), 'ssl' => array( |
), 'ssl' => array( |
'CN_match' => parse_url($url, PHP_URL_HOST), |
'CN_match' => parse_url($url, PHP_URL_HOST), |
), |
), |
); |
); |
$url = $url . ($params ? '?' . $params : ''); |
$url = $url . ($params ? '?' . $params : ''); |
break; |
break; |
case 'POST': |
case 'POST': |
$opts = array( |
$opts = array( |
'http' => array( |
'http' => array( |
'method' => 'POST', |
'method' => 'POST', |
'header' => 'Content-type: application/x-www-form-urlencoded', |
'header' => 'Content-type: application/x-www-form-urlencoded', |
'content' => $params, |
'content' => $params, |
'ignore_errors' => true, |
'ignore_errors' => true, |
), 'ssl' => array( |
), 'ssl' => array( |
'CN_match' => parse_url($url, PHP_URL_HOST), |
'CN_match' => parse_url($url, PHP_URL_HOST), |
), |
), |
); |
); |
break; |
break; |
case 'HEAD': |
case 'HEAD': |
# We want to send a HEAD request, |
# We want to send a HEAD request, |
# but since get_headers doesn't accept $context parameter, |
# but since get_headers doesn't accept $context parameter, |
# we have to change the defaults. |
# we have to change the defaults. |
$default = stream_context_get_options(stream_context_get_default()); |
$default = stream_context_get_options(stream_context_get_default()); |
stream_context_get_default( |
stream_context_get_default( |
array( |
array( |
'http' => array( |
'http' => array( |
'method' => 'HEAD', |
'method' => 'HEAD', |
'header' => 'Accept: application/xrds+xml, */*', |
'header' => 'Accept: application/xrds+xml, */*', |
'ignore_errors' => true, |
'ignore_errors' => true, |
), 'ssl' => array( |
), 'ssl' => array( |
'CN_match' => parse_url($url, PHP_URL_HOST), |
'CN_match' => parse_url($url, PHP_URL_HOST), |
), |
), |
) |
) |
); |
); |
|
|
$url = $url . ($params ? '?' . $params : ''); |
$url = $url . ($params ? '?' . $params : ''); |
$headers_tmp = get_headers ($url); |
$headers_tmp = get_headers ($url); |
if(!$headers_tmp) { |
if(!$headers_tmp) { |
return array(); |
return array(); |
} |
} |
|
|
# Parsing headers. |
# Parsing headers. |
$headers = array(); |
$headers = array(); |
foreach($headers_tmp as $header) { |
foreach($headers_tmp as $header) { |
$pos = strpos($header,':'); |
$pos = strpos($header,':'); |
$name = strtolower(trim(substr($header, 0, $pos))); |
$name = strtolower(trim(substr($header, 0, $pos))); |
$headers[$name] = trim(substr($header, $pos+1)); |
$headers[$name] = trim(substr($header, $pos+1)); |
|
|
# Following possible redirections. The point is just to have |
# Following possible redirections. The point is just to have |
# claimed_id change with them, because get_headers() will |
# claimed_id change with them, because get_headers() will |
# follow redirections automatically. |
# follow redirections automatically. |
# We ignore redirections with relative paths. |
# We ignore redirections with relative paths. |
# If any known provider uses them, file a bug report. |
# If any known provider uses them, file a bug report. |
if($name == 'location') { |
if($name == 'location') { |
if(strpos($headers[$name], 'http') === 0) { |
if(strpos($headers[$name], 'http') === 0) { |
$this->identity = $this->claimed_id = $headers[$name]; |
$this->identity = $this->claimed_id = $headers[$name]; |
} elseif($headers[$name][0] == '/') { |
} elseif($headers[$name][0] == '/') { |
$parsed_url = parse_url($this->claimed_id); |
$parsed_url = parse_url($this->claimed_id); |
$this->identity = |
$this->identity = |
$this->claimed_id = $parsed_url['scheme'] . '://' |
$this->claimed_id = $parsed_url['scheme'] . '://' |
. $parsed_url['host'] |
. $parsed_url['host'] |
. $headers[$name]; |
. $headers[$name]; |
} |
} |
} |
} |
} |
} |
|
|
# And restore them. |
# And restore them. |
stream_context_get_default($default); |
stream_context_get_default($default); |
return $headers; |
return $headers; |
} |
} |
|
|
if($this->verify_peer) { |
if($this->verify_peer) { |
$opts['ssl'] += array( |
$opts['ssl'] += array( |
'verify_peer' => true, |
'verify_peer' => true, |
'capath' => $this->capath, |
'capath' => $this->capath, |
'cafile' => $this->cainfo, |
'cafile' => $this->cainfo, |
); |
); |
} |
} |
|
|
$context = stream_context_create ($opts); |
$context = stream_context_create ($opts); |
|
|
return file_get_contents($url, false, $context); |
return file_get_contents($url, false, $context); |
} |
} |
|
|
protected function request($url, $method='GET', $params=array()) |
protected function request($url, $method='GET', $params=array()) |
{ |
{ |
if (function_exists('curl_init') |
if (function_exists('curl_init') |
&& (!in_array('https', stream_get_wrappers()) || !ini_get('safe_mode') && !ini_get('open_basedir')) |
&& (!in_array('https', stream_get_wrappers()) || !ini_get('safe_mode') && !ini_get('open_basedir')) |
) { |
) { |
return $this->request_curl($url, $method, $params); |
return $this->request_curl($url, $method, $params); |
} |
} |
return $this->request_streams($url, $method, $params); |
return $this->request_streams($url, $method, $params); |
} |
} |
|
|
protected function build_url($url, $parts) |
protected function build_url($url, $parts) |
{ |
{ |
if (isset($url['query'], $parts['query'])) { |
if (isset($url['query'], $parts['query'])) { |
$parts['query'] = $url['query'] . '&' . $parts['query']; |
$parts['query'] = $url['query'] . '&' . $parts['query']; |
} |
} |
|
|
$url = $parts + $url; |
$url = $parts + $url; |
$url = $url['scheme'] . '://' |
$url = $url['scheme'] . '://' |
. (empty($url['username'])?'' |
. (empty($url['username'])?'' |
:(empty($url['password'])? "{$url['username']}@" |
:(empty($url['password'])? "{$url['username']}@" |
|