| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | /** |
|---|
| 4 | * Lepton OpenID 2.0 Consumer |
|---|
| 5 | * |
|---|
| 6 | * @license GPLv3 |
|---|
| 7 | * @author Christopher Vagnetoft <noccy@chillat.net> |
|---|
| 8 | * @author Eddie Roosenmaalen <eddie@roosenmaallen.com> |
|---|
| 9 | */ |
|---|
| 10 | class Openid extends Library { |
|---|
| 11 | |
|---|
| 12 | private $fields = array(); |
|---|
| 13 | private $config = array(); |
|---|
| 14 | private $server; |
|---|
| 15 | private $identityurl = ''; |
|---|
| 16 | private $delegateurl = ''; |
|---|
| 17 | |
|---|
| 18 | function __construct() { |
|---|
| 19 | // Read the configuration |
|---|
| 20 | $this->fields['required'] = |
|---|
| 21 | config::get('lepton.openid.fields.required',array()); |
|---|
| 22 | $this->fields['optional'] = |
|---|
| 23 | config::get('lepton.openid.fields.optional',array()); |
|---|
| 24 | $this->config['urls'] = array( |
|---|
| 25 | 'trustroot' => config::get('lepton.openid.trustroot'), |
|---|
| 26 | 'cancelurl' => config::get('lepton.openid.cancelurl'), |
|---|
| 27 | 'approvedurl' => config::get('lepton.openid.approvedurl') |
|---|
| 28 | ); |
|---|
| 29 | } |
|---|
| 30 | |
|---|
| 31 | public function setTrustRoot($url) { |
|---|
| 32 | $this->config['urls']['trustroot'] = $url; |
|---|
| 33 | } |
|---|
| 34 | public function getTrustRoot() { |
|---|
| 35 | return $this->config['urls']['trustroot']; |
|---|
| 36 | } |
|---|
| 37 | |
|---|
| 38 | public function setCancelURL($url) { |
|---|
| 39 | $this->config['urls']['cancelurl']; |
|---|
| 40 | } |
|---|
| 41 | public function getCancelURL() { |
|---|
| 42 | return $this->config['urls']['cancelurl']; |
|---|
| 43 | } |
|---|
| 44 | |
|---|
| 45 | public function setApprovedURL($url) { |
|---|
| 46 | $this->config['urls']['approvedurl'] = $url; |
|---|
| 47 | } |
|---|
| 48 | public function getApprovedURL() { |
|---|
| 49 | return $this->config['urls']['approvedurl']; |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | /** |
|---|
| 53 | * Sets the identity to use for login. |
|---|
| 54 | * Set any additional needed parameters manually (or by using the |
|---|
| 55 | * lepton.openid.* config space) and call login() to proceed. |
|---|
| 56 | * @param string $identity The identity to log in as |
|---|
| 57 | * @param string $server The server (if cached) |
|---|
| 58 | */ |
|---|
| 59 | public function setIdentity($identity,$server='') { |
|---|
| 60 | $this->identityurl = $this->normalizeIdentity($identity); |
|---|
| 61 | if ($this->identityurl != '') { |
|---|
| 62 | if ($server != '') { |
|---|
| 63 | // Save the server as provided |
|---|
| 64 | $this->server = $server; |
|---|
| 65 | } else { |
|---|
| 66 | try { |
|---|
| 67 | // Get the server from the page |
|---|
| 68 | $page = http::get($this->identityurl); |
|---|
| 69 | $dom = DOMDocument::loadHTML($page); |
|---|
| 70 | $domx = new DOMXPath($dom); |
|---|
| 71 | // Look for delegates |
|---|
| 72 | $servers = $domx->query('*/link[@rel=\'openid.delegate\']'); |
|---|
| 73 | foreach($servers as $server) { |
|---|
| 74 | $this->delegateurl = $server->getAttribute('href'); |
|---|
| 75 | break; |
|---|
| 76 | } |
|---|
| 77 | // Look for servers |
|---|
| 78 | $servers = $domx->query('*/link[@rel=\'openid.server\']'); |
|---|
| 79 | foreach($servers as $server) { |
|---|
| 80 | $this->server = $server->getAttribute('href'); |
|---|
| 81 | return; |
|---|
| 82 | } |
|---|
| 83 | throw new OpenIDException('No servers found at identity URL',OpenIDException::ERR_BAD_IDENTITY); |
|---|
| 84 | } catch (HTTPException $e) { |
|---|
| 85 | throw new OpenIDException('Unable to retrieve URL',OpenIDException::ERR_GENERIC); |
|---|
| 86 | } |
|---|
| 87 | } |
|---|
| 88 | } else { |
|---|
| 89 | throw new OpenIDException('Invalid identity URL',OpenIDException::ERR_BAD_IDENTITY); |
|---|
| 90 | } |
|---|
| 91 | } |
|---|
| 92 | |
|---|
| 93 | /** |
|---|
| 94 | * Returns the assigned identity or delegated identity. |
|---|
| 95 | * @return string The identity |
|---|
| 96 | */ |
|---|
| 97 | public function getIdentity() { |
|---|
| 98 | return $this->identityurl; |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | /** |
|---|
| 102 | * Return the resolved server |
|---|
| 103 | * @return string The resolved server |
|---|
| 104 | */ |
|---|
| 105 | public function getServer() { |
|---|
| 106 | return $this->server; |
|---|
| 107 | } |
|---|
| 108 | |
|---|
| 109 | /** |
|---|
| 110 | * Normalize and return the identity |
|---|
| 111 | * Makes the identity lowercase and prepends http:// if not there |
|---|
| 112 | * already. |
|---|
| 113 | * @param string $url The identity URL |
|---|
| 114 | * @return string The normalized identity URL |
|---|
| 115 | */ |
|---|
| 116 | private function normalizeIdentity($url) { |
|---|
| 117 | $url = String::toLowerCase($url); |
|---|
| 118 | if ((stripos($url, 'http://') === false) && (stripos($url, 'https://') === false)){ |
|---|
| 119 | $url = 'http://'.$url; |
|---|
| 120 | } |
|---|
| 121 | return $url; |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | /** |
|---|
| 125 | * Send the user off to the provider to sign in |
|---|
| 126 | */ |
|---|
| 127 | public function login() { |
|---|
| 128 | // Check that everything is in order |
|---|
| 129 | |
|---|
| 130 | if (($this->config['urls']['trustroot'] != '') && |
|---|
| 131 | ($this->config['urls']['cancelurl'] != '') && |
|---|
| 132 | ($this->config['urls']['approvedurl'] != '')) { |
|---|
| 133 | |
|---|
| 134 | $url = $this->getRedirectURl(); |
|---|
| 135 | response::redirect($url); |
|---|
| 136 | // echo $url; |
|---|
| 137 | |
|---|
| 138 | } else { |
|---|
| 139 | throw new OpenIDException('Check your configuration!',OpenIDException::ERR_CONFIGURATION); |
|---|
| 140 | } |
|---|
| 141 | |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | /** |
|---|
| 145 | * Get the URL to redirect to for authentication |
|---|
| 146 | */ |
|---|
| 147 | private function getRedirectURL() { |
|---|
| 148 | |
|---|
| 149 | // TODO: Might be a good idea to save the original identity here |
|---|
| 150 | // in the case of delegation. If the main identity is saved, it |
|---|
| 151 | // can be pulled in place of the returned identity from the provider |
|---|
| 152 | if ($this->delegateurl != '') { |
|---|
| 153 | Session::set('lepton.openid.delegatedidentity',$this->identityurl); |
|---|
| 154 | $identity = $this->delegateurl; |
|---|
| 155 | } else { |
|---|
| 156 | Session::clear('lepton.openid.delegatedidentity'); |
|---|
| 157 | $identity = $this->identityurl; |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | $params = array(); |
|---|
| 161 | $params['openid.return_to'] = urlencode($this->config['urls']['approvedurl']); |
|---|
| 162 | $params['openid.mode'] = 'checkid_setup'; |
|---|
| 163 | $params['openid.identity'] = urlencode($identity); |
|---|
| 164 | $params['openid.trust_root'] = urlencode($this->config['urls']['trustroot']); |
|---|
| 165 | |
|---|
| 166 | if (isset($this->fields['required']) |
|---|
| 167 | && (count($this->fields['required']) > 0)) { |
|---|
| 168 | $params['openid.sreg.required'] = implode(',',$this->fields['required']); |
|---|
| 169 | } |
|---|
| 170 | if (isset($this->fields['optional']) |
|---|
| 171 | && (count($this->fields['optional']) > 0)) { |
|---|
| 172 | $params['openid.sreg.optional'] = implode(',',$this->fields['optional']); |
|---|
| 173 | } |
|---|
| 174 | return $this->server . "?". $this->arrayToURL($params); |
|---|
| 175 | } |
|---|
| 176 | |
|---|
| 177 | private function arrayToURL($arr) { |
|---|
| 178 | if (!is_array($arr)){ |
|---|
| 179 | return false; |
|---|
| 180 | } |
|---|
| 181 | $query = ''; |
|---|
| 182 | foreach($arr as $key => $value){ |
|---|
| 183 | $query .= $key . "=" . $value . "&"; |
|---|
| 184 | } |
|---|
| 185 | return $query; |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | /** |
|---|
| 189 | * Validate the response |
|---|
| 190 | */ |
|---|
| 191 | public function validate() { |
|---|
| 192 | |
|---|
| 193 | $this->setIdentity(urldecode($_GET['openid_identity'])); |
|---|
| 194 | $params = array( |
|---|
| 195 | 'openid.assoc_handle' => urlencode($_GET['openid_assoc_handle']), |
|---|
| 196 | 'openid.signed' => urlencode($_GET['openid_signed']), |
|---|
| 197 | 'openid.sig' => urlencode($_GET['openid_sig']) |
|---|
| 198 | ); |
|---|
| 199 | // Send only required parameters to confirm validity |
|---|
| 200 | $arr_signed = explode(",",str_replace('sreg.','sreg_',$_GET['openid_signed'])); |
|---|
| 201 | for ($i=0; $i<count($arr_signed); $i++){ |
|---|
| 202 | $s = str_replace('sreg_','sreg.', $arr_signed[$i]); |
|---|
| 203 | $c = $_GET['openid_' . $arr_signed[$i]]; |
|---|
| 204 | // if ($c != ""){ |
|---|
| 205 | $params['openid.' . $s] = urlencode($c); |
|---|
| 206 | // } |
|---|
| 207 | } |
|---|
| 208 | $params['openid.mode'] = "check_authentication"; |
|---|
| 209 | |
|---|
| 210 | $openid_server = $this->server; |
|---|
| 211 | if ($openid_server == false){ |
|---|
| 212 | return false; |
|---|
| 213 | } |
|---|
| 214 | $response = http::post($openid_server,$this->arrayToURL($params)); |
|---|
| 215 | $data = $this->splitResponse($response); |
|---|
| 216 | |
|---|
| 217 | $this->identityurl = Session::get('lepton.openid.delegatedidentity',$this->identityurl); |
|---|
| 218 | |
|---|
| 219 | if ($data['is_valid'] == "true") { |
|---|
| 220 | return true; |
|---|
| 221 | }else{ |
|---|
| 222 | return false; |
|---|
| 223 | } |
|---|
| 224 | |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | private function splitResponse($response) { |
|---|
| 228 | $r = array(); |
|---|
| 229 | $response = explode("\n", $response); |
|---|
| 230 | foreach($response as $line) { |
|---|
| 231 | $line = trim($line); |
|---|
| 232 | if ($line != "") { |
|---|
| 233 | list($key, $value) = explode(":", $line, 2); |
|---|
| 234 | $r[trim($key)] = trim($value); |
|---|
| 235 | } |
|---|
| 236 | } |
|---|
| 237 | return $r; |
|---|
| 238 | } |
|---|
| 239 | |
|---|
| 240 | public function setRequiredFields($fields) { |
|---|
| 241 | if (is_array($fields)) { |
|---|
| 242 | $this->fields['required'] = $fields; |
|---|
| 243 | } else { |
|---|
| 244 | $this->fields['required'] = explode(',',$fields); |
|---|
| 245 | } |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | public function setOptionalFields($fields) { |
|---|
| 249 | if (is_array($fields)) { |
|---|
| 250 | $this->fields['optional'] = $fields; |
|---|
| 251 | } else { |
|---|
| 252 | $this->fields['optional'] = explode(',',$fields); |
|---|
| 253 | } |
|---|
| 254 | } |
|---|
| 255 | |
|---|
| 256 | /** |
|---|
| 257 | * Return a requested field |
|---|
| 258 | */ |
|---|
| 259 | public function getField($key) { |
|---|
| 260 | return $_GET['openid_sreg_'.$key]; |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | |
|---|
| 264 | public function loginUser() { |
|---|
| 265 | |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | } |
|---|
| 269 | |
|---|
| 270 | class OpenIDException extends Exception { |
|---|
| 271 | const ERR_GENERIC = 0; |
|---|
| 272 | const ERR_BAD_IDENTITY = 1; /// No identity at this URL or bad URL |
|---|
| 273 | const ERR_CONFIGURATION = 2; |
|---|
| 274 | } |
|---|
| 275 | |
|---|
| 276 | ?> |
|---|