OpenIdConsumer.class.php

Go to the documentation of this file.
00001 <?php
00002 /***************************************************************************
00003  *   Copyright (C) 2007 by Anton E. Lebedevich                             *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU Lesser General Public License as        *
00007  *   published by the Free Software Foundation; either version 3 of the    *
00008  *   License, or (at your option) any later version.                       *
00009  *                                                                         *
00010  ***************************************************************************/
00011 /* $Id: OpenIdConsumer.class.php 4687 2007-12-09 18:57:18Z voxus $ */
00012 
00021     final class OpenIdConsumer
00022     {
00023         const DIFFIE_HELLMAN_P = '155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443';
00024         const DIFFIE_HELLMAN_G = 2;
00025         const ASSOCIATION_TYPE = 'HMAC-SHA1';
00026         
00027         private $randomSource   = null;
00028         private $numberFactory  = null;
00029         private $httpClient     = null;
00030         
00031         public function __construct(
00032             RandomSource $randomSource,
00033             BigNumberFactory $numberFactory,
00034             HttpClient $httpClient
00035         )
00036         {
00037             $this->randomSource = $randomSource;
00038             $this->numberFactory = $numberFactory;
00039             $this->httpClient = $httpClient;
00040         }
00041         
00045         public static function create(
00046             RandomSource $randomSource,
00047             BigNumberFactory $numberFactory,
00048             HttpClient $httpClient
00049         )
00050         {
00051             return new self($randomSource, $numberFactory, $httpClient);
00052         }
00053         
00061         public function associate(
00062             HttpUrl $server,
00063             OpenIdConsumerAssociationManager $manager
00064         )
00065         {
00066             Assert::isTrue($server->isValid());
00067             
00068             if ($association = $manager->findByServer($server))
00069                 return $association;
00070             
00071             $dhParameters = new DiffieHellmanParameters(
00072                 $this->numberFactory->makeNumber(self::DIFFIE_HELLMAN_G),
00073                 $this->numberFactory->makeNumber(self::DIFFIE_HELLMAN_P)
00074             );
00075             
00076             $keyPair = DiffieHellmanKeyPair::generate(
00077                 $dhParameters,
00078                 $this->randomSource
00079             );
00080             
00081             $request = HttpRequest::create()->
00082                 setMethod(HttpMethod::post())->
00083                 setUrl($server)->
00084                 setPostVar('openid.mode', 'associate')->
00085                 setPostVar('openid.assoc_type', self::ASSOCIATION_TYPE)->
00086                 setPostVar('openid.session_type', 'DH-SHA1')->
00087                 setPostVar(
00088                     'openid.dh_modulus',
00089                     base64_encode($dhParameters->getModulus()->toBinary())
00090                 )->
00091                 setPostVar(
00092                     'openid.dh_gen',
00093                     base64_encode($dhParameters->getGen()->toBinary())
00094                 )->
00095                 setPostVar(
00096                     'openid.dh_consumer_public',
00097                     base64_encode($keyPair->getPublic()->toBinary())
00098                 );
00099             
00100             $response = $this->httpClient->send($request);
00101             if ($response->getStatus()->getId() != HttpStatus::CODE_200)
00102                 throw new OpenIdException('bad response code from server');
00103             
00104             $result = $this->parseKeyValueFormat($response->getBody());
00105             
00106             if (empty($result['assoc_handle']))
00107                 throw new OpenIdException('can\t live without handle');
00108             
00109             if (
00110                 !isset($result['assoc_type'])
00111                 || $result['assoc_type'] !== self::ASSOCIATION_TYPE
00112             )
00113                 throw new OpenIdException('bad association type');
00114             
00115             if (
00116                 !isset($result['expires_in'])
00117                 || !is_numeric($result['expires_in'])
00118             )
00119                 throw new OpenIdException('bad expires');
00120             
00121             if (
00122                 isset($result['session_type'])
00123                 && $result['session_type'] == 'DH-SHA1'
00124                 && isset($result['dh_server_public'])
00125             ) {
00126                 $secret =
00127                     sha1(
00128                         $keyPair->
00129                             makeSharedKey(
00130                                 $this->numberFactory->makeFromBinary(
00131                                     base64_decode($result['dh_server_public'])
00132                                 )
00133                             )->
00134                             toBinary(),
00135                         true
00136                     )
00137                     ^ base64_decode($result['enc_mac_key']);
00138             } elseif (
00139                 empty($result['session_type'])
00140                 && isset($result['mac_key'])
00141             ) {
00142                 $secret = base64_decode($result['mac_key']);
00143             } else {
00144                 throw new OpenIdException('no secret in answer');
00145             }
00146             
00147             return $manager->makeAndSave(
00148                 $result['assoc_handle'],
00149                 $result['assoc_type'],
00150                 $secret,
00151                 Timestamp::makeNow()->
00152                     modify('+ '.$result['expires_in'].' seconds'),
00153                 $server
00154             );
00155         }
00156         
00157         private function makeCheckIdRequest(
00158             OpenIdCredentials $credentials,
00159             HttpUrl $returnTo,
00160             $trustRoot = null,
00161             $association = null
00162         )
00163         {
00164             Assert::isTrue($returnTo->isValid());
00165             
00166             $view = RedirectView::create(
00167                 $credentials->getServer()->toString()
00168             );
00169             
00170             $model = Model::create()->
00171                 set(
00172                     'openid.identity',
00173                     $credentials->getRealId()->toString()
00174                 )->
00175                 set(
00176                     'openid.return_to',
00177                     $returnTo->toString()
00178                 );
00179             
00180             if ($association) {
00181                 Assert::isTrue(
00182                     $association instanceof OpenIdConsumerAssociation
00183                     && $association->getServer()->toString()
00184                         == $credentials->getServer()->toString()
00185                 );
00186                 
00187                 $model->set(
00188                     'openid.assoc_handle',
00189                     $association->getHandle()
00190                 );
00191             }
00192             
00193             if ($trustRoot) {
00194                 Assert::isTrue(
00195                     $trustRoot instanceof HttpUrl
00196                     && $trustRoot->isValid()
00197                 );
00198                 
00199                 $model->set(
00200                     'openid.trust_root',
00201                     $trustRoot->toString()
00202                 );
00203             }
00204             
00205             return ModelAndView::create()->setModel($model)->setView($view);
00206         }
00207         
00217         public function checkIdImmediate(
00218             OpenIdCredentials $credentials,
00219             HttpUrl $returnTo,
00220             $trustRoot = null,
00221             $association = null
00222         )
00223         {
00224             $mav = $this->makeCheckIdRequest(
00225                 $credentials,
00226                 $returnTo,
00227                 $trustRoot,
00228                 $association
00229             );
00230             
00231             $mav->getModel()->
00232                 set('openid.mode', 'checkid_immediate');
00233             
00234             return $mav;
00235         }
00236         
00246         public function checkIdSetup(
00247             OpenIdCredentials $credentials,
00248             HttpUrl $returnTo,
00249             $trustRoot = null,
00250             $association = null
00251         )
00252         {
00253             $mav = $this->makeCheckIdRequest(
00254                 $credentials,
00255                 $returnTo,
00256                 $trustRoot,
00257                 $association
00258             );
00259             
00260             $mav->getModel()->
00261                 set('openid.mode', 'checkid_setup');
00262             
00263             return $mav;
00264         }
00265         
00272         public function doContinue(HttpRequest $request, $manager = null)
00273         {
00274             if ($manager)
00275                 Assert::isTrue($manager instanceof OpenIdConsumerAssociationManager);
00276             
00277             $parameters = $this->parseGetParameters($request->getGet());
00278             
00279             if (!isset($parameters['openid.mode']))
00280                 throw new WrongArgumentException('not an openid request');
00281             
00282             if ($parameters['openid.mode'] == 'id_res') {
00283                 if (isset($parameters['openid.user_setup_url'])) {
00284                     $setupUrl = HttpUrl::create()->parse(
00285                         $parameters['openid.user_setup_url']
00286                     );
00287                     
00288                     Assert::isTrue($setupUrl->isValid());
00289                     
00290                     return new OpenIdConsumerSetupRequired($setupUrl);
00291                 }
00292             } elseif ($parameters['openid.mode'] = 'cancel') {
00293                 return new OpenIdConsumerCancel();
00294             }
00295             
00296             if (!isset($parameters['openid.assoc_handle']))
00297                 throw new WrongArgumentException('no association handle');
00298             
00299             if (!isset($parameters['openid.identity']))
00300                 throw new WrongArgumentException('no identity');
00301             
00302             $identity =
00303                 HttpUrl::create()->
00304                 parse($parameters['openid.identity']);
00305             
00306             Assert::isTrue($identity->isValid(), 'invalid identity');
00307             $identity->makeComparable();
00308             
00309             $signedFields = array();
00310             if (isset($parameters['openid.signed'], $parameters['openid.sig'])) {
00311                 $signedFields = explode(',', $parameters['openid.signed']);
00312                 
00313                 if (!in_array('identity', $signedFields))
00314                     throw new WrongArgumentException('identity must be signed');
00315             } else
00316                 throw new WrongArgumentException('no signature in response');
00317             
00318             if (
00319                 $manager
00320                 && (
00321                     $association = $manager->findByHandle(
00322                         $parameters['openid.assoc_handle'],
00323                         self::ASSOCIATION_TYPE
00324                     )
00325                 )
00326                 && !isset($parameters['openid.invalidate_handle'])
00327             ) { // smart mode
00328                 $tokenContents = null;
00329                 foreach ($signedFields as $signedField) {
00330                     $tokenContents .=
00331                         $signedField
00332                         .':'
00333                         .$parameters['openid.'.$signedField]
00334                         ."\n";
00335                 }
00336                 
00337                 if (
00338                     base64_encode(
00339                         CryptoFunctions::hmacsha1(
00340                             $association->getSecret(),
00341                             $tokenContents
00342                         )
00343                     )
00344                     != $parameters['openid.sig']
00345                 )
00346                     throw new WrongArgumentException('signature mismatch');
00347                 
00348                 return new OpenIdConsumerPositive($identity);
00349                 
00350             } elseif (
00351                 !$manager
00352                 || isset($parameters['openid.invalidate_handle'])
00353             ) { // dumb or handle invalidation mode
00354                 if ($this->checkAuthentication($parameters, $manager))
00355                     return new OpenIdConsumerPositive($identity);
00356                 else
00357                     return new OpenIdConsumerFail();
00358             }
00359             
00360             Assert::isUnreachable();
00361         }
00362         
00366         private function checkAuthentication(
00367             /* array */ $parameters,
00368             $manager = null
00369         )
00370         {
00371             $credentials = new OpenIdCredentials(
00372                 HttpUrl::create()->parse($parameters['openid.identity']),
00373                 $this->httpClient
00374             );
00375             
00376             $request = HttpRequest::create()->
00377                 setMethod(HttpMethod::post())->
00378                 setUrl($credentials->getServer());
00379             
00380             if (isset($parameters['openid.invalidate_handle']) && $manager)
00381                 $request->setPostVar(
00382                     'openid.invalidate_handle',
00383                     $parameters['openid.invalidate_handle']
00384                 );
00385             
00386             foreach (explode(',', $parameters['openid.signed']) as $key) {
00387                 $key = 'openid.'.$key;
00388                 $request->setPostVar($key, $parameters[$key]);
00389             }
00390             
00391             $request->
00392                 setPostVar('openid.mode', 'check_authentication')->
00393                 setPostVar(
00394                     'openid.assoc_handle',
00395                     $parameters['openid.assoc_handle']
00396                 )->
00397                 setPostVar(
00398                     'openid.sig',
00399                     $parameters['openid.sig']
00400                 )->
00401                 setPostVar(
00402                     'openid.signed',
00403                     $parameters['openid.signed']
00404                 );
00405             
00406             $response = $this->httpClient->send($request);
00407             if ($response->getStatus()->getId() != HttpStatus::CODE_200)
00408                 throw new OpenIdException('bad response code from server');
00409             
00410             $result = $this->parseKeyValueFormat($response->getBody());
00411             
00412             if (
00413                 !isset($result['is_valid'])
00414                 || (
00415                     $result['is_valid'] !== 'true'
00416                     &&
00417                     $result['is_valid'] !== 'false'
00418                 )
00419             )
00420                 throw new OpenIdException('strange response given');
00421             
00422             if ($result['is_valid'] === 'true') {
00423                 if (isset($result['invalidate_handle']) && $manager) {
00424                     $manager->purgeByHandle($result['invalidate_handle']);
00425                 }
00426                 
00427                 return true;
00428             } elseif ($result['is_valid'] === 'false')
00429                 return false;
00430             
00431             Assert::isUnreachable();
00432         }
00433         
00434         private function parseKeyValueFormat($raw)
00435         {
00436             $result = array();
00437             $lines = explode("\n", $raw);
00438             
00439             foreach ($lines as $line) {
00440                 if (!empty($line) && strpos($line, ':') !== false) {
00441                     list($key, $value) = explode(':', $line, 2);
00442                     $result[trim($key)] = trim($value);
00443                 }
00444             }
00445             
00446             return $result;
00447         }
00448         
00449         private function parseGetParameters(/* array */ $get)
00450         {
00451             $result = array();
00452             foreach ($get as $key => $value) {
00453                 if (strpos($key, 'openid') === 0) {
00454                     $key = preg_replace('/^openid_/', 'openid.', $key);
00455                     $result[$key] = $value;
00456                 }
00457             }
00458             
00459             return $result;
00460         }
00461     }
00462 ?>

Generated on Sun Dec 9 21:56:24 2007 for onPHP by  doxygen 1.5.4