PageRenderTime 200ms CodeModel.GetById 81ms app.highlight 75ms RepoModel.GetById 35ms app.codeStats 1ms

/OpenId/Provider.php

https://bitbucket.org/goldie/zend-framework1
PHP | 803 lines | 503 code | 58 blank | 242 comment | 145 complexity | 631a94feb51d46b477b4ed92d3361cff MD5 | raw file
  1<?php
  2
  3/**
  4 * Zend Framework
  5 *
  6 * LICENSE
  7 *
  8 * This source file is subject to the new BSD license that is bundled
  9 * with this package in the file LICENSE.txt.
 10 * It is also available through the world-wide-web at this URL:
 11 * http://framework.zend.com/license/new-bsd
 12 * If you did not receive a copy of the license and are unable to
 13 * obtain it through the world-wide-web, please send an email
 14 * to license@zend.com so we can send you a copy immediately.
 15 *
 16 * @category   Zend
 17 * @package    Zend_OpenId
 18 * @subpackage Zend_OpenId_Provider
 19 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 20 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 21 * @version    $Id: Provider.php 23775 2011-03-01 17:25:24Z ralph $
 22 */
 23
 24/**
 25 * @see Zend_OpenId
 26 */
 27require_once "Zend/OpenId.php";
 28
 29/**
 30 * @see Zend_OpenId_Extension
 31 */
 32require_once "Zend/OpenId/Extension.php";
 33
 34/**
 35 * OpenID provider (server) implementation
 36 *
 37 * @category   Zend
 38 * @package    Zend_OpenId
 39 * @subpackage Zend_OpenId_Provider
 40 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 41 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 42 */
 43class Zend_OpenId_Provider
 44{
 45
 46    /**
 47     * Reference to an implementation of storage object
 48     *
 49     * @var Zend_OpenId_Provider_Storage $_storage
 50     */
 51    private $_storage;
 52
 53    /**
 54     * Reference to an implementation of user object
 55     *
 56     * @var Zend_OpenId_Provider_User $_user
 57     */
 58    private $_user;
 59
 60    /**
 61     * Time to live of association session in secconds
 62     *
 63     * @var integer $_sessionTtl
 64     */
 65    private $_sessionTtl;
 66
 67    /**
 68     * URL to peform interactive user login
 69     *
 70     * @var string $_loginUrl
 71     */
 72    private $_loginUrl;
 73
 74    /**
 75     * URL to peform interactive validation of consumer by user
 76     *
 77     * @var string $_trustUrl
 78     */
 79    private $_trustUrl;
 80
 81    /**
 82     * The OP Endpoint URL
 83     *
 84     * @var string $_opEndpoint
 85     */
 86    private $_opEndpoint;
 87
 88    /**
 89     * Constructs a Zend_OpenId_Provider object with given parameters.
 90     *
 91     * @param string $loginUrl is an URL that provides login screen for
 92     *  end-user (by default it is the same URL with additional GET variable
 93     *  openid.action=login)
 94     * @param string $trustUrl is an URL that shows a question if end-user
 95     *  trust to given consumer (by default it is the same URL with additional
 96     *  GET variable openid.action=trust)
 97     * @param Zend_OpenId_Provider_User $user is an object for communication
 98     *  with User-Agent and store information about logged-in user (it is a
 99     *  Zend_OpenId_Provider_User_Session object by default)
100     * @param Zend_OpenId_Provider_Storage $storage is an object for keeping
101     *  persistent database (it is a Zend_OpenId_Provider_Storage_File object
102     *  by default)
103     * @param integer $sessionTtl is a default time to live for association
104     *   session in seconds (1 hour by default). Consumer must reestablish
105     *   association after that time.
106     */
107    public function __construct($loginUrl = null,
108                                $trustUrl = null,
109                                Zend_OpenId_Provider_User $user = null,
110                                Zend_OpenId_Provider_Storage $storage = null,
111                                $sessionTtl = 3600)
112    {
113        if ($loginUrl === null) {
114            $loginUrl = Zend_OpenId::selfUrl() . '?openid.action=login';
115        } else {
116            $loginUrl = Zend_OpenId::absoluteUrl($loginUrl);
117        }
118        $this->_loginUrl = $loginUrl;
119        if ($trustUrl === null) {
120            $trustUrl = Zend_OpenId::selfUrl() . '?openid.action=trust';
121        } else {
122            $trustUrl = Zend_OpenId::absoluteUrl($trustUrl);
123        }
124        $this->_trustUrl = $trustUrl;
125        if ($user === null) {
126            require_once "Zend/OpenId/Provider/User/Session.php";
127            $this->_user = new Zend_OpenId_Provider_User_Session();
128        } else {
129            $this->_user = $user;
130        }
131        if ($storage === null) {
132            require_once "Zend/OpenId/Provider/Storage/File.php";
133            $this->_storage = new Zend_OpenId_Provider_Storage_File();
134        } else {
135            $this->_storage = $storage;
136        }
137        $this->_sessionTtl = $sessionTtl;
138    }
139
140    /**
141     * Sets the OP Endpoint URL
142     *
143     * @param string $url the OP Endpoint URL
144     * @return null
145     */
146    public function setOpEndpoint($url)
147    {
148        $this->_opEndpoint = $url;
149    }
150
151    /**
152     * Registers a new user with given $id and $password
153     * Returns true in case of success and false if user with given $id already
154     * exists
155     *
156     * @param string $id user identity URL
157     * @param string $password encoded user password
158     * @return bool
159     */
160    public function register($id, $password)
161    {
162        if (!Zend_OpenId::normalize($id) || empty($id)) {
163            return false;
164        }
165        return $this->_storage->addUser($id, md5($id.$password));
166    }
167
168    /**
169     * Returns true if user with given $id exists and false otherwise
170     *
171     * @param string $id user identity URL
172     * @return bool
173     */
174    public function hasUser($id) {
175        if (!Zend_OpenId::normalize($id)) {
176            return false;
177        }
178        return $this->_storage->hasUser($id);
179    }
180
181    /**
182     * Performs login of user with given $id and $password
183     * Returns true in case of success and false otherwise
184     *
185     * @param string $id user identity URL
186     * @param string $password user password
187     * @return bool
188     */
189    public function login($id, $password)
190    {
191        if (!Zend_OpenId::normalize($id)) {
192            return false;
193        }
194        if (!$this->_storage->checkUser($id, md5($id.$password))) {
195            return false;
196        }
197        $this->_user->setLoggedInUser($id);
198        return true;
199    }
200
201    /**
202     * Performs logout. Clears information about logged in user.
203     *
204     * @return void
205     */
206    public function logout()
207    {
208        $this->_user->delLoggedInUser();
209        return true;
210    }
211
212    /**
213     * Returns identity URL of current logged in user or false
214     *
215     * @return mixed
216     */
217    public function getLoggedInUser() {
218        return $this->_user->getLoggedInUser();
219    }
220
221    /**
222     * Retrieve consumer's root URL from request query.
223     * Returns URL or false in case of failure
224     *
225     * @param array $params query arguments
226     * @return mixed
227     */
228    public function getSiteRoot($params)
229    {
230        $version = 1.1;
231        if (isset($params['openid_ns']) &&
232            $params['openid_ns'] == Zend_OpenId::NS_2_0) {
233            $version = 2.0;
234        }
235        if ($version >= 2.0 && isset($params['openid_realm'])) {
236            $root = $params['openid_realm'];
237        } else if ($version < 2.0 && isset($params['openid_trust_root'])) {
238            $root = $params['openid_trust_root'];
239        } else if (isset($params['openid_return_to'])) {
240            $root = $params['openid_return_to'];
241        } else {
242            return false;
243        }
244        if (Zend_OpenId::normalizeUrl($root) && !empty($root)) {
245            return $root;
246        }
247        return false;
248    }
249
250    /**
251     * Allows consumer with given root URL to authenticate current logged
252     * in user. Returns true on success and false on error.
253     *
254     * @param string $root root URL
255     * @param mixed $extensions extension object or array of extensions objects
256     * @return bool
257     */
258    public function allowSite($root, $extensions=null)
259    {
260        $id = $this->getLoggedInUser();
261        if ($id === false) {
262            return false;
263        }
264        if ($extensions !== null) {
265            $data = array();
266            Zend_OpenId_Extension::forAll($extensions, 'getTrustData', $data);
267        } else {
268            $data = true;
269        }
270        $this->_storage->addSite($id, $root, $data);
271        return true;
272    }
273
274    /**
275     * Prohibit consumer with given root URL to authenticate current logged
276     * in user. Returns true on success and false on error.
277     *
278     * @param string $root root URL
279     * @return bool
280     */
281    public function denySite($root)
282    {
283        $id = $this->getLoggedInUser();
284        if ($id === false) {
285            return false;
286        }
287        $this->_storage->addSite($id, $root, false);
288        return true;
289    }
290
291    /**
292     * Delete consumer with given root URL from known sites of current logged
293     * in user. Next time this consumer will try to authenticate the user,
294     * Provider will ask user's confirmation.
295     * Returns true on success and false on error.
296     *
297     * @param string $root root URL
298     * @return bool
299     */
300    public function delSite($root)
301    {
302        $id = $this->getLoggedInUser();
303        if ($id === false) {
304            return false;
305        }
306        $this->_storage->addSite($id, $root, null);
307        return true;
308    }
309
310    /**
311     * Returns list of known consumers for current logged in user or false
312     * if he is not logged in.
313     *
314     * @return mixed
315     */
316    public function getTrustedSites()
317    {
318        $id = $this->getLoggedInUser();
319        if ($id === false) {
320            return false;
321        }
322        return $this->_storage->getTrustedSites($id);
323    }
324
325    /**
326     * Handles HTTP request from consumer
327     *
328     * @param array $params GET or POST variables. If this parameter is omited
329     *  or set to null, then $_GET or $_POST superglobal variable is used
330     *  according to REQUEST_METHOD.
331     * @param mixed $extensions extension object or array of extensions objects
332     * @param Zend_Controller_Response_Abstract $response an optional response
333     *  object to perform HTTP or HTML form redirection
334     * @return mixed
335     */
336    public function handle($params=null, $extensions=null,
337                           Zend_Controller_Response_Abstract $response = null)
338    {
339        if ($params === null) {
340            if ($_SERVER["REQUEST_METHOD"] == "GET") {
341                $params = $_GET;
342            } else if ($_SERVER["REQUEST_METHOD"] == "POST") {
343                $params = $_POST;
344            } else {
345                return false;
346            }
347        }
348        $version = 1.1;
349        if (isset($params['openid_ns']) &&
350            $params['openid_ns'] == Zend_OpenId::NS_2_0) {
351            $version = 2.0;
352        }
353        if (isset($params['openid_mode'])) {
354            if ($params['openid_mode'] == 'associate') {
355                $response = $this->_associate($version, $params);
356                $ret = '';
357                foreach ($response as $key => $val) {
358                    $ret .= $key . ':' . $val . "\n";
359                }
360                return $ret;
361            } else if ($params['openid_mode'] == 'checkid_immediate') {
362                $ret = $this->_checkId($version, $params, 1, $extensions, $response);
363                if (is_bool($ret)) return $ret;
364                if (!empty($params['openid_return_to'])) {
365                    Zend_OpenId::redirect($params['openid_return_to'], $ret, $response);
366                }
367                return true;
368            } else if ($params['openid_mode'] == 'checkid_setup') {
369                $ret = $this->_checkId($version, $params, 0, $extensions, $response);
370                if (is_bool($ret)) return $ret;
371                if (!empty($params['openid_return_to'])) {
372                    Zend_OpenId::redirect($params['openid_return_to'], $ret, $response);
373                }
374                return true;
375            } else if ($params['openid_mode'] == 'check_authentication') {
376                $response = $this->_checkAuthentication($version, $params);
377                $ret = '';
378                foreach ($response as $key => $val) {
379                    $ret .= $key . ':' . $val . "\n";
380                }
381                return $ret;
382            }
383        }
384        return false;
385    }
386
387    /**
388     * Generates a secret key for given hash function, returns RAW key or false
389     * if function is not supported
390     *
391     * @param string $func hash function (sha1 or sha256)
392     * @return mixed
393     */
394    protected function _genSecret($func)
395    {
396        if ($func == 'sha1') {
397            $macLen = 20; /* 160 bit */
398        } else if ($func == 'sha256') {
399            $macLen = 32; /* 256 bit */
400        } else {
401            return false;
402        }
403        return Zend_OpenId::randomBytes($macLen);
404    }
405
406    /**
407     * Processes association request from OpenID consumerm generates secret
408     * shared key and send it back using Diffie-Hellman encruption.
409     * Returns array of variables to push back to consumer.
410     *
411     * @param float $version OpenID version
412     * @param array $params GET or POST request variables
413     * @return array
414     */
415    protected function _associate($version, $params)
416    {
417        $ret = array();
418
419        if ($version >= 2.0) {
420            $ret['ns'] = Zend_OpenId::NS_2_0;
421        }
422
423        if (isset($params['openid_assoc_type']) &&
424            $params['openid_assoc_type'] == 'HMAC-SHA1') {
425            $macFunc = 'sha1';
426        } else if (isset($params['openid_assoc_type']) &&
427            $params['openid_assoc_type'] == 'HMAC-SHA256' &&
428            $version >= 2.0) {
429            $macFunc = 'sha256';
430        } else {
431            $ret['error'] = 'Wrong "openid.assoc_type"';
432            $ret['error-code'] = 'unsupported-type';
433            return $ret;
434        }
435
436        $ret['assoc_type'] = $params['openid_assoc_type'];
437
438        $secret = $this->_genSecret($macFunc);
439
440        if (empty($params['openid_session_type']) ||
441            $params['openid_session_type'] == 'no-encryption') {
442            $ret['mac_key'] = base64_encode($secret);
443        } else if (isset($params['openid_session_type']) &&
444            $params['openid_session_type'] == 'DH-SHA1') {
445            $dhFunc = 'sha1';
446        } else if (isset($params['openid_session_type']) &&
447            $params['openid_session_type'] == 'DH-SHA256' &&
448            $version >= 2.0) {
449            $dhFunc = 'sha256';
450        } else {
451            $ret['error'] = 'Wrong "openid.session_type"';
452            $ret['error-code'] = 'unsupported-type';
453            return $ret;
454        }
455
456        if (isset($params['openid_session_type'])) {
457            $ret['session_type'] = $params['openid_session_type'];
458        }
459
460        if (isset($dhFunc)) {
461            if (empty($params['openid_dh_consumer_public'])) {
462                $ret['error'] = 'Wrong "openid.dh_consumer_public"';
463                return $ret;
464            }
465            if (empty($params['openid_dh_gen'])) {
466                $g = pack('H*', Zend_OpenId::DH_G);
467            } else {
468                $g = base64_decode($params['openid_dh_gen']);
469            }
470            if (empty($params['openid_dh_modulus'])) {
471                $p = pack('H*', Zend_OpenId::DH_P);
472            } else {
473                $p = base64_decode($params['openid_dh_modulus']);
474            }
475
476            $dh = Zend_OpenId::createDhKey($p, $g);
477            $dh_details = Zend_OpenId::getDhKeyDetails($dh);
478
479            $sec = Zend_OpenId::computeDhSecret(
480                base64_decode($params['openid_dh_consumer_public']), $dh);
481            if ($sec === false) {
482                $ret['error'] = 'Wrong "openid.session_type"';
483                $ret['error-code'] = 'unsupported-type';
484                return $ret;
485            }
486            $sec = Zend_OpenId::digest($dhFunc, $sec);
487            $ret['dh_server_public'] = base64_encode(
488                Zend_OpenId::btwoc($dh_details['pub_key']));
489            $ret['enc_mac_key']      = base64_encode($secret ^ $sec);
490        }
491
492        $handle = uniqid();
493        $expiresIn = $this->_sessionTtl;
494
495        $ret['assoc_handle'] = $handle;
496        $ret['expires_in'] = $expiresIn;
497
498        $this->_storage->addAssociation($handle,
499            $macFunc, $secret, time() + $expiresIn);
500
501        return $ret;
502    }
503
504    /**
505     * Performs authentication (or authentication check).
506     *
507     * @param float $version OpenID version
508     * @param array $params GET or POST request variables
509     * @param bool $immediate enables or disables interaction with user
510     * @param mixed $extensions extension object or array of extensions objects
511     * @param Zend_Controller_Response_Abstract $response
512     * @return array
513     */
514    protected function _checkId($version, $params, $immediate, $extensions=null,
515        Zend_Controller_Response_Abstract $response = null)
516    {
517        $ret = array();
518
519        if ($version >= 2.0) {
520            $ret['openid.ns'] = Zend_OpenId::NS_2_0;
521        }
522        $root = $this->getSiteRoot($params);
523        if ($root === false) {
524            return false;
525        }
526
527        if (isset($params['openid_identity']) &&
528            !$this->_storage->hasUser($params['openid_identity'])) {
529            $ret['openid.mode'] = ($immediate && $version >= 2.0) ? 'setup_needed': 'cancel';
530            return $ret;
531        }
532
533        /* Check if user already logged in into the server */
534        if (!isset($params['openid_identity']) ||
535            $this->_user->getLoggedInUser() !== $params['openid_identity']) {
536            $params2 = array();
537            foreach ($params as $key => $val) {
538                if (strpos($key, 'openid_ns_') === 0) {
539                    $key = 'openid.ns.' . substr($key, strlen('openid_ns_'));
540                } else if (strpos($key, 'openid_sreg_') === 0) {
541                    $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_'));
542                } else if (strpos($key, 'openid_') === 0) {
543                    $key = 'openid.' . substr($key, strlen('openid_'));
544                }
545                $params2[$key] = $val;
546            }
547            if ($immediate) {
548                $params2['openid.mode'] = 'checkid_setup';
549                $ret['openid.mode'] = ($version >= 2.0) ? 'setup_needed': 'id_res';
550                $ret['openid.user_setup_url'] = $this->_loginUrl
551                    . (strpos($this->_loginUrl, '?') === false ? '?' : '&')
552                    . Zend_OpenId::paramsToQuery($params2);
553                return $ret;
554            } else {
555                /* Redirect to Server Login Screen */
556                Zend_OpenId::redirect($this->_loginUrl, $params2, $response);
557                return true;
558            }
559        }
560
561        if (!Zend_OpenId_Extension::forAll($extensions, 'parseRequest', $params)) {
562            $ret['openid.mode'] = ($immediate && $version >= 2.0) ? 'setup_needed': 'cancel';
563            return $ret;
564        }
565
566        /* Check if user trusts to the consumer */
567        $trusted = null;
568        $sites = $this->_storage->getTrustedSites($params['openid_identity']);
569        if (isset($params['openid_return_to'])) {
570            $root = $params['openid_return_to'];
571        }
572        if (isset($sites[$root])) {
573            $trusted = $sites[$root];
574        } else {
575            foreach ($sites as $site => $t) {
576                if (strpos($root, $site) === 0) {
577                    $trusted = $t;
578                    break;
579                } else {
580                    /* OpenID 2.0 (9.2) check for realm wild-card matching */
581                    $n = strpos($site, '://*.');
582                    if ($n != false) {
583                        $regex = '/^'
584                               . preg_quote(substr($site, 0, $n+3), '/')
585                               . '[A-Za-z1-9_\.]+?'
586                               . preg_quote(substr($site, $n+4), '/')
587                               . '/';
588                        if (preg_match($regex, $root)) {
589                            $trusted = $t;
590                            break;
591                        }
592                    }
593                }
594            }
595        }
596
597        if (is_array($trusted)) {
598            if (!Zend_OpenId_Extension::forAll($extensions, 'checkTrustData', $trusted)) {
599                $trusted = null;
600            }
601        }
602
603        if ($trusted === false) {
604            $ret['openid.mode'] = 'cancel';
605            return $ret;
606        } else if ($trusted === null) {
607            /* Redirect to Server Trust Screen */
608            $params2 = array();
609            foreach ($params as $key => $val) {
610                if (strpos($key, 'openid_ns_') === 0) {
611                    $key = 'openid.ns.' . substr($key, strlen('openid_ns_'));
612                } else if (strpos($key, 'openid_sreg_') === 0) {
613                    $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_'));
614                } else if (strpos($key, 'openid_') === 0) {
615                    $key = 'openid.' . substr($key, strlen('openid_'));
616                }
617                $params2[$key] = $val;
618            }
619            if ($immediate) {
620                $params2['openid.mode'] = 'checkid_setup';
621                $ret['openid.mode'] = ($version >= 2.0) ? 'setup_needed': 'id_res';
622                $ret['openid.user_setup_url'] = $this->_trustUrl
623                    . (strpos($this->_trustUrl, '?') === false ? '?' : '&')
624                    . Zend_OpenId::paramsToQuery($params2);
625                return $ret;
626            } else {
627                Zend_OpenId::redirect($this->_trustUrl, $params2, $response);
628                return true;
629            }
630        }
631
632        return $this->_respond($version, $ret, $params, $extensions);
633    }
634
635    /**
636     * Perepares information to send back to consumer's authentication request,
637     * signs it using shared secret and send back through HTTP redirection
638     *
639     * @param array $params GET or POST request variables
640     * @param mixed $extensions extension object or array of extensions objects
641     * @param Zend_Controller_Response_Abstract $response an optional response
642     *  object to perform HTTP or HTML form redirection
643     * @return bool
644     */
645    public function respondToConsumer($params, $extensions=null,
646                           Zend_Controller_Response_Abstract $response = null)
647    {
648        $version = 1.1;
649        if (isset($params['openid_ns']) &&
650            $params['openid_ns'] == Zend_OpenId::NS_2_0) {
651            $version = 2.0;
652        }
653        $ret = array();
654        if ($version >= 2.0) {
655            $ret['openid.ns'] = Zend_OpenId::NS_2_0;
656        }
657        $ret = $this->_respond($version, $ret, $params, $extensions);
658        if (!empty($params['openid_return_to'])) {
659            Zend_OpenId::redirect($params['openid_return_to'], $ret, $response);
660        }
661        return true;
662    }
663
664    /**
665     * Perepares information to send back to consumer's authentication request
666     * and signs it using shared secret.
667     *
668     * @param float $version OpenID protcol version
669     * @param array $ret arguments to be send back to consumer
670     * @param array $params GET or POST request variables
671     * @param mixed $extensions extension object or array of extensions objects
672     * @return array
673     */
674    protected function _respond($version, $ret, $params, $extensions=null)
675    {
676        if (empty($params['openid_assoc_handle']) ||
677            !$this->_storage->getAssociation($params['openid_assoc_handle'],
678                $macFunc, $secret, $expires)) {
679            /* Use dumb mode */
680            if (!empty($params['openid_assoc_handle'])) {
681                $ret['openid.invalidate_handle'] = $params['openid_assoc_handle'];
682            }
683            $macFunc = $version >= 2.0 ? 'sha256' : 'sha1';
684            $secret = $this->_genSecret($macFunc);
685            $handle = uniqid();
686            $expiresIn = $this->_sessionTtl;
687            $this->_storage->addAssociation($handle,
688                $macFunc, $secret, time() + $expiresIn);
689            $ret['openid.assoc_handle'] = $handle;
690        } else {
691            $ret['openid.assoc_handle'] = $params['openid_assoc_handle'];
692        }
693        if (isset($params['openid_return_to'])) {
694            $ret['openid.return_to'] = $params['openid_return_to'];
695        }
696        if (isset($params['openid_claimed_id'])) {
697            $ret['openid.claimed_id'] = $params['openid_claimed_id'];
698        }
699        if (isset($params['openid_identity'])) {
700            $ret['openid.identity'] = $params['openid_identity'];
701        }
702
703        if ($version >= 2.0) {
704            if (!empty($this->_opEndpoint)) {
705                $ret['openid.op_endpoint'] = $this->_opEndpoint;
706            } else {
707                $ret['openid.op_endpoint'] = Zend_OpenId::selfUrl();
708            }
709        }
710        $ret['openid.response_nonce'] = gmdate('Y-m-d\TH:i:s\Z') . uniqid();
711        $ret['openid.mode'] = 'id_res';
712
713        Zend_OpenId_Extension::forAll($extensions, 'prepareResponse', $ret);
714
715        $signed = '';
716        $data = '';
717        foreach ($ret as $key => $val) {
718            if (strpos($key, 'openid.') === 0) {
719                $key = substr($key, strlen('openid.'));
720                if (!empty($signed)) {
721                    $signed .= ',';
722                }
723                $signed .= $key;
724                $data .= $key . ':' . $val . "\n";
725            }
726        }
727        $signed .= ',signed';
728        $data .= 'signed:' . $signed . "\n";
729        $ret['openid.signed'] = $signed;
730
731        $ret['openid.sig'] = base64_encode(
732            Zend_OpenId::hashHmac($macFunc, $data, $secret));
733
734        return $ret;
735    }
736
737    /**
738     * Performs authentication validation for dumb consumers
739     * Returns array of variables to push back to consumer.
740     * It MUST contain 'is_valid' variable with value 'true' or 'false'.
741     *
742     * @param float $version OpenID version
743     * @param array $params GET or POST request variables
744     * @return array
745     */
746    protected function _checkAuthentication($version, $params)
747    {
748        $ret = array();
749        if ($version >= 2.0) {
750            $ret['ns'] = Zend_OpenId::NS_2_0;
751        }
752        $ret['openid.mode'] = 'id_res';
753
754        if (empty($params['openid_assoc_handle']) ||
755            empty($params['openid_signed']) ||
756            empty($params['openid_sig']) ||
757            !$this->_storage->getAssociation($params['openid_assoc_handle'],
758                $macFunc, $secret, $expires)) {
759            $ret['is_valid'] = 'false';
760            return $ret;
761        }
762
763        $signed = explode(',', $params['openid_signed']);
764        $data = '';
765        foreach ($signed as $key) {
766            $data .= $key . ':';
767            if ($key == 'mode') {
768                $data .= "id_res\n";
769            } else {
770                $data .= $params['openid_' . strtr($key,'.','_')]."\n";
771            }
772        }
773        if ($this->_secureStringCompare(base64_decode($params['openid_sig']),
774            Zend_OpenId::hashHmac($macFunc, $data, $secret))) {
775            $ret['is_valid'] = 'true';
776        } else {
777            $ret['is_valid'] = 'false';
778        }
779        return $ret;
780    }
781
782    /**
783     * Securely compare two strings for equality while avoided C level memcmp()
784     * optimisations capable of leaking timing information useful to an attacker
785     * attempting to iteratively guess the unknown string (e.g. password) being
786     * compared against.
787     *
788     * @param string $a
789     * @param string $b
790     * @return bool
791     */
792    protected function _secureStringCompare($a, $b)
793    {
794        if (strlen($a) !== strlen($b)) {
795            return false;
796        }
797        $result = 0;
798        for ($i = 0; $i < strlen($a); $i++) {
799            $result |= ord($a[$i]) ^ ord($b[$i]);
800        }
801        return $result == 0;
802    }
803}