<?php

/**
 * Tests for the OpenID consumer.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

require_once 'Auth/OpenID/CryptUtil.php';
require_once 'Auth/Yadis/HTTPFetcher.php';
require_once 'Auth/OpenID/DiffieHellman.php';
require_once 'Auth/OpenID/FileStore.php';
require_once 'Auth/OpenID/KVForm.php';
require_once 'Auth/OpenID/Consumer.php';
require_once 'Auth/OpenID/Server.php';
require_once 'Auth/OpenID/Nonce.php';
require_once 'Auth/OpenID/SReg.php';
require_once 'Auth/OpenID/Message.php';
require_once 'Tests/Auth/OpenID/MemStore.php';

/*
 * Convenience function to create a SuccessResponse with the given
 * arguments, all signed.
 */
function mkSuccess($endpoint, $q)
{
    $signed_list = [];
    foreach (array_keys($q) as $k) {
        $signed_list[] = 'openid.' . $k;
    }
    return new Auth_OpenID_SuccessResponse($endpoint,
                                           Auth_OpenID_Message::fromOpenIDArgs($q),
                                           $signed_list);
}

class FastConsumerSession extends Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
    function __construct($dh = null)
    {
        if ($dh === null) {
            $dh = new Auth_OpenID_DiffieHellman(100389557, 2);
        }

        $this->dh = $dh;
    }
}

function setConsumerSession($con)
{
    $con->session_types = ['DH-SHA1' => 'FastConsumerSession'];
}

global $_Auth_OpenID_assocs;
$_Auth_OpenID_assocs = [
    ['another 20-byte key.', 'Snarky'],
    [str_repeat("\x00", 20), 'Zeros'],
];

function Auth_OpenID_parse($qs)
{
    $result = [];
    $parts = explode("&", $qs);
    foreach ($parts as $pair) {
        list($key, $value) = explode("=", $pair, 2);
        assert(!array_key_exists($key, $result));
        $result[urldecode($key)] = urldecode($value);
    }
    return $result;
}

function Auth_OpenID_associate($qs, $assoc_secret, $assoc_handle)
{
    $query_data = Auth_OpenID_parse($qs);

    assert($query_data['openid.mode'] == 'associate');
    assert($query_data['openid.assoc_type'] == 'HMAC-SHA1');

    $reply_dict = [
        'assoc_type' => 'HMAC-SHA1',
        'assoc_handle' => $assoc_handle,
        'expires_in' => '600',
    ];

    if (defined('Auth_OpenID_NO_MATH_SUPPORT')) {
        assert(count($query_data) == 2);
        $message = Auth_OpenID_Message::fromPostArgs($query_data);
        $session = Auth_OpenID_PlainTextServerSession::fromMessage($message);
    } else {
        assert((count($query_data) == 6) || (count($query_data) == 4));
        assert($query_data['openid.mode'] == 'associate');
        assert($query_data['openid.session_type'] == 'DH-SHA1');

        $message = Auth_OpenID_Message::fromPostArgs($query_data);

        $session = Auth_OpenID_DiffieHellmanSHA1ServerSession::fromMessage($message);
        $reply_dict['session_type'] = 'DH-SHA1';

    }

    $reply_dict = array_merge($reply_dict, $session->answer($assoc_secret));
    return Auth_OpenID_KVForm::fromArray($reply_dict);
}

class Auth_OpenID_TestFetcher extends Auth_Yadis_HTTPFetcher {
    function __construct($user_url, $user_page,
                                     $assoc_secret, $assoc_handle)
    {
        $this->get_responses = [
            $user_url =>
                new Auth_Yadis_HTTPResponse($user_url,
                    200,
                    [],
                    $user_page),
        ];
        $this->assoc_secret = $assoc_secret;
        $this->assoc_handle = $assoc_handle;
        $this->num_assocs = 0;
    }

    function response($url, $body)
    {
        if ($body === null) {
            return new Auth_Yadis_HTTPResponse($url, 404, [], 'Not found');
        } else {
            return new Auth_Yadis_HTTPResponse($url, 200, [], $body);
        }
    }

    function get($url)
    {
        if (array_key_exists($url, $this->get_responses)) {
            return $this->get_responses[$url];
        } else {
            return $this->response($url, null);
        }
    }

    function _checkAuth($url, $body)
    {
        $query_data = Auth_OpenID_parse($body);
        $expected = [
            'openid.mode' => 'check_authentication',
            'openid.signed' => 'assoc_handle,sig,signed',
            'openid.sig' => 'fake',
            'openid.assoc_handle' => $this->assoc_handle,
        ];

        if ($query_data == $expected) {
            return new Auth_Yadis_HTTPResponse($url, 200, [], "is_valid:true\n");
        } else {
            return new Auth_Yadis_HTTPResponse($url, 400, [],
                                                "error:bad check_authentication query\n");
        }
    }

    function post($url, $body)
    {
        if (strpos($body, 'openid.mode=associate') !== false) {
            $response = Auth_OpenID_associate($body, $this->assoc_secret,
                                              $this->assoc_handle);
            $this->num_assocs++;
            return $this->response($url, $response);
        } elseif (strpos($body, 'openid.mode=check_authentication') !== false) {
            return $this->_checkAuth($url, $body);
        }

        return $this->response($url, null);
    }
}

global $_Auth_OpenID_user_page_pat;
$_Auth_OpenID_user_page_pat = "<html>
  <head>
    <title>A user page</title>
    %s
  </head>
  <body>
    blah blah
  </body>
</html>";

global $_Auth_OpenID_server_url;
$_Auth_OpenID_server_url = "http://server.example.com/";

global $_Auth_OpenID_consumer_url;
$_Auth_OpenID_consumer_url = "http://consumer.example.com/";

class Tests_Auth_OpenID_Consumer extends PHPUnit_Framework_TestCase {

    function _run($consumer, $user_url, $mode, $delegate_url,
                  $fetcher, $store, $immediate)
    {
        global $_Auth_OpenID_consumer_url,
            $_Auth_OpenID_server_url;

        if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
            setConsumerSession($consumer);
        }

        $endpoint = new Auth_OpenID_ServiceEndpoint();
        $endpoint->claimed_id = $user_url;
        $endpoint->server_url = $_Auth_OpenID_server_url;
        $endpoint->local_id = $delegate_url;
        $endpoint->type_uris = [Auth_OpenID_TYPE_1_1];

        $result = $consumer->begin($endpoint);

        $return_to = $_Auth_OpenID_consumer_url;
        $trust_root = $_Auth_OpenID_consumer_url;
        $redirect_url = $result->redirectURL($trust_root, $return_to,
                                             $immediate);

        $parsed = parse_url($redirect_url);
        $qs = $parsed['query'];
        $q = Auth_OpenID_parse($qs);
        $new_return_to = $q['openid.return_to'];
        unset($q['openid.return_to']);

        $expected = [
            'openid.mode' => $mode,
            'openid.identity' => $delegate_url,
            'openid.trust_root' => $trust_root,
        ];

        if ($consumer->_use_assocs) {
            $expected['openid.assoc_handle'] = $fetcher->assoc_handle;
        }

        $this->assertEquals($expected, $q);
        $this->assertEquals(0, strpos($redirect_url, $_Auth_OpenID_server_url));
        $this->assertEquals(0, strpos($new_return_to, $return_to));

	$parsed = parse_url($new_return_to);
	$query = Auth_OpenID_parse($parsed['query']);

        $query = array_merge($query, [
            'openid.mode' => 'id_res',
            'openid.return_to' => $new_return_to,
            'openid.identity' => $delegate_url,
            'openid.assoc_handle' => $fetcher->assoc_handle,
        ]);

        if (!$consumer->_use_assocs) {
            $query['openid.signed'] =
                'assoc_handle,mode,signed,identity';
            $query['openid.assoc_handle'] = $fetcher->assoc_handle;
            $query['openid.sig'] = 'fake';
        }

        $message = Auth_OpenID_Message::fromPostArgs($query);

        if ($consumer->_use_assocs) {
            $assoc = $store->getAssociation($_Auth_OpenID_server_url,
                                            $fetcher->assoc_handle);
            $message = $assoc->signMessage($message);
        }

        $result = $consumer->complete($message, $result->endpoint, $new_return_to);

        $this->assertEquals(Auth_OpenID_SUCCESS, $result->status);
        $this->assertEquals($result->identity_url, $user_url);
    }

    function _test_success($user_url, $delegate_url, $links, $immediate = false)
    {
        global $_Auth_OpenID_filestore_base_dir,
            $_Auth_OpenID_server_url,
            $_Auth_OpenID_user_page_pat,
            $_Auth_OpenID_assocs;

        $store = new Tests_Auth_OpenID_MemStore();

        if ($immediate) {
            $mode = 'checkid_immediate';
        } else {
            $mode = 'checkid_setup';
        }

        $user_page = sprintf($_Auth_OpenID_user_page_pat, $links);
        $fetcher = new Auth_OpenID_TestFetcher($user_url, $user_page,
                                               $_Auth_OpenID_assocs[0][0],
                                               $_Auth_OpenID_assocs[0][1]);

        $consumer = new Auth_OpenID_GenericConsumer($store);
        $consumer->fetcher =& $fetcher;

        $expected_num_assocs = 0;
        $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);
        $this->_run($consumer, $user_url, $mode, $delegate_url,
                    $fetcher, $store, $immediate);

        if ($consumer->_use_assocs) {
            $expected_num_assocs += 1;
        }

        $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);

        // Test that doing it again uses the existing association
        $this->_run($consumer, $user_url, $mode, $delegate_url,
                    $fetcher, $store, $immediate);

        $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);

        // Another association is created if we remove the existing one
        $store->removeAssociation($_Auth_OpenID_server_url,
                                  $fetcher->assoc_handle);

        $this->_run($consumer, $user_url, $mode, $delegate_url,
                    $fetcher, $store, $immediate);

        if ($consumer->_use_assocs) {
            $expected_num_assocs += 1;
        }

        $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);

        // Test that doing it again uses the existing association
        $this->_run($consumer, $user_url, $mode, $delegate_url,
                    $fetcher, $store, $immediate);

        $this->assertEquals($expected_num_assocs, $fetcher->num_assocs);
    }

    function test_success()
    {
        global $_Auth_OpenID_server_url;

        $user_url = 'http://www.example.com/user.html';
        $links = sprintf('<link rel="openid.server" href="%s" />',
                         $_Auth_OpenID_server_url);

        $delegate_url = 'http://consumer.example.com/user';
        $delegate_links = sprintf('<link rel="openid.server" href="%s" />'.
                                  '<link rel="openid.delegate" href="%s" />',
                                  $_Auth_OpenID_server_url, $delegate_url);

        $this->_test_success($user_url, $user_url, $links);
        $this->_test_success($user_url, $user_url, $links, true);
        $this->_test_success($user_url, $delegate_url, $delegate_links);
        $this->_test_success($user_url, $delegate_url, $delegate_links, true);
    }
}

class ConfigurableConsumer extends Auth_OpenID_GenericConsumer {
  var $return_to_check_disabled = false;

  function disableReturnToChecking() {
    $this->return_to_check_disabled = true;
  }

  function complete($message, $endpoint, $return_to) {
    if ($this->return_to_check_disabled) {
      $return_to = null;
    }

    return parent::complete($message, $endpoint, $return_to);
  }

  function _checkReturnTo($unused, $unused2) {
    if ($this->return_to_check_disabled) {
      return true;
    } else {
      return parent::_checkReturnTo($unused, $unused2);
    }
  }
}

class _TestIdRes extends PHPUnit_Framework_TestCase {
    var $consumer_class = 'ConfigurableConsumer';

    function setUp()
    {
        $this->store = new Tests_Auth_OpenID_MemStore();
        $cl = $this->consumer_class;
        $this->consumer = new $cl($this->store);
        $this->return_to = "http://some.host/path";
        $this->endpoint = new Auth_OpenID_ServiceEndpoint();

        $this->server_id = "sirod";
        $this->server_url = "serlie";
        $this->consumer_id = "consu";

        $this->endpoint->claimed_id = $this->consumer_id;
        $this->endpoint->server_url = $this->server_url;
        $this->endpoint->local_id = $this->server_id;
        $this->endpoint->type_uris = [Auth_OpenID_TYPE_1_1];
    }
}

class Tests_Auth_OpenID_Consumer_TestSetupNeeded extends _TestIdRes {
    function failUnlessSetupNeeded($expected_setup_url, $message)
    {
        if ($this->consumer._checkSetupNeeded($message)) {
            $this->assertEquals($expected_setup_url,
                                $message->getArg(Auth_OpenID_OPENID_NS,
                                                 'user_setup_url'));
        } else {
            $this->fail("Expected to find an immediate-mode response");
        }
    }

    function test_setupNeededOpenID1()
    {
        // The minimum conditions necessary to trigger Setup Needed
        $setup_url = 'http://unittest/setup-here';
        $message = Auth_OpenID_Message::fromPostArgs([
            'opaenid.mode' => 'id_res',
            'openid.user_setup_url' => $setup_url
        ]);
        $this->assertTrue($message->isOpenID1());
        $this->failUnlessSetupNeeded($setup_url, $message);
    }

    function test_setupNeededOpenID1_extra()
    {
        // Extra stuff along with setup_url still trigger Setup Needed
        $setup_url = 'http://unittest/setup-here';
        $message = Auth_OpenID_Message::fromPostArgs([
            'openid.mode' => 'id_res',
            'openid.user_setup_url' => $setup_url,
            'openid.identity' => 'bogus'
        ]);
        $this->assertTrue($message->isOpenID1());
        $this->failUnlessSetupNeeded($setup_url, $message);
    }

    function test_noSetupNeededOpenID1()
    {
        // When the user_setup_url is missing on an OpenID 1 message,
        // we assume that it's not a cancel response to
        // checkid_immediate
        $message = Auth_OpenID_Message::fromOpenIDArgs(['mode' => 'id_res']);
        $this->assertTrue($message->isOpenID1());

        // No SetupNeededError raised
        $this->consumer->_checkSetupNeeded($message);
    }

    function test_setupNeededOpenID2()
    {
        $message = Auth_OpenID_Message::fromOpenIDArgs([
            'mode' => 'setup_needed',
            'ns' => Auth_OpenID_OPENID2_NS
        ]);
        $this->assertTrue($message->isOpenID2());
        $response = $this->consumer->complete($message, null, null);
        $this->assertEquals('setup_needed', $response->status);
        $this->assertEquals(null, $response->setup_url);
    }

    function test_setupNeededDoesntWorkForOpenID1()
    {
        $message = Auth_OpenID_Message::fromOpenIDArgs([
                     'mode' => 'setup_needed'
        ]);

        $this->assertFalse($this->consumer._checkSetupNeeded($message));

        $response = $this->consumer->complete($message, null, null);
        $this->assertEquals('failure', $response->status);
        $this->assertTrue(strpos($response->message, 'Invalid openid.mode') === 0);
    }

    function test_noSetupNeededOpenID2()
    {
        $message = Auth_OpenID_Message::fromOpenIDArgs([
            'mode' => 'id_res',
            'game' => 'puerto_rico',
            'ns' => Auth_OpenID_OPENID2_NS
        ]);
        $this->assertTrue($message->isOpenID2());

        $this->assertFalse($this->consumer._checkSetupNeeded($message));
    }
}

class IdResCheckForFieldsTest extends _TestIdRes {
  function setUp() {
    # Argh.
    $v = null;
    $this->consumer = new Auth_OpenID_GenericConsumer($v);
  }

  function successTest($openid_args, $signed_list) {
    $message = Auth_OpenID_Message::fromOpenIDArgs($openid_args);
    $message->setArg(Auth_OpenID_OPENID_NS, 'signed', implode(',', $signed_list));
    $result = $this->consumer->_idResCheckForFields($message);
    $this->assertFalse(Auth_OpenID::isFailure($result));
  }

  function test_openid1Success() {
    $this->successTest(
        [
            'return_to' => 'return',
            'assoc_handle' => 'assoc handle',
            'sig' => 'a signature',
            'identity' => 'someone',
        ],
        ['return_to', 'identity']);
  }

  function test_openid2Success() {
      $this->successTest(
          [
              'ns' => Auth_OpenID_OPENID2_NS,
              'return_to' => 'return',
              'assoc_handle' => 'assoc handle',
              'sig' => 'a signature',
              'op_endpoint' => 'my favourite server',
              'response_nonce' => 'use only once',
          ],
          ['return_to', 'response_nonce', 'assoc_handle', 'op_endpoint']);
  }

    function test_openid2Success_identifiers()
    {
        $this->successTest(
            [
                'ns' => Auth_OpenID_OPENID2_NS,
                'return_to' => 'return',
                'assoc_handle' => 'assoc handle',
                'sig' => 'a signature',
                'claimed_id' => 'i claim to be me',
                'identity' => 'my server knows me as me',
                'op_endpoint' => 'my favourite server',
                'response_nonce' => 'use only once',
            ],
            [
                'return_to',
                'response_nonce',
                'identity',
                'claimed_id',
                'assoc_handle',
                'op_endpoint',
            ]);
    }

    function endswith($str, $it)
    {
      $it_len = strlen($it);
      $total = strlen($str);
      return (strpos($str, $it) === $total - $it_len);
  }

  function missingFieldTest($openid_args) {
    $message = Auth_OpenID_Message::fromOpenIDArgs($openid_args);
    $result = $this->consumer->_idResCheckForFields($message);
    $this->assertTrue(Auth_OpenID::isFailure($result));
    $this->assertTrue(strpos($result->message, 'Missing required') === 0);
  }

  function missingSignedTest($openid_args) {
    $message = Auth_OpenID_Message::fromOpenIDArgs($openid_args);
    $result = $this->consumer->_idResCheckForFields($message);
    $this->assertTrue(Auth_OpenID::isFailure($result));
    if (Auth_OpenID::isFailure($result)) {
        $this->assertTrue($this->endswith($result->message, 'not signed'),
                          $result->message);
    }
  }

    function test_openid1Missing_returnToSig()
    {
        $this->missingSignedTest(
            [
                'return_to' => 'return',
                'assoc_handle' => 'assoc handle',
                'sig' => 'a signature',
                'identity' => 'someone',
                'signed' => 'identity,assoc_handle',
            ]);
    }

    function test_openid2Missing_opEndpointSig()
    {
        $this->missingSignedTest(
            [
                'ns' => Auth_OpenID_OPENID2_NS,
                'return_to' => 'return',
                'assoc_handle' => 'assoc handle',
                'sig' => 'a signature',
                'identity' => 'someone',
                'op_endpoint' => 'the endpoint',
                'signed' => 'identity,return_to,assoc_handle',
            ]);
    }

    function test_openid1Missing_identitySig()
    {
        $this->missingSignedTest(
            [
                'return_to' => 'return',
                'assoc_handle' => 'assoc handle',
                'sig' => 'a signature',
                'identity' => 'someone',
                'signed' => 'eturn_to',
            ]);
    }

    function test_openid1MissingReturnTo()
    {
        $this->missingFieldTest(
            [
                'assoc_handle' => 'assoc handle',
                'sig' => 'a signature',
                'identity' => 'someone',
                'signed' => 'return_to,identity',
            ]);
    }

    function test_openid1MissingAssocHandle()
    {
        $this->missingFieldTest(
            [
                'return_to' => 'return',
                'sig' => 'a signature',
                'identity' => 'someone',
                'signed' => 'return_to,identity',
            ]);
    }
}

define('E_CHECK_AUTH_HAPPENED', 'checkauth occurred');
define('E_MOCK_FETCHER_EXCEPTION', 'mock fetcher exception');
define('E_ASSERTION_ERROR', 'assertion error');

class _CheckAuthDetectingConsumer extends ConfigurableConsumer {
    function _verifyDiscoveryResults($message, $endpoint)
    {
        return $endpoint;
    }

    function _idResCheckNonce($message, $endpoint)
    {
        return true;
    }

    function _checkAuth($query, $server_url)
    {
        __raiseError(E_CHECK_AUTH_HAPPENED);
    }
}

global $GOODSIG;
$GOODSIG = "[A Good Signature]";

class GoodAssociation {
    var $expiresIn = 3600;
    var $handle = "-blah-";

    function getExpiresIn()
    {
        return $this->expiresIn;
    }

    function checkMessageSignature($message)
    {
        global $GOODSIG;
        return $message->getArg(Auth_OpenID_OPENID_NS, 'sig') == $GOODSIG;
    }
}

class GoodAssocStore extends Tests_Auth_OpenID_MemStore {
    function getAssociation($server_url, $handle = null)
    {
        return new GoodAssociation();
    }
}

class TestIdResCheckSignature extends _TestIdRes {
    function setUp()
    {
        global $GOODSIG;

        parent::setUp();
        $this->assoc = new GoodAssociation();
        $this->assoc->handle = "{not_dumb}";
        $this->store->storeAssociation($this->endpoint->server_url, $this->assoc);

        $this->message = Auth_OpenID_Message::fromPostArgs([
            'openid.mode'=> 'id_res',
            'openid.identity'=> '=example',
            'openid.sig'=> $GOODSIG,
            'openid.assoc_handle'=> $this->assoc->handle,
            'openid.signed'=> 'mode,identity,assoc_handle,signed',
            'frobboz'=> 'banzit'
        ]);
    }

    function test_sign()
    {
        // assoc_handle to assoc with good sig
        $this->consumer->_idResCheckSignature($this->message,
                                              $this->endpoint->server_url);
    }

    function test_signFailsWithBadSig()
    {
        $this->message.setArg(Auth_OpenID_OPENID_NS, 'sig', 'BAD SIGNATURE');
        $result = $this->consumer->_idResCheckSignature($this->message, $this->endpoint->server_url);
        $this->assertTrue(Auth_OpenID::isFailure($result));
    }
}

class StatelessConsumer1 extends ConfigurableConsumer {
    function _processCheckAuthResponse($response, $server_url)
    {
        return true;
    }

    function _makeKVPost($args, $server_url)
    {
        return [];
    }
}

class Tests_Auth_OpenID_Stateless1 extends _TestIdRes {
    var $consumer_class = "StatelessConsumer1";

    function setUp()
    {
        global $GOODSIG;

        parent::setUp();
        $this->assoc = new GoodAssociation();
        $this->assoc->handle = "{not_dumb}";
        $this->store->storeAssociation($this->endpoint->server_url, $this->assoc);

        $this->message = Auth_OpenID_Message::fromPostArgs([
            'openid.mode'=> 'id_res',
            'openid.identity'=> '=example',
            'openid.sig'=> $GOODSIG,
            'openid.assoc_handle'=> $this->assoc->handle,
            'openid.signed'=> 'mode,identity,assoc_handle,signed',
            'frobboz'=> 'banzit'
        ]);
    }

    function test_stateless()
    {
        // assoc_handle missing assoc, consumer._checkAuth returns
        // goodthings
        $this->message->setArg(Auth_OpenID_OPENID_NS, "assoc_handle", "dumbHandle");
        $this->consumer->_idResCheckSignature($this->message,
                                              $this->endpoint->server_url);
    }
}

class StatelessConsumer2 extends ConfigurableConsumer {
    function _checkAuth($_, $__)
    {
        return false;
    }
}

class Tests_Auth_OpenID_Stateless2 extends _TestIdRes {
    var $consumer_class = "StatelessConsumer2";

    function setUp()
    {
        global $GOODSIG;

        parent::setUp();
        $this->assoc = new GoodAssociation();
        $this->assoc->handle = "{not_dumb}";
        $this->store->storeAssociation($this->endpoint->server_url, $this->assoc);

        $this->message = Auth_OpenID_Message::fromPostArgs([
            'openid.mode'=> 'id_res',
            'openid.identity'=> '=example',
            'openid.sig'=> $GOODSIG,
            'openid.assoc_handle'=> $this->assoc->handle,
            'openid.signed'=> 'mode,identity,assoc_handle,signed',
            'frobboz'=> 'banzit'
        ]);
    }

    function test_statelessRaisesError()
    {
        // assoc_handle missing assoc, consumer._checkAuth returns
        // goodthings
        $this->message->setArg(Auth_OpenID_OPENID_NS, "assoc_handle",
                               "dumbHandle");
        $result = $this->consumer->_idResCheckSignature($this->message,
                                                        $this->endpoint->server_url);
        $this->assertTrue(Auth_OpenID::isFailure($result));
    }
}

class Tests_Auth_OpenID_Consumer_CheckNonceTest extends _TestIdRes {
    function setUp()
    {
        parent::setUp();
        $this->consumer->openid1_nonce_query_arg_name = 'nonce';
    }

    function test_openid1Success()
    {
        // use consumer-generated nonce
        $nonce_value = Auth_OpenID_mkNonce();
        $this->return_to = sprintf('http://rt.unittest/?nonce=%s',
                                   $nonce_value);
        $this->response = Auth_OpenID_Message::fromOpenIDArgs(
                            ['return_to' => $this->return_to]);
        $this->response->setArg(Auth_OpenID_BARE_NS, 'nonce', $nonce_value);

        $result = $this->consumer->_idResCheckNonce($this->response, $this->endpoint);
        $this->assertFalse(Auth_OpenID::isFailure($result));
    }

    function test_openid1Missing()
    {
        // use consumer-generated nonce
        $this->response = Auth_OpenID_Message::fromOpenIDArgs([]);
        $n = $this->consumer->_idResGetNonceOpenID1($this->response, $this->endpoint);
        $this->assertTrue($n === null);
    }

    function test_consumerNonceOpenID2()
    {
        // OpenID 2 does not use consumer-generated nonce
        $this->return_to = sprintf('http://rt.unittest/?nonce=%s',
            Auth_OpenID_mkNonce());
        $this->response = Auth_OpenID_Message::fromOpenIDArgs(
            [
                'return_to' => $this->return_to,
                'ns' => Auth_OpenID_OPENID2_NS,
            ]);
        $result = $this->consumer->_idResCheckNonce($this->response, $this->endpoint);
        $this->assertTrue(Auth_OpenID::isFailure($result));
    }

    function test_serverNonce()
    {
        // use server-generated nonce
        $this->response = Auth_OpenID_Message::fromOpenIDArgs(
            [
                'ns' => Auth_OpenID_OPENID2_NS,
                'response_nonce' => Auth_OpenID_mkNonce(),
            ]);
        $this->consumer->_idResCheckNonce($this->response, $this->endpoint);
    }

    function test_serverNonceOpenID1()
    {
        // OpenID 1 does not use server-generated nonce
        $this->response = Auth_OpenID_Message::fromOpenIDArgs(
            [
                'ns' => Auth_OpenID_OPENID1_NS,
                'return_to' => 'http://return.to/',
                'response_nonce' => Auth_OpenID_mkNonce(),
            ]);
        $result = $this->consumer->_idResCheckNonce($this->response, $this->endpoint);
        $this->assertTrue(Auth_OpenID::isFailure($result));
    }

    function test_badNonce()
    {
        // remove the nonce from the store
        $nonce = Auth_OpenID_mkNonce();
        list($timestamp, $salt) = Auth_OpenID_splitNonce($nonce);

        $this->store->useNonce($this->server_url, $timestamp, $salt);

        $response = Auth_OpenID_Message::fromOpenIDArgs([
            'response_nonce' => $nonce,
            'ns' => Auth_OpenID_OPENID2_NS,
        ]);

        $result = $this->consumer->_idResCheckNonce($response,
                                                    $this->endpoint);

        $this->assertTrue(Auth_OpenID::isFailure($result));
    }

    function test_tamperedNonce()
    {
        // Malformed nonce
        $query = [
            'response_nonce' => 'malformed',
            'ns' => Auth_OpenID_OPENID2_NS,
        ];
        $message = Auth_OpenID_Message::fromPostArgs($query);

        $result = $this->consumer->_idResCheckNonce($message,
                                                    $this->endpoint);

        $this->assertTrue(Auth_OpenID::isFailure($result));
    }

    function test_missingNonce()
    {
        // no nonce parameter on the return_to
        $query = ['openid.return_to' => $this->return_to];
        $message = Auth_OpenID_Message::fromPostArgs($query);

        $result = $this->consumer->_idResCheckNonce($message,
                                                    $this->endpoint);

        $this->assertTrue(Auth_OpenID::isFailure($result));
    }
}

class Tests_Auth_OpenID_Consumer_TestCheckAuthTriggered extends _TestIdRes {
    var $consumer_class = '_CheckAuthDetectingConsumer';

    function _doIdRes($message, $endpoint, $return_to)
    {
        return $this->consumer->_doIdRes($message, $endpoint, $return_to);
    }

    function test_checkAuthTriggered()
    {
        $query = [
            'openid.return_to' => $this->return_to,
            'openid.identity' => $this->server_id,
            'openid.assoc_handle' => 'not_found',
            'openid.sig' => 'bogus',
            'openid.signed' => 'identity,return_to',
        ];

        $message = Auth_OpenID_Message::fromPostArgs($query);

	$this->consumer->disableReturnToChecking();

        $result = $this->_doIdRes($message, $this->endpoint, null);

        $error = __getError();

        if ($error === null) {
            $this->fail('_checkAuth did not happen.');
        }
    }

    function test_checkAuthTriggeredWithAssoc()
    {
        // Store an association for this server that does not match
        // the handle that is in the query
        $issued = time();
        $lifetime = 1000;
        $assoc = new Auth_OpenID_Association(
                      'handle', 'secret', $issued, $lifetime, 'HMAC-SHA1');
        $this->store->storeAssociation($this->server_url, $assoc);

        $query = [
            'openid.return_to' => $this->return_to,
            'openid.identity' => $this->server_id,
            'openid.assoc_handle' => 'not_found',
            'openid.sig' => 'bogus',
            'openid.signed' => 'return_to,identity'
        ];

 	$this->consumer->disableReturnToChecking();

	$message = Auth_OpenID_Message::fromPostArgs($query);

        $result = $this->_doIdRes($message, $this->endpoint, null);
        $error = __getError();

        if ($error === null) {
            $this->fail('_checkAuth did not happen.');
        }
    }

    function test_expiredAssoc()
    {
        // Store an expired association for the server with the handle
        // that is in the query
        $issued = time() - 10;
        $lifetime = 0;
        $handle = 'handle';
        $assoc = new Auth_OpenID_Association(
                        $handle, 'secret', $issued, $lifetime, 'HMAC-SHA1');
        $this->assertTrue($assoc->getExpiresIn() <= 0);
        $this->store->storeAssociation($this->server_url, $assoc);

        $query = [
            'openid.return_to' => $this->return_to,
            'openid.identity' => $this->server_id,
            'openid.sig' => 'bogus',
            'openid.signed' => 'identity,return_to',
            'openid.assoc_handle' => $handle
        ];

        $message = Auth_OpenID_Message::fromPostArgs($query);

	$this->consumer->disableReturnToChecking();

        $info = $this->_doIdRes($message, $this->endpoint, null);

        $this->assertEquals('failure', $info->status);

        $this->assertTrue(strpos($info->message, 'expired') !== false);
    }

    function test_newerAssoc()
    {
        // Store an expired association for the server with the handle
        // that is in the query
        $lifetime = 1000;

        $good_issued = time() - 10;
        $good_handle = 'handle';
        $good_assoc = new Auth_OpenID_Association(
                $good_handle, 'secret', $good_issued, $lifetime, 'HMAC-SHA1');
        $this->store->storeAssociation($this->server_url, $good_assoc);

        $bad_issued = time() - 5;
        $bad_handle = 'handle2';
        $bad_assoc = new Auth_OpenID_Association(
                  $bad_handle, 'secret', $bad_issued, $lifetime, 'HMAC-SHA1');
        $this->store->storeAssociation($this->server_url, $bad_assoc);

        $query = [
            'openid.return_to' => $this->return_to,
            'openid.identity' => $this->server_id,
            'openid.assoc_handle' => $good_handle
        ];

	$this->consumer->disableReturnToChecking();

        $message = Auth_OpenID_Message::fromPostArgs($query);
        $message = $good_assoc->signMessage($message);

        $info = $this->_doIdRes($message, $this->endpoint, null);

        $this->assertEquals($info->status, 'success');
        $this->assertEquals($this->consumer_id, $info->identity_url);
    }
}

class _MockFetcher {
    function __construct($response = null)
    {
        // response is (code, url, body)
        $this->response = $response;
        $this->fetches = [];
    }

    function post($url, $body)
    {
        $this->fetches[] = [$url, $body, []];
        return $this->response;
    }

    function get($url)
    {
        $this->fetches[] = [$url, null, []];
        return $this->response;
    }
}

class Tests_Auth_OpenID_Complete extends _TestIdRes {
    function test_cancel()
    {
        $query = ['openid.mode' => 'cancel'];
        $message = Auth_OpenID_Message::fromPostArgs($query);

	$this->consumer->disableReturnToChecking();

        $r = $this->consumer->complete($message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_CANCEL);
        $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
    }

    function test_cancel_with_return_to() {
        $message = Auth_OpenID_Message::fromPostArgs(['openid.mode' => 'cancel']);
        $r = $this->consumer->complete($message, $this->endpoint, $this->return_to);
        $this->assertEquals($r->status, Auth_OpenID_CANCEL);
        $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
    }

    function test_errorWithNoOptionalKeys()
    {
        $msg = 'an error message';
        $contact = 'some contact info here';
        $message = Auth_OpenID_Message::fromPostArgs([
            'openid.mode' => 'error',
            'openid.error' => $msg,
            'openid.contact' => $contact,
        ]);

	$this->consumer->disableReturnToChecking();

        $r = $this->consumer->complete($message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_FAILURE);
        $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
        $this->assertTrue($r->contact == $contact);
        $this->assertTrue($r->reference === null);
        $this->assertEquals($r->message, $msg);
    }

    function test_errorWithOptionalKeys()
    {
        $msg = 'an error message';
        $contact = 'me';
        $reference = 'support ticket';
        $message = Auth_OpenID_Message::fromPostArgs([
            'openid.mode' => 'error',
            'openid.error' => $msg,
            'openid.reference' => $reference,
            'openid.contact' => $contact, 'openid.ns'=> Auth_OpenID_OPENID2_NS
        ]);
        $r = $this->consumer->complete($message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_FAILURE);
        $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
        $this->assertTrue($r->contact == $contact);
        $this->assertTrue($r->reference == $reference);
        $this->assertEquals($r->message, $msg);
    }

    function test_error()
    {
        $msg = 'an error message';
        $query = [
            'openid.mode' => 'error',
            'openid.error' => $msg,
        ];
        $message = Auth_OpenID_Message::fromPostArgs($query);

	$this->consumer->disableReturnToChecking();

        $r = $this->consumer->complete($message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_FAILURE);
        $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
        $this->assertEquals($r->message, $msg);
    }

    function test_noMode()
    {
        $query = [];
        $message = Auth_OpenID_Message::fromPostArgs($query);
        $r = $this->consumer->complete($message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_FAILURE);
        $this->assertTrue($r->identity_url == $this->endpoint->claimed_id);
    }

    function test_idResMissingField()
    {
        $query = ['openid.mode' => 'id_res'];
        $message = Auth_OpenID_Message::fromPostArgs($query);
        $r = $this->consumer->complete($message, $this->endpoint, null);
        $this->assertTrue(Auth_openID::isFailure($r));
    }
}

class _VerifiedError extends Auth_OpenID_FailureResponse {
}

class Consumer_idResURLMismatch extends ConfigurableConsumer {
    function _discoverAndVerify($to_match)
    {
        return new _VerifiedError(null, 'verified error');
    }
}

class Tests_idResURLMismatch extends _TestIdRes {
    var $consumer_class = 'Consumer_idResURLMismatch';

    function test_idResURLMismatch()
    {
        $query = [
            'openid.mode' => 'id_res',
            'openid.return_to' => 'return_to (just anything)',
            'openid.identity' => 'something wrong (not this->consumer_id)',
            'openid.assoc_handle' => 'does not matter',
            'openid.signed' => 'identity,return_to',
            'openid.sig' => 'bogus',
        ];

	$this->consumer->disableReturnToChecking();

        $message = Auth_OpenID_Message::fromPostArgs($query);
        $r = $this->consumer->complete($message, $this->endpoint, null);
        $this->assertTrue(is_a($r, '_VerifiedError'));
    }
}

class SetupNeededConsumer extends Auth_OpenID_GenericConsumer {
    function _checkSetupNeeded($message)
    {
        return true;
    }
}

class Tests_Auth_OpenID_SetupNeeded extends _TestIdRes {
    function test_setupNeededIdRes()
    {
        $message = Auth_OpenID_Message::fromOpenIDArgs(['mode'=> 'id_res']);
        $response = $this->consumer->complete($message, null, null);
        $this->assertEquals(Auth_OpenID_SETUP_NEEDED,
                            $response->status);
    }
}

class TempConsumer extends ConfigurableConsumer {
    function _verifyDiscoveryResults($message, $endpoint)
    {
        return $endpoint;
    }
}

class TestCompleteMissingSig extends PHPUnit_Framework_TestCase {

    function setUp()
    {
        global $GOODSIG;

        $this->store = new GoodAssocStore();
        $this->consumer = new ConfigurableConsumer($this->store);
        $this->server_url = "http://idp.unittest/";

        $claimed_id = 'bogus.claimed';

        $this->message = Auth_OpenID_Message::fromOpenIDArgs(
        [
            'mode' => 'id_res',
            'return_to' => 'return_to (just anything)',
            'identity' => $claimed_id,
            'assoc_handle' => 'does not matter',
            'sig' => $GOODSIG,
            'response_nonce' => Auth_OpenID_mkNonce(),
            'signed' => 'identity,return_to,response_nonce,assoc_handle,claimed_id,op_endpoint',
            'claimed_id' => $claimed_id,
            'op_endpoint' => $this->server_url,
            'ns' => Auth_OpenID_OPENID2_NS,
        ]);

        $this->endpoint = new Auth_OpenID_ServiceEndpoint();
        $this->endpoint->server_url = $this->server_url;
        $this->endpoint->claimed_id = $claimed_id;
	$this->consumer->disableReturnToChecking();
    }

    function test_idResMissingNoSigs()
    {
        $c = new TempConsumer($this->store);
	$c->disableReturnToChecking();
        $r = $c->complete($this->message, $this->endpoint, null);
        $this->failUnlessSuccess($r);
    }

    function test_idResNoIdentity()
    {
        $this->message->delArg(Auth_OpenID_OPENID_NS, 'identity');
        $this->message->delArg(Auth_OpenID_OPENID_NS, 'claimed_id');
        $this->endpoint->claimed_id = null;
        $this->message->setArg(Auth_OpenID_OPENID_NS,
                               'signed', 'return_to,response_nonce,assoc_handle,op_endpoint');
        $r = $this->consumer->complete($this->message, $this->endpoint, null);
        $this->failUnlessSuccess($r);
    }

    function test_idResMissingIdentitySig()
    {
        $this->message->setArg(Auth_OpenID_OPENID_NS,
                               'signed',
                               'return_to,response_nonce,assoc_handle,claimed_id,op_endpoint');
        $r = $this->consumer->complete($this->message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_FAILURE);
    }

    function test_idResMissingReturnToSig()
    {
        $this->message->setArg(Auth_OpenID_OPENID_NS,
                               'signed',
                               'identity,response_nonce,assoc_handle,claimed_id');
        $r = $this->consumer->complete($this->message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_FAILURE);
    }

    function test_idResMissingAssocHandleSig()
    {
        $this->message->setArg(Auth_OpenID_OPENID_NS, 'signed',
                               'identity,response_nonce,return_to,claimed_id');
        $r = $this->consumer->complete($this->message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_FAILURE);
    }

    function test_idResMissingClaimedIDSig()
    {
        $this->message->setArg(Auth_OpenID_OPENID_NS, 'signed',
                               'identity,response_nonce,return_to,assoc_handle');
        $r = $this->consumer->complete($this->message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_FAILURE);
    }

    function failUnlessSuccess($response)
    {
        if ($response->status != Auth_OpenID_SUCCESS) {
            $this->fail(sprintf("Non-successful response: %s", $response->status));
        }
    }
}

class TestReturnToArgs extends PHPUnit_Framework_TestCase {
    function setUp()
    {
        $store = null;
        $this->consumer = new Auth_OpenID_GenericConsumer($store);
    }

    function test_returnToArgsUnexpectedArg()
    {
        $query = [
            'openid.mode' => 'id_res',
            'openid.return_to' => 'http://example.com/',
            'foo' => 'bar'
        ];

        // no return value, success is assumed if there are no
        // exceptions.
        $this->assertTrue(Auth_OpenID::isFailure(
                 $this->consumer->_verifyReturnToArgs($query)));
    }

    function test_returnToArgsOkay()
    {
        $query = [
            'openid.mode'=> 'id_res',
            'openid.return_to'=> 'http://example.com/?foo=bar',
            'foo'=> 'bar'
        ];
        // no return value, success is assumed if there are no exceptions.
        $result = $this->consumer->_verifyReturnToArgs($query);

        $this->assertFalse(Auth_OpenID::isFailure($result));
        $this->assertTrue($result);
    }

    function test_returnToMismatch()
    {
        $query = [
            'openid.mode' => 'id_res',
            'openid.return_to' => 'http://example.com/?foo=bar'
        ];

        // fail, query has no key 'foo'.
        $result = $this->consumer->_verifyReturnToArgs($query);
        $this->assertTrue(Auth_OpenID::isFailure($result));

        $query['foo'] = 'baz';
        // fail, values for 'foo' do not match.
        $result = $this->consumer->_verifyReturnToArgs($query);
        $this->assertTrue(Auth_OpenID::isFailure($result));
    }

    function test_noReturnTo()
    {
        $query = ['openid.mode'=> 'id_res'];
        $result = $this->consumer->_verifyReturnToArgs($query);
        $this->assertTrue(Auth_OpenID::isFailure($result));
    }

    function test_completeBadReturnTo()
    {
        // Test GenericConsumer.complete()'s handling of bad return_to
        // values.
        $return_to = "http://some.url/path?foo=bar";

        // Scheme, authority, and path differences are checked by
        // GenericConsumer._checkReturnTo.  Query args checked by
        // GenericConsumer._verifyReturnToArgs.
        $bad_return_tos = [
            // Scheme only
            "https://some.url/path?foo=bar",
            // Authority only
            "http://some.url.invalid/path?foo=bar",
            // Path only
            "http://some.url/path_extra?foo=bar",
            // Query args differ
            "http://some.url/path?foo=bar2",
            "http://some.url/path?foo2=bar",
        ];

        $m = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
        $m->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
        $m->setArg(Auth_OpenID_BARE_NS, 'foo', 'bar');
        $endpoint = null;

        foreach ($bad_return_tos as $bad) {
            $m->setArg(Auth_OpenID_OPENID_NS, 'return_to', $bad);
	    $this->assertFalse($this->consumer->_checkReturnTo($m, $return_to));
        }
    }

    function test_completeGoodReturnTo()
    {
        // Test GenericConsumer.complete()'s handling of good
        // return_to values.
        $return_to = "http://some.url/path";

        $good_return_tos = [
            [
                $return_to,
                [],
            ],
            [
                $return_to . "?another=arg",
                [
                    [
                        [Auth_OpenID_BARE_NS, 'another'],
                        'arg',
                    ],
                ],
            ],
            [
                $return_to . "?another=arg#fragment",
                [
                    [
                        [Auth_OpenID_BARE_NS, 'another'],
                        'arg',
                    ],
                ],
            ],
            ["HTTP://some.url/path", []],
            ["http://some.URL/path", []],
            ["http://some.url:80/path", []],
            ["http://some.url/./path", []],
        ];

        $endpoint = null;

        foreach ($good_return_tos as $pair) {
            list($good_return_to, $extra) = $pair;
            $m = new Auth_OpenID_Message(Auth_OpenID_OPENID1_NS);
            $m->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');

            for ($i = 0; $i < count($extra); $i++) {
                list($ckey, $value) = $extra[$i];
                $ns = $ckey[0];
                $key = $ckey[1];
                $m->setArg($ns, $key, $value);
            }

            $m->setArg(Auth_OpenID_OPENID_NS, 'return_to', $good_return_to);
            $result = $this->consumer->complete($m, $endpoint, $return_to);
            $this->assertTrue(is_a($result, 'Auth_OpenID_CancelResponse'));
        }
    }
}

class Tests_Auth_OpenID_CheckAuthResponse extends _TestIdRes {
    function _createAssoc()
    {
        $issued = time();
        $lifetime = 1000;
        $assoc = new Auth_OpenID_Association(
                        'handle', 'secret', $issued, $lifetime, 'HMAC-SHA1');
        $store =& $this->consumer->store;
        $store->storeAssociation($this->server_url, $assoc);
        $assoc2 = $store->getAssociation($this->server_url);
        $this->assertEquals($assoc, $assoc2);
    }

    function test_goodResponse()
    {
        // successful response to check_authentication
        $response = ['is_valid' => 'true'];
        $message = Auth_OpenID_Message::fromOpenIDArgs($response);
        $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
        $this->assertTrue($r);
    }

    function test_missingAnswer()
    {
        // check_authentication returns false when the server sends no
        // answer
        $response = [];
        $message = Auth_OpenID_Message::fromPostArgs($response);
        $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
        $this->assertFalse($r);
    }

    function test_badResponse()
    {
        // check_authentication returns false when is_valid is false
        $response = ['is_valid' => 'false'];
        $message = Auth_OpenID_Message::fromOpenIDArgs($response);

        $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
        $this->assertFalse($r);
    }

    function test_badResponseInvalidate()
    {
        // Make sure that the handle is invalidated when is_valid is
        // false
        $this->_createAssoc();
        $response = [
            'is_valid' => 'false',
            'invalidate_handle' => 'handle',
        ];

        $message = Auth_OpenID_Message::fromOpenIDArgs($response);

        $r = $this->consumer->_processCheckAuthResponse($message,
                                                        $this->server_url);
        $this->assertFalse($r);
        $this->assertTrue(
                $this->consumer->store->getAssociation($this->server_url) === null);
    }

    function test_invalidateMissing()
    {
        // invalidate_handle with a handle that is not present
        $response = [
            'is_valid' => 'true',
            'invalidate_handle' => 'missing',
        ];

        $message = Auth_OpenID_Message::fromOpenIDArgs($response);

        $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
        $this->assertTrue($r);
    }

    function test_invalidatePresent()
    {
        // invalidate_handle with a handle that exists"""
        $this->_createAssoc();
        $response = [
            'is_valid' => 'true',
            'invalidate_handle' => 'handle',
        ];

        $message = Auth_OpenID_Message::fromOpenIDArgs($response);

        $r = $this->consumer->_processCheckAuthResponse($message, $this->server_url);
        $this->assertTrue($r);
        $this->assertTrue(
                  $this->consumer->store->getAssociation($this->server_url) === null);
    }
}

class _IdResFetchFailingConsumer extends Auth_OpenID_GenericConsumer {
    var $message = 'fetch failed';

    function _doIdRes($message, $endpoint)
    {
        return new Auth_OpenID_FailureResponse($endpoint,
                                               $this->message);
    }
}

class Tests_Auth_OpenID_FetchErrorInIdRes extends _TestIdRes {
    var $consumer_class = '_IdResFetchFailingConsumer';

    function test_idResFailure()
    {
        $query = ['openid.mode' => 'id_res'];
        $message = Auth_OpenID_Message::fromPostArgs($query);
        $r = $this->consumer->complete($message, $this->endpoint, null);
        $this->assertEquals($r->status, Auth_OpenID_FAILURE);
        $this->assertEquals($r->identity_url, $this->consumer_id);
        $this->assertEquals($this->consumer->message, $r->message);
    }
}

class _ExceptionRaisingMockFetcher {
    function get($url)
    {
        __raiseError(E_MOCK_FETCHER_EXCEPTION);
    }

    function post($url, $body)
    {
        __raiseError(E_MOCK_FETCHER_EXCEPTION);

        return new Auth_Yadis_HTTPResponse($url, 400,
                                               [], '');
    }
}

class _BadArgCheckingConsumer extends Auth_OpenID_GenericConsumer {
    function _makeKVPost($message, $tmp)
    {
        $args = $message->toPostArgs();

        if ($args != [
            'openid.mode' => 'check_authentication',
            'openid.signed' => 'foo'
            ]
        ) {
            __raiseError(E_ASSERTION_ERROR);
        }
        return null;
    }
}

class Tests_Auth_OpenID_Consumer_TestCheckAuth extends _TestIdRes {
    public $fetcher;
    public $consumer;

    function setUp()
    {
        $this->store = new Tests_Auth_OpenID_MemStore();
        $this->consumer = new Auth_OpenID_GenericConsumer($this->store);
        $this->fetcher = new _MockFetcher();
        $this->consumer->fetcher =& $this->fetcher;
    }

    function test_checkauth_error()
    {
        global $_Auth_OpenID_server_url;
        $this->fetcher->response = new Auth_Yadis_HTTPResponse("http://some_url",
                                                                404,
                                                                [],
                                                                "blah:blah\n");
        $query = ['openid.signed' => 'stuff, things'];
        $message = Auth_OpenID_Message::fromPostArgs($query);

        $r = $this->consumer->_checkAuth($message, $_Auth_OpenID_server_url);
        if ($r !== false) {
            $this->fail("Expected _checkAuth result to be false");
        }
    }

    function test_bad_args()
    {
        $query = [
            'openid.signed' => 'foo',
            'closid.foo' => 'something',
        ];

        $consumer = new _BadArgCheckingConsumer($this->store);

        $message = Auth_OpenID_Message::fromPostArgs($query);

        $this->assertFalse($consumer->_checkAuth($message, 'does://not.matter'));
    }

    function test_signedList()
    {
        $signed = 'identity,mode,ns.sreg,sreg.email';
        $query = Auth_OpenID_Message::fromOpenIDArgs([
            'mode'=> 'id_res',
            'sig'=> 'rabbits',
            'identity'=> '=example',
            'assoc_handle'=> 'munchkins',
            'ns.sreg' => 'urn:sreg',
            'sreg.email' => 'bogus@example.com',
            'signed'=> $signed,
            'foo'=> 'bar'
        ]);

        $args = $this->consumer->_createCheckAuthRequest($query);
        $this->assertTrue($args->isOpenID1());
        $signed_list = explode(',',$signed);
        foreach ($signed_list as $k) {
            $this->assertTrue($args->getAliasedArg($k));
        }
    }


    function test_112()
    {
        $args = [
            'openid.assoc_handle' => 'fa1f5ff0-cde4-11dc-a183-3714bfd55ca8',
            'openid.claimed_id' => 'http://binkley.lan/user/test01',
            'openid.identity' => 'http://test01.binkley.lan/',
            'openid.mode' => 'id_res',
            'openid.ns' => 'http://specs.openid.net/auth/2.0',
            'openid.ns.pape' => 'http://specs.openid.net/extensions/pape/1.0',
            'openid.op_endpoint' => 'http://binkley.lan/server',
            'openid.pape.auth_policies' => 'none',
            'openid.pape.auth_time' => '2008-01-28T20 =>42 =>36Z',
            'openid.pape.nist_auth_level' => '0',
            'openid.response_nonce' => '2008-01-28T21 =>07 =>04Z99Q=',
            'openid.return_to' => 'http://binkley.lan =>8001/process?janrain_nonce=2008-01-28T21%3A07%3A02Z0tMIKx',
            'openid.sig' => 'YJlWH4U6SroB1HoPkmEKx9AyGGg=',
            'openid.signed' => 'assoc_handle,identity,response_nonce,return_to,claimed_id,op_endpoint,pape.auth_time,ns.pape,pape.nist_auth_level,pape.auth_policies',
        ];
        $this->assertEquals(Auth_OpenID_OPENID2_NS, $args['openid.ns']);
        $incoming = Auth_OpenID_Message::fromPostArgs($args);
        $this->assertTrue($incoming->isOpenID2());
        $car = $this->consumer->_createCheckAuthRequest($incoming);
        $expected_args = $args;
        $expected_args['openid.mode'] = 'check_authentication';
        $expected = Auth_OpenID_Message::fromPostArgs($expected_args);
        $this->assertTrue($expected->isOpenID2());
        $this->assertEquals($expected, $car);
        $this->assertEquals($expected_args, $car->toPostArgs());
    }
}

class Tests_Auth_OpenID_Consumer_TestFetchAssoc extends PHPUnit_Framework_TestCase {
    public $fetcher;
    public $consumer;
    function setUp()
    {
        $this->store = new Tests_Auth_OpenID_MemStore();
        $this->fetcher = new _MockFetcher();
        $this->consumer = new Auth_OpenID_GenericConsumer($this->store);
        $this->consumer->fetcher =& $this->fetcher;
    }

    function test_kvpost_error()
    {
        $this->fetcher->response = new Auth_Yadis_HTTPResponse("http://some_url",
                                                                   404,
                                                                   [],
                                                                   "blah:blah\n");
        $query = ['openid.mode' => 'associate'];
        $message = Auth_OpenID_Message::fromPostArgs($query);

        $r = $this->consumer->_makeKVPost($message,
                                          "http://server_url");
        if ($r !== null) {
            $this->fail("Expected _makeKVPost result to be null");
        }
    }

    function test_error_404()
    {
        // 404 from a kv post raises HTTPFetchingError
        $this->fetcher->response = new Auth_Yadis_HTTPResponse(
           "http://some_url", 404, ['Hea'=> 'der'], 'blah:blah\n');

        $result = $this->consumer->_makeKVPost(
               Auth_OpenID_Message::fromPostArgs(['mode'=>'associate']),
               "http://server_url");

        $this->assertTrue($result === null);
    }

    function test_error_exception()
    {
        $this->consumer->fetcher = new _ExceptionRaisingMockFetcher();

        $query = ['openid.mode' => 'associate'];
        $message = Auth_OpenID_Message::fromPostArgs($query);

        $this->consumer->_makeKVPost($message,
                                     "http://server_url");

        if (__getError() !== E_MOCK_FETCHER_EXCEPTION) {
            $this->fail("Expected ExceptionRaisingMockFetcher to " .
                        "raise E_MOCK_FETCHER_EXCEPTION");
        }

        $endpoint = new Auth_OpenID_ServiceEndpoint();
        $endpoint->server_url = 'some://url';

        // exception fetching returns no association
        $this->assertEquals($this->consumer->_getAssociation($endpoint),
                            null);

        $query = ['openid.signed' => ''];
        $message = Auth_OpenID_Message::fromPostArgs($query);

        $this->consumer->_checkAuth($message,
                                    'some://url');

        if (__getError() !== E_MOCK_FETCHER_EXCEPTION) {
            $this->fail("Expected ExceptionRaisingMockFetcher to " .
                        "raise E_MOCK_FETCHER_EXCEPTION (_checkAuth)");
        }
    }
}

class Tests_Auth_OpenID_AuthRequestHTMLMarkup extends PHPUnit_Framework_TestCase {
    function setUp()
    {
        $this->endpoint = new Auth_OpenID_ServiceEndpoint();
        $this->endpoint->claimed_id = 'identity_url';

        $this->request = new Auth_OpenID_AuthRequest($this->endpoint, null);
    }

    function test_htmlMarkup()
    {
        $html = $this->request->htmlMarkup('http://realm.com/',
                                           'http://realm.com/return_to');
        $this->assertTrue(strpos($html,"<html>") !== false);
        $this->assertTrue(strpos($html,"</html>") !== false);
        $this->assertTrue(strpos($html,"<body onload") !== false);
        $this->assertTrue(strpos($html,"</body>") !== false);
        $this->assertTrue(strpos($html,"<form") !== false);
        $this->assertTrue(strpos($html,"</form>") !== false);
    }
}

class Tests_Auth_OpenID_SuccessResponse extends PHPUnit_Framework_TestCase {
    function setUp()
    {
        $this->endpoint = new Auth_OpenID_ServiceEndpoint();
        $this->endpoint->claimed_id = 'identity_url';
    }

    function test_extensionResponse()
    {
        $uri = "http://bogus.unittest/1.0";

        $query = [
            'openid.ns.unittest' => $uri,
            'openid.unittest.one' => '1',
            'openid.unittest.two' =>'2',
            'openid.sreg.nickname' => 'j3h',
            'openid.return_to' => 'return_to'
        ];

        $message = Auth_OpenID_Message::fromPostArgs($query);
        $resp = new Auth_OpenID_SuccessResponse($this->endpoint, $message);

        $utargs = $resp->extensionResponse($uri, false);
        $this->assertEquals($utargs, ['one' => '1', 'two' => '2']);
        $sregargs = $resp->extensionResponse(Auth_OpenID_SREG_NS_URI, false);
        $this->assertEquals($sregargs, ['nickname' => 'j3h']);
    }

    function test_extensionResponseSigned()
    {
        $args = [
            'ns.sreg' => 'urn:sreg',
            'ns.unittest' => 'urn:unittest',
            'unittest.one' => '1',
            'unittest.two' => '2',
            'sreg.nickname' => 'j3h',
            'sreg.dob' => 'yesterday',
            'return_to' => 'return_to',
            'signed' => 'sreg.nickname,unittest.one,sreg.dob',
        ];

        $signed_list = [
            'openid.sreg.nickname',
            'openid.unittest.one',
            'openid.sreg.dob',
            'openid.ns.sreg',
        ];

        $msg = Auth_OpenID_Message::fromOpenIDArgs($args);
        $resp = new Auth_OpenID_SuccessResponse($this->endpoint, $msg, $signed_list);

        // All args in this NS are signed, so expect all.
        $sregargs = $resp->extensionResponse('urn:sreg', true);
        $this->assertEquals($sregargs,
                            [
                                'nickname' => 'j3h',
                                'dob' => 'yesterday',
                            ]);

        // Not all args in this NS are signed, so expect null when
        // asking for them.
        $utargs = $resp->extensionResponse('urn:unittest', true);
        $this->assertEquals($utargs, null);
    }

    function test_noReturnTo()
    {
        $message = Auth_OpenID_Message::fromPostArgs([]);
        $resp = new Auth_OpenID_SuccessResponse($this->endpoint, $message);
        $this->assertTrue($resp->getReturnTo() === null);
    }

    function test_returnTo()
    {
        $query = ['openid.return_to' => 'return_to'];
        $message = Auth_OpenID_Message::fromPostArgs($query);

        $resp = new Auth_OpenID_SuccessResponse($this->endpoint,
                                                $message, ['openid.return_to']);

        $this->assertEquals($resp->getReturnTo(), 'return_to');
    }
}

class _StubConsumer {
    function __construct()
    {
        $this->assoc = null;
        $this->response = null;
        $this->endpoint = null;
        $this->fetcher = new _MockFetcher();
    }

    function begin($service)
    {
        $auth_req = new Auth_OpenID_AuthRequest($service, $this->assoc);
        $this->endpoint = $service;
        return $auth_req;
    }

    function complete($message, $endpoint, $return_to)
    {
        return $this->response;
    }
}

class Tests_Auth_OpenID_DiscoFailure extends PHPUnit_Framework_TestCase {
    var $consumerClass = null;

    function setUp()
    {
        foreach ($_SESSION as $k => $v) {
          unset($_SESSION[$k]);
        }

        $this->endpoint = new Auth_OpenID_ServiceEndpoint();
        $this->claimed_id = 'http://identity.url/';
        $this->endpoint->claimed_id = $this->claimed_id;
        $this->store = null;
        $this->session = new Auth_Yadis_PHPSession();
        $cls = $this->consumerClass;
        $this->consumer = new $cls($this->store, $this->session);
        $this->consumer->consumer = new _StubConsumer();
        $this->discovery = new Auth_Yadis_Discovery($this->session,
                                         $this->claimed_id,
                                         $this->consumer->session_key_prefix);
    }
}

class Consumer_completeEmptySession extends Auth_OpenID_GenericConsumer {
    var $test_case = null;
    var $text = "failed complete";

    function complete($message, $endpoint, $return_to)
    {
        $this->test_case->assertTrue($endpoint === null);
        return new Auth_OpenID_FailureResponse($endpoint, $this->text);
    }
}

class Tests_Auth_OpenID_ConsumerTest2 extends PHPUnit_Framework_TestCase {
    function setUp()
    {
        foreach ($_SESSION as $k => $v) {
          unset($_SESSION[$k]);
        }

        $this->endpoint = new Auth_OpenID_ServiceEndpoint();
        $this->claimed_id = 'http://identity.url/';
        $this->endpoint->claimed_id = $this->claimed_id;
        $this->store = null;
        $this->session = new Auth_Yadis_PHPSession();
        $this->consumer = new Auth_OpenID_Consumer($this->store, $this->session);
        $this->consumer->consumer = new _StubConsumer();
        $this->discovery = new Auth_Yadis_Discovery($this->session,
                                         $this->claimed_id,
                                         $this->consumer->session_key_prefix);
    }

    function test_beginWithoutDiscovery()
    {
        // Does this really test anything non-trivial?
        $result = $this->consumer->beginWithoutDiscovery($this->endpoint);

        // The result is an auth request
        $this->assertTrue(strtolower(get_class($result)) ==
                          'auth_openid_authrequest');

        $loader = new Auth_OpenID_ServiceEndpointLoader();

        // Side-effect of calling beginWithoutDiscovery is setting the
        // session value to the endpoint attribute of the result
        $this->assertTrue(
                $loader->fromSession(
                       $this->session->get($this->consumer->_token_key)) ==
                $result->endpoint);

        // The endpoint that we passed in is the endpoint on the
        // auth_request
        $this->assertTrue($result->endpoint == $this->endpoint);
    }

    function test_completeEmptySession()
    {
        $this->consumer->consumer = new Consumer_completeEmptySession($this->store);
        $this->consumer->consumer->test_case =& $this;

        $response = $this->consumer->complete(null);
        $this->assertTrue(Auth_OpenID::isFailure($response));
        $this->assertEquals($this->consumer->consumer->text, $response->message);
        $this->assertTrue($response->identity_url === null);
    }

    function _doResp($auth_req, $exp_resp)
    {
        // complete a transaction, using the expected response from
        // the generic consumer.
        $this->consumer->consumer->response = $exp_resp;

        // endpoint is stored in the session
        // $this->assertTrue($this->session->data);
        $this->assertTrue($_SESSION);
        $resp = $this->consumer->complete(null);

        // All responses should have the same identity URL, and the
        // session should be cleaned out
        if ($this->endpoint->claimed_id != Auth_OpenID_IDENTIFIER_SELECT) {
            $this->assertTrue($resp->identity_url == $this->claimed_id);
        }

        $this->assertFalse(in_array($this->consumer->_token_key,
                                    $_SESSION)); // this->session->data));

        // Expected status response
        $this->assertEquals($resp->status, $exp_resp->status);

        return $resp;
    }

    function _doRespNoDisco($exp_resp)
    {
        // Set up a transaction without discovery
        $auth_req = $this->consumer->beginWithoutDiscovery($this->endpoint);
        $resp = $this->_doResp($auth_req, $exp_resp);
        // There should be nothing left in the session once we have
        // completed.
        $this->assertFalse($this->session->contents());
        return $resp;
    }

    /*
     * Be sure that the session gets cleaned up when the response is
     * successful and has a different URL than the one in the request.
     */
    function test_successDifferentURL()
    {
        // Set up a request endpoint describing an IDP URL
        $this->identity_url = 'http://idp.url/';
        $this->endpoint->claimed_id = $this->endpoint->local_id = Auth_OpenID_IDENTIFIER_SELECT;

        // Use a response endpoint with a different URL (asserted by
        // the IDP)
        $resp_endpoint = new Auth_OpenID_ServiceEndpoint();
        $resp_endpoint->claimed_id = "http://user.url/";

        $resp = $this->_doRespDisco(
            true,
            mkSuccess($resp_endpoint, []));
        $this->assertTrue($this->discovery->getManager(true) === null);
    }

    function test_noDiscoCompleteSuccessWithToken()
    {
        $message = Auth_OpenID_Message::fromPostArgs([]);
        $this->_doRespNoDisco(new Auth_OpenID_SuccessResponse($this->endpoint,
                                                              $message));
    }

    function test_noDiscoCompleteCancelWithToken()
    {
        $this->_doRespNoDisco(new Auth_OpenID_CancelResponse($this->endpoint));
    }

    function test_noDiscoCompleteFailure()
    {
        $msg = 'failed!';
        $resp = $this->_doRespNoDisco(new Auth_OpenID_FailureResponse($this->endpoint, $msg));
        $this->assertTrue($resp->message == $msg);
    }

    function test_noDiscoCompleteSetupNeeded()
    {
        $setup_url = 'http://setup.url/';
        $resp = $this->_doRespNoDisco(
              new Auth_OpenID_SetupNeededResponse($this->endpoint, $setup_url));
        $this->assertTrue($resp->setup_url == $setup_url);
    }

    // To test that discovery is cleaned up, we need to initialize a
    // Yadis manager, and have it put its values in the session.
    function _doRespDisco($is_clean, $exp_resp)
    {
        // Set up and execute a transaction, with discovery
        $this->discovery->createManager([$this->endpoint],
                                        $this->claimed_id);
        $auth_req = $this->consumer->begin($this->claimed_id);
        $resp = $this->_doResp($auth_req, $exp_resp);

        $manager = $this->discovery->getManager();
        if ($is_clean) {
            $this->assertTrue($this->discovery->getManager() === null);
        } else {
            $this->assertFalse($this->discovery->getManager() === null);
        }

        return $resp;
    }

    // Cancel and success DO clean up the discovery process
    function test_completeSuccess()
    {
        $message = Auth_OpenID_Message::fromPostArgs([]);
        $this->_doRespDisco(true,
                            new Auth_OpenID_SuccessResponse($this->endpoint,
                                                            $message));
    }

    function test_completeCancel()
    {
        $this->_doRespDisco(true,
                            new Auth_OpenID_CancelResponse($this->endpoint));
    }

    // Failure and setup_needed don't clean up the discovery process
    function test_completeFailure()
    {
        $msg = 'failed!';
        $resp = $this->_doRespDisco(false,
                    new Auth_OpenID_FailureResponse($this->endpoint, $msg));
        $this->assertTrue($resp->message == $msg);
    }

    function test_completeSetupNeeded()
    {
        $setup_url = 'http://setup.url/';
        $resp = $this->_doRespDisco(false,
            new Auth_OpenID_SetupNeededResponse($this->endpoint, $setup_url));
        $this->assertTrue($resp->status == Auth_OpenID_SETUP_NEEDED);
        $this->assertTrue($resp->setup_url == $setup_url);
    }

    function test_begin()
    {
        $this->discovery->createManager([$this->endpoint],
                                        $this->claimed_id);
        // Should not raise an exception
        $auth_req = $this->consumer->begin($this->claimed_id);
        $this->assertTrue(strtolower(get_class($auth_req)) === 'auth_openid_authrequest');
        $this->assertTrue($auth_req->endpoint == $this->endpoint);
        $this->assertTrue($auth_req->endpoint == $this->consumer->consumer->endpoint);
        $this->assertTrue($auth_req->assoc == $this->consumer->consumer->assoc);
    }
}

class IDPDrivenTest_Consumer1 extends ConfigurableConsumer {
    var $iverified = [];
    var $endpoint = null;
    var $failure_cb = null;
    var $check_endpoint = null;

    function _idResCheckNonce($message, $endpoint)
    {
        return true;
    }

    function _verifyDiscoveryResults($identifier, $endpoint)
    {
        call_user_func($this->failure_cb,
                       $endpoint === $this->check_endpoint);
        $this->iverified[] = $this->endpoint;
        return $this->endpoint;
    }
}

class IDPDrivenTest_Consumer2 extends ConfigurableConsumer {
    function verifyDiscoveryResults($identifier, $endp)
    {
        return new Auth_OpenID_FailureResponse(null,
                                               "Bogus");
    }
}

class IDPDrivenTest extends PHPUnit_Framework_TestCase {
    public $consumer;
    public $endpoint;

    function setUp()
    {
        $this->store = new GoodAssocStore();
        $this->consumer = new ConfigurableConsumer($this->store);
        $this->endpoint = new Auth_OpenID_ServiceEndpoint();
        $this->endpoint->server_url = "http://idp.unittest/";
    }

    function test_idpDrivenBegin()
    {
        // Testing here that the token-handling doesn't explode...
        $this->assertTrue($this->consumer->begin($this->endpoint) !== null);
    }

    function test_idpDrivenComplete()
    {
        global $GOODSIG;

        $this->consumer = new IDPDrivenTest_Consumer1($this->store);
        $this->consumer->failure_cb = [$this, "assertTrue"];
        $this->consumer->check_endpoint =& $this->endpoint;

        $identifier = '=directed_identifier';
        $message = Auth_OpenID_Message::fromPostArgs([
            'openid.identity'=> '=directed_identifier',
            'openid.return_to'=> 'x',
            'openid.assoc_handle'=> 'z',
            'openid.signed'=> 'identity,return_to',
            'openid.sig'=> $GOODSIG
        ]);

        $endpoint = new Auth_OpenID_ServiceEndpoint();
        $endpoint->claimed_id = $identifier;
        $endpoint->server_url = $this->endpoint->server_url;
        $endpoint->local_id = $identifier;

	$this->consumer->disableReturnToChecking();

        $this->consumer->endpoint =& $endpoint;
        $response = $this->consumer->_doIdRes($message, $this->endpoint, null);

        $this->failUnlessSuccess($response);

        $this->assertEquals($response->identity_url, "=directed_identifier");

        // assert that discovery attempt happens and returns good
        $this->assertEquals($this->consumer->iverified, [$endpoint]);
    }

    function test_idpDrivenCompleteFraud()
    {
        global $GOODSIG;

        $this->consumer = new IDPDrivenTest_Consumer2($this->store);

        // crap with an identifier that doesn't match discovery info
        $message = Auth_OpenID_Message::fromPostArgs([
            'openid.identity'=> '=directed_identifier',
            'openid.return_to'=> 'x',
            'openid.assoc_handle'=> 'z',
            'openid.signed'=> 'identity,return_to',
            'openid.sig'=> $GOODSIG
        ]);

	$this->consumer->disableReturnToChecking();

        $result = $this->consumer->_doIdRes($message, $this->endpoint, null);
        $this->assertTrue(Auth_OpenID::isFailure($result));
    }

    function failUnlessSuccess($response)
    {
        if ($response->status != Auth_OpenID_SUCCESS) {
            $this->fail("Non-successful response (status is ".$response->status.")");
        }
    }
}

global $__test_otherServer_text;
$__test_otherServer_text = "__test_otherServer";
class TestDiscoveryVerification_test_otherServer extends Auth_OpenID_GenericConsumer {
    function _discoverAndVerify($to_match)
    {
        global $__test_otherServer_text;
        return new Auth_OpenID_FailureResponse(null, $__test_otherServer_text);
    }
}

class TestDiscoveryVerification extends PHPUnit_Framework_TestCase {
    var $services = [];

    function discoveryFunc($identifier)
    {
        return [$identifier, $this->services];
    }

    function setUp()
    {
        $this->store = new GoodAssocStore();
        $this->consumer = new Auth_OpenID_GenericConsumer($this->store);
        $this->consumer->discoverMethod = [
            $this,
                                                'discoveryFunc'
        ];

        $this->identifier = "http://idp.unittest/1337";
        $this->server_url = "http://endpoint.unittest/";

        $this->message = Auth_OpenID_Message::fromPostArgs([
            'openid.ns'=> Auth_OpenID_OPENID2_NS,
            'openid.identity'=> $this->identifier,
            'openid.claimed_id'=> $this->identifier,
            'openid.op_endpoint'=> $this->server_url
        ]);

        $this->endpoint = new Auth_OpenID_ServiceEndpoint();
        $this->endpoint->server_url = $this->server_url;
    }

    function test_theGoodStuff()
    {
        $endpoint = new Auth_OpenID_ServiceEndpoint();
        $endpoint->type_uris = [Auth_OpenID_TYPE_2_0];
        $endpoint->claimed_id = $this->identifier;
        $endpoint->server_url = $this->server_url;
        $endpoint->local_id = $this->identifier;
        $this->services = [$endpoint];
        $r = $this->consumer->_verifyDiscoveryResults($this->message, $endpoint);

        $this->assertEquals($r, $endpoint);
    }

    function test_otherServer()
    {
        global $__test_otherServer_text;

        // setup
        $this->consumer = new TestDiscoveryVerification_test_otherServer($this->store);
        $this->consumer->discoverMethod = [
            $this,
                                                'discoveryFunc'
        ];

        // a set of things without the stuff
        $endpoint = new Auth_OpenID_ServiceEndpoint();
        $endpoint->type_uris = [Auth_OpenID_TYPE_2_0];
        $endpoint->claimed_id = $this->identifier;
        $endpoint->server_url = "http://the-MOON.unittest/";
        $endpoint->local_id = $this->identifier;
        $this->services = [$endpoint];

        $result = $this->consumer->_verifyDiscoveryResults(
                                 $this->message, $endpoint);

        $this->assertTrue(Auth_OpenID::isFailure($result));
        $this->assertTrue(strpos($result->message, $__test_otherServer_text) !== false);
    }

    function test_foreignDelegate()
    {
        global $__test_otherServer_text;

        // setup
        $this->consumer = new TestDiscoveryVerification_test_otherServer($this->store);
        $this->consumer->discoverMethod = [
            $this,
                                                'discoveryFunc'
        ];

        // a set of things with the server stuff but other delegate
        $endpoint = new Auth_OpenID_ServiceEndpoint();
        $endpoint->type_uris = [Auth_OpenID_TYPE_2_0];
        $endpoint->claimed_id = $this->identifier;
        $endpoint->server_url = $this->server_url;
        $endpoint->local_id = "http://unittest/juan-carlos";

        $result = $this->consumer->_verifyDiscoveryResults(
                                 $this->message, $endpoint);
        $this->assertTrue(Auth_OpenID::isFailure($result));
        $this->assertTrue(strpos($result->message, $__test_otherServer_text) !== false);
    }

    function test_nothingDiscovered()
    {
        // a set of no things.
        $this->services = [];
        $result = $this->consumer->_verifyDiscoveryResults(
                                 $this->message, $this->endpoint);
        $this->assertTrue(Auth_OpenID::isFailure($result));
    }
}

class DummyEndpoint {
    var $use_compatibility = false;

    function compatibilityMode()
    {
        return $this->use_compatibility;
    }
}

class TestCreateAssociationRequest extends PHPUnit_Framework_TestCase {
    function setUp()
    {
        $this->endpoint = new DummyEndpoint();
        $s = null;
        $this->consumer = new Auth_OpenID_GenericConsumer($s);
        $this->assoc_type = 'HMAC-SHA1';
    }

    function test_noEncryptionSendsType()
    {
        $session_type = 'no-encryption';
        list($session, $args) = $this->consumer->_createAssociateRequest(
                       $this->endpoint, $this->assoc_type, $session_type);

        $this->assertTrue(is_a($session, 'Auth_OpenID_PlainTextConsumerSession'));

        $expected = Auth_OpenID_Message::fromOpenIDArgs(
            [
                'ns' => Auth_OpenID_OPENID2_NS,
                'session_type' => $session_type,
                'mode' => 'associate',
                'assoc_type' => $this->assoc_type,
            ]);

        $this->assertEquals($expected->toPostArgs(),
                            $args->toPostArgs());
    }

    function test_noEncryptionSendsTypeHMACSHA256()
    {
        if (!Auth_OpenID_HMACSHA256_SUPPORTED) {
            $this->pass();
            return;
        }

        $session_type = 'no-encryption';
        $this->assoc_type = 'HMAC-SHA256';

        list($session, $args) = $this->consumer->_createAssociateRequest(
                           $this->endpoint, $this->assoc_type, $session_type);

        $this->assertTrue(is_a($session, 'Auth_OpenID_PlainTextConsumerSession'));

        $expected = Auth_OpenID_Message::fromOpenIDArgs(
            [
                'ns' => Auth_OpenID_OPENID2_NS,
                'session_type' => $session_type,
                'mode' => 'associate',
                'assoc_type' => $this->assoc_type,
            ]);

        $this->assertEquals($expected->toPostArgs(),
                            $args->toPostArgs());

        $response = Auth_OpenID_Message::fromOpenIDArgs(
            [
                'ns' => Auth_OpenID_OPENID2_NS,
                'session_type' => $session_type,
                'assoc_type' => $this->assoc_type,
                'expires_in' => '10000000000',
                'mac_key' => 'ZM9v',
                'assoc_handle' => 'turnme',
            ]
        );

        $assoc = $this->consumer->_extractAssociation($response, $session);

        $this->assertTrue($assoc !== null);
        $this->assertTrue(is_a($assoc, 'Auth_OpenID_Association'));
        $this->assertTrue($assoc->assoc_type = $this->assoc_type);
        $this->assertTrue($assoc->session_type = $session_type);
    }

    function test_noEncryptionCompatibility()
    {
        $this->endpoint->use_compatibility = true;
        $session_type = 'no-encryption';
        list($session, $args) = $this->consumer->_createAssociateRequest(
                       $this->endpoint, $this->assoc_type, $session_type);

        $this->assertTrue(is_a($session, 'Auth_OpenID_PlainTextConsumerSession'));
        $this->assertEquals(Auth_OpenID_Message::fromOpenIDArgs([
            'mode' => 'associate',
            'assoc_type' => $this->assoc_type,
        ]),
            $args);
    }

    function test_dhSHA1Compatibility()
    {
        // Set the consumer's session type to a fast session since we
        // need it here.
        setConsumerSession($this->consumer);

        $this->endpoint->use_compatibility = true;
        $session_type = 'DH-SHA1';
        list($session, $args) = $this->consumer->_createAssociateRequest(
                       $this->endpoint, $this->assoc_type, $session_type);

        $this->assertTrue(is_a($session,
                               'Auth_OpenID_DiffieHellmanSHA1ConsumerSession'));

        // This is a random base-64 value, so just check that it's
        // present.
        $this->assertTrue($args->hasKey(Auth_OpenID_OPENID1_NS, 'dh_consumer_public'));
        $args->delArg(Auth_OpenID_OPENID1_NS, 'dh_consumer_public');

        // OK, session_type is set here and not for no-encryption
        // compatibility
        $expected = Auth_OpenID_Message::fromOpenIDArgs([
            'mode' => 'associate',
            'session_type' => 'DH-SHA1',
            'assoc_type' => $this->assoc_type,
            'dh_modulus' => 'BfvStQ==',
            'dh_gen' => 'Ag==',
        ]);

        $this->assertEquals($expected->toPostArgs(),
                            $args->toPostArgs());
    }
}

class TestDiffieHellmanResponseParameters extends PHPUnit_Framework_TestCase {
    var $session_cls = null;
    var $message_namespace = null;

    function setUp()
    {
        // Pre-compute DH with small prime so tests run quickly.
        $this->server_dh = new Auth_OpenID_DiffieHellman(100389557, 2);
        $this->consumer_dh = new Auth_OpenID_DiffieHellman(100389557, 2);

        $lib = Auth_OpenID_getMathLib();

        $cls = $this->session_cls;
        $this->consumer_session = new $cls($this->consumer_dh);

        // base64(btwoc(g ^ xb mod p))
        $this->dh_server_public = $lib->longToBase64($this->server_dh->public);

        $this->secret = Auth_OpenID_CryptUtil::randomString(
                            $this->consumer_session->secret_size);

        $this->enc_mac_key = base64_encode(
            $this->server_dh->xorSecret($this->consumer_dh->public,
                                        $this->secret,
                                        $this->consumer_session->hash_func));

        $this->msg = new Auth_OpenID_Message($this->message_namespace);
    }

    function testExtractSecret()
    {
        $this->msg->setArg(Auth_OpenID_OPENID_NS, 'dh_server_public',
                           $this->dh_server_public);

        $this->msg->setArg(Auth_OpenID_OPENID_NS, 'enc_mac_key',
                           $this->enc_mac_key);

        $extracted = $this->consumer_session->extractSecret($this->msg);
        $this->assertEquals($extracted, $this->secret);
    }

    function testAbsentServerPublic()
    {
        $this->msg->setArg(Auth_OpenID_OPENID_NS, 'enc_mac_key',
                           $this->enc_mac_key);

        $this->assertTrue($this->consumer_session->extractSecret($this->msg) === null);
    }

    function testAbsentMacKey()
    {
        $this->msg->setArg(Auth_OpenID_OPENID_NS, 'dh_server_public',
                           $this->dh_server_public);

        $this->assertTrue($this->consumer_session->extractSecret($this->msg) === null);
    }

    /*
    function testInvalidBase64Public()
    {
        $this->msg->setArg(Auth_OpenID_OPENID_NS, 'dh_server_public',
                           'n o t b a s e 6 4.');

        $this->msg->setArg(Auth_OpenID_OPENID_NS, 'enc_mac_key',
                           $this->enc_mac_key);

        $this->assertTrue($this->consumer_session->extractSecret($this->msg) === null);
    }

    function testInvalidBase64MacKey()
    {
        $this->msg->setArg(Auth_OpenID_OPENID_NS, 'dh_server_public',
                           $this->dh_server_public);

        $this->msg->setArg(Auth_OpenID_OPENID_NS, 'enc_mac_key',
                           'n o t base 64');

        $this->assertTrue($this->consumer_session->extractSecret($this->msg) === null);
    }
    */
}

class TestOpenID1SHA1 extends TestDiffieHellmanResponseParameters {
    var $session_cls = 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession';
    var $message_namespace = Auth_OpenID_OPENID1_NS;
}

class TestOpenID2SHA1 extends TestDiffieHellmanResponseParameters {
    var $session_cls = 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession';
    var $message_namespace = Auth_OpenID_OPENID2_NS;
}

if (!defined('Auth_OpenID_NO_MATH_SUPPORT') &&
    Auth_OpenID_SHA256_SUPPORTED) {
    class TestOpenID2SHA256 extends TestDiffieHellmanResponseParameters {
        var $session_cls = 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession';
        var $message_namespace = Auth_OpenID_OPENID2_NS;
    }
}

class Tests_Auth_OpenID_KVPost extends PHPUnit_Framework_TestCase {
    function setUp()
    {
        $this->server_url = 'http://unittest/bogus';
    }

    function test_200()
    {
        $response = new Auth_Yadis_HTTPResponse();
        $response->status = 200;
        $response->body = "foo:bar\nbaz:quux\n";
        $r = Auth_OpenID_GenericConsumer::_httpResponseToMessage($response, $this->server_url);
        $expected_msg = Auth_OpenID_Message::fromOpenIDArgs(['foo' => 'bar', 'baz' => 'quux']);
        $this->assertEquals($expected_msg, $r);
    }

    function test_400()
    {
        $response = new Auth_Yadis_HTTPResponse();
        $response->status = 400;
        $response->body = "error:bonk\nerror_code:7\n";
        $result = Auth_OpenID_GenericConsumer::_httpResponseToMessage($response, $this->server_url);

        $this->assertTrue(is_a($result, 'Auth_OpenID_ServerErrorContainer'));
        $this->assertEquals($result->error_text, 'bonk');
        $this->assertEquals($result->error_code, '7');
    }

    function test_500()
    {
        // 500 as an example of any non-200, non-400 code.
        $response = new Auth_Yadis_HTTPResponse();
        $response->status = 500;
        $response->body = "foo:bar\nbaz:quux\n";
        $result = Auth_OpenID_GenericConsumer::_httpResponseToMessage($response, $this->server_url);
        $this->assertTrue($result === null);
    }
}

// Add other test cases to be run.
global $Tests_Auth_OpenID_Consumer_other;
$Tests_Auth_OpenID_Consumer_other = [
    // new Tests_Auth_OpenID_Consumer_TestSetupNeeded(),
    new Tests_Auth_OpenID_AuthRequestHTMLMarkup(),
    new Tests_Auth_OpenID_Consumer_TestCheckAuth(),
    new Tests_Auth_OpenID_Consumer_TestCheckAuthTriggered(),
    new Tests_Auth_OpenID_Consumer_TestFetchAssoc(),
    new Tests_Auth_OpenID_Consumer_CheckNonceTest(),
    new Tests_Auth_OpenID_Complete(),
    new Tests_Auth_OpenID_SuccessResponse(),
    new Tests_Auth_OpenID_CheckAuthResponse(),
    new Tests_Auth_OpenID_FetchErrorInIdRes(),
    new Tests_Auth_OpenID_ConsumerTest2(),
    new Tests_Auth_OpenID_Stateless1(),
    new Tests_Auth_OpenID_Stateless2(),
    new TestCompleteMissingSig(),
    new TestReturnToArgs(),
    new IDPDrivenTest(),
    new TestDiscoveryVerification(),
    new Tests_Auth_OpenID_KVPost(),
    new Tests_idResURLMismatch(),
    new IdResCheckForFieldsTest(),
];

if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
    $Tests_Auth_OpenID_Consumer_other[] = new TestCreateAssociationRequest();
    $Tests_Auth_OpenID_Consumer_other[] = new TestOpenID1SHA1();
    $Tests_Auth_OpenID_Consumer_other[] = new TestOpenID2SHA1();
}

if (!defined('Auth_OpenID_NO_MATH_SUPPORT') &&
    Auth_OpenID_SHA256_SUPPORTED) {
    $Tests_Auth_OpenID_Consumer_other[] = new TestOpenID2SHA256();
}


