def setUp(self):
     self.plugin = VEPAuthPlugin(audiences=["localhost"],
                                 verifier=vep.DummyVerifier(),
                                 token_manager=StubTokenManager())
     application = PluggableAuthenticationMiddleware(stub_application,
                              [["vep", self.plugin]],
                              [["vep", self.plugin]],
                              [["vep", self.plugin]],
                              [],
                              stub_request_classifier,
                              stub_challenge_decider)
     self.app = TestApp(application)
 def setUp(self):
     self.plugin = VEPAuthPlugin(audiences=["localhost"], verifier=vep.DummyVerifier())
     application = PluggableAuthenticationMiddleware(
         stub_application,
         [["vep", self.plugin]],
         [["vep", self.plugin]],
         [["vep", self.plugin]],
         [],
         stub_request_classifier,
         stub_challenge_decider,
     )
     self.app = TestApp(application)
class TestVEPAuthPlugin(unittest2.TestCase):
    """Testcases for the main VEPAuthPlugin class."""

    def setUp(self):
        self.plugin = VEPAuthPlugin(
            audiences=["localhost"], verifier=vep.DummyVerifier(), token_manager=StubTokenManager()
        )
        application = PluggableAuthenticationMiddleware(
            stub_application,
            [["vep", self.plugin]],
            [["vep", self.plugin]],
            [["vep", self.plugin]],
            [],
            stub_request_classifier,
            stub_challenge_decider,
        )
        self.app = TestApp(application)

    def _make_assertion(self, address, audience="http://localhost", **kwds):
        return vep.DummyVerifier.make_assertion(address, audience, **kwds)

    def _start_session(self, email="*****@*****.**", *args, **kwds):
        assertion = self._make_assertion(email, *args, **kwds)
        headers = {"Authorization": "Browser-ID " + assertion}
        session = self.app.get(self.plugin.token_url, headers=headers).json
        return {"token": session["id"], "secret": session["key"]}

    def test_implements(self):
        verifyClass(IIdentifier, VEPAuthPlugin)
        verifyClass(IAuthenticator, VEPAuthPlugin)
        verifyClass(IChallenger, VEPAuthPlugin)

    def test_make_plugin_can_explicitly_set_all_properties(self):
        plugin = make_plugin(
            audiences="example.com",
            token_url="/test_token_url",
            verifier="vep:DummyVerifier",
            token_manager="vep:LocalVerifier",
            nonce_timeout=42,
        )
        self.assertEquals(plugin.audiences, ["example.com"])
        self.assertEquals(plugin.token_url, "/test_token_url")
        self.assertTrue(isinstance(plugin.verifier, vep.DummyVerifier))
        self.assertTrue(isinstance(plugin.token_manager, vep.LocalVerifier))
        self.assertEquals(plugin.nonce_timeout, 42)

    def test_make_plugin_produces_sensible_defaults(self):
        # The "audiences" parameter must be set explicitly
        self.assertRaises(ValueError, make_plugin)
        plugin = make_plugin("one two")
        self.assertEquals(plugin.audiences, ["one", "two"])
        self.assertEquals(plugin.token_url, "/request_token")
        self.assertTrue(isinstance(plugin.verifier, vep.RemoteVerifier))
        self.assertTrue(isinstance(plugin.token_manager, SignedTokenManager))
        self.assertEquals(plugin.nonce_timeout, 60)

    def test_make_plugin_loads_urlopen_from_dotted_name(self):
        plugin = make_plugin("one two", verifier="vep:LocalVerifier", verifier_urlopen="urllib2:urlopen")
        self.assertEquals(plugin.audiences, ["one", "two"])
        self.assertTrue(isinstance(plugin.verifier, vep.LocalVerifier))
        self.assertEquals(plugin.verifier.urlopen, urllib2.urlopen)

    def test_make_plugin_treats_empty_audiences_string_as_none(self):
        plugin = make_plugin("")
        self.assertEquals(plugin.audiences, None)
        plugin = make_plugin(" ")
        self.assertEquals(plugin.audiences, [])

    def test_make_plugin_errors_out_on_unexpected_keyword_args(self):
        self.assertRaises(TypeError, make_plugin, "", unexpected="spanish-inquisition")

    def test_make_plugin_errors_out_on_args_to_a_non_callable(self):
        self.assertRaises(ValueError, make_plugin, "", verifier="vep:__doc__", verifier_urlopen="urllib2:urlopen")

    def test_checking_for_silly_argument_errors(self):
        self.assertRaises(ValueError, VEPAuthPlugin, audiences="notalist")

    def test_remember_does_nothing(self):
        self.assertEquals(self.plugin.remember(make_environ(), {}), [])

    def test_forget_gives_a_challenge_header(self):
        headers = self.plugin.forget(make_environ(), {})
        self.assertEquals(len(headers), 1)
        self.assertEquals(headers[0][0], "WWW-Authenticate")
        self.assertTrue(headers[0][1].startswith("MAC+BrowserID "))
        self.assertTrue(self.plugin.token_url in headers[0][1])

    def test_unauthenticated_requests_get_a_challenge(self):
        # Requests to most URLs generate a 401, which is passed through
        # with the appropriate challenge.
        r = self.app.get("/", status=401)
        challenge = r.headers["WWW-Authenticate"]
        self.assertTrue(challenge.startswith("MAC+BrowserID"))
        self.assertTrue(self.plugin.token_url in challenge)
        # Requests to URLs with "forbidden" generate a 403 in the downstream
        # app, which should be converted into a 401 by the plugin.
        r = self.app.get("/forbidden", status=401)
        challenge = r.headers["WWW-Authenticate"]
        self.assertTrue(challenge.startswith("MAC+BrowserID"))
        self.assertTrue(self.plugin.token_url in challenge)

    def test_sending_an_assertion_creates_a_token(self):
        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}
        # This fails since we're not at the token-provisioning URL.
        r = self.app.get("/", headers=headers, status=401)
        self.assertTrue("id" not in r.body)
        # This works since we're at the postback url.
        r = self.app.get(self.plugin.token_url, headers=headers)
        self.assertTrue("id" in r.body)

    def test_that_an_empty_token_url_disables_provisioning(self):
        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}
        self.plugin.token_url = ""
        r = self.app.get("/", headers=headers, status=401)
        self.assertTrue("id" not in r.body)
        r = self.app.get("/request_token", headers=headers, status=401)
        self.assertTrue("id" not in r.body)

    def test_non_get_requests_give_405(self):
        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}
        self.app.post(self.plugin.token_url, headers=headers, status=405)

    def test_provisioning_with_malformed_assertion(self):
        authz = "Browser-ID I AINT NO ASSERTION, FOOL!"
        headers = {"Authorization": authz}
        r = self.app.get(self.plugin.token_url, headers=headers, status=400)
        self.assertTrue("assertion" in r.body)

    def test_provisioning_with_no_credentials_gives_401(self):
        headers = {}
        self.app.get(self.plugin.token_url, headers=headers, status=401)

    def test_provisioning_with_basic_credentials_gives_400(self):
        headers = {"Authorization": "Basic dTpw"}
        self.app.get(self.plugin.token_url, headers=headers, status=400)

    def test_provisioning_with_untrusted_assertion(self):
        assertion = self._make_assertion("test@moz", assertion_sig="X")
        headers = {"Authorization": "Browser-ID " + assertion}
        r = self.app.get(self.plugin.token_url, headers=headers, status=400)
        self.assertTrue("assertion" in r.body)

    def test_provisioning_with_invalid_audience(self):
        assertion = self._make_assertion("*****@*****.**", "http://evil.com")
        headers = {"Authorization": "Browser-ID " + assertion}
        r = self.app.get(self.plugin.token_url, headers=headers, status=400)
        self.assertTrue("audience" in r.body)
        # Setting audiences to None will allow it to pass
        # if it matches the HTTP_HOST header.
        self.plugin.audiences = None
        r = self.app.get(self.plugin.token_url, headers=headers, status=400)
        self.assertTrue("audience" in r.body)
        r = self.app.get(self.plugin.token_url, headers=headers, extra_environ={"HTTP_HOST": "evil.com"})
        self.assertTrue("id" in r.body)

    def test_provisioning_with_unaccepted_email_address(self):
        assertion = self._make_assertion("*****@*****.**")
        headers = {"Authorization": "Browser-ID " + assertion}
        self.app.get(self.plugin.token_url, headers=headers, status=401)

    def test_authenticated_request_works(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        r = self.app.request(req)
        self.assertEquals(r.body, "*****@*****.**")

    def test_authentication_with_non_mac_scheme_fails(self):
        req = Request.blank("/")
        req.authorization = "OpenID hello=world"
        self.app.request(req, status=401)

    def test_authentication_without_consumer_key_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace("id", "idd")
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_without_timestamp_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace("ts", "typostamp")
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_without_nonce_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace("nonce", "typonce")
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_with_expired_timestamp_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        # Do an initial request so that the server can
        # calculate and cache our clock skew.
        ts = str(int(time.time()))
        req.authorization = ("MAC", {"ts": ts})
        sign_request(req, **session)
        self.app.request(req, status=200)
        # Now do one with a really old timestamp.
        ts = str(int(time.time() - 1000))
        req.authorization = ("MAC", {"ts": ts})
        sign_request(req, **session)
        self.app.request(req, status=401)

    def test_authentication_with_far_future_timestamp_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        # Do an initial request so that the server can
        # calculate and cache our clock skew.
        ts = str(int(time.time()))
        req.authorization = ("MAC", {"ts": ts})
        sign_request(req, **session)
        self.app.request(req, status=200)
        # Now do one with a far future timestamp.
        ts = str(int(time.time() + 1000))
        req.authorization = ("MAC", {"ts": ts})
        sign_request(req, **session)
        self.app.request(req, status=401)

    def test_authentication_with_reused_nonce_fails(self):
        session = self._start_session()
        # First request with that nonce should succeed.
        req = Request.blank("/")
        req.authorization = ("MAC", {"nonce": "PEPPER"})
        sign_request(req, **session)
        r = self.app.request(req)
        self.assertEquals(r.body, "*****@*****.**")
        # Second request with that nonce should fail.
        req = Request.blank("/")
        req.authorization = ("MAC", {"nonce": "PEPPER"})
        sign_request(req, **session)
        self.app.request(req, status=401)

    def test_authentication_with_busted_token_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        token = parse_authz_header(req)["id"]
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace(token, "XXX" + token)
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_with_busted_signature_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        signature = parse_authz_header(req)["mac"]
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace(signature, "XXX" + signature)
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_access_to_public_urls(self):
        # Request with no credentials is allowed access.
        req = Request.blank("/public")
        resp = self.app.request(req)
        self.assertEquals(resp.body, "public")
        # Request with valid credentials is allowed access.
        session = self._start_session()
        req = Request.blank("/public")
        sign_request(req, **session)
        resp = self.app.request(req)
        self.assertEquals(resp.body, "public")
        # Request with invalid credentials gets a 401.
        req = Request.blank("/public")
        sign_request(req, **session)
        signature = parse_authz_header(req)["mac"]
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace(signature, "XXX" + signature)
        req.environ["HTTP_AUTHORIZATION"] = authz
        resp = self.app.request(req, status=401)

    def test_authenticate_only_accepts_mac_credentials(self):
        # Yes, this is a rather pointless test that boosts line coverage...
        self.assertEquals(self.plugin.authenticate(make_environ(), {}), None)

    def test_token_url_can_contain_placeholders(self):
        self.plugin.token_url = "/{application}/{version}/{test}/foo"

        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}

        # this doesn't match the pattern and should return a 401
        r = self.app.get("/foo/bar/bar", headers=headers, status=401)
        self.assertTrue("id" not in r.body)

        # valid pattern should return a consumer key
        r = self.app.get("/foo/1.0/bar/foo", headers=headers)
        self.assertTrue("id" in r.body)

        self.plugin.token_manager.applications = {"foo": ["1.0"], "bar": ["2.1"], "baz": ["1.2"]}

        # defining manually a set of applications an making a request for one
        # of them should work
        r = self.app.get("/foo/1.0/bar/foo", headers=headers)
        self.assertTrue("id" in r.body)

        # this doesn't match any of the defined applications and should return
        # a 404
        self.assertRaises(HTTPNotFound, self.app.get, "/not_an_app/1.0/bar/foo", headers=headers)
        # bad version
        self.assertRaises(HTTPNotFound, self.app.get, "/foo/1.4/bar/foo", headers=headers)

    def test_extra_data_gets_added(self):
        self.plugin.token_manager = ExtraTokenManager()

        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}
        resp = self.app.get(self.plugin.token_url, headers=headers, status=200)
        self.assertTrue("foo" in resp.json)
        self.assertEqual(resp.json["foo"], "bar")
class TestVEPAuthPlugin(unittest2.TestCase):
    """Testcases for the main VEPAuthPlugin class."""

    def setUp(self):
        self.plugin = VEPAuthPlugin(audiences=["localhost"],
                                    verifier=vep.DummyVerifier(),
                                    token_manager=StubTokenManager())
        application = PluggableAuthenticationMiddleware(stub_application,
                                 [["vep", self.plugin]],
                                 [["vep", self.plugin]],
                                 [["vep", self.plugin]],
                                 [],
                                 stub_request_classifier,
                                 stub_challenge_decider)
        self.app = TestApp(application)

    def _make_assertion(self, address, audience="http://localhost", **kwds):
        return vep.DummyVerifier.make_assertion(address, audience, **kwds)

    def _start_session(self, email="*****@*****.**", *args, **kwds):
        assertion = self._make_assertion(email, *args, **kwds)
        headers = {"Authorization": "Browser-ID " + assertion}
        session = self.app.get(self.plugin.token_url, headers=headers).json
        return {
            "token": session["id"],
            "secret": session["key"],
        }

    def test_implements(self):
        verifyClass(IIdentifier, VEPAuthPlugin)
        verifyClass(IAuthenticator, VEPAuthPlugin)
        verifyClass(IChallenger, VEPAuthPlugin)

    def test_make_plugin_can_explicitly_set_all_properties(self):
        plugin = make_plugin(
            audiences="example.com",
            token_url="/test_token_url",
            verifier="vep:DummyVerifier",
            token_manager="vep:LocalVerifier",
            nonce_timeout=42)
        self.assertEquals(plugin.audiences, ["example.com"])
        self.assertEquals(plugin.token_url, "/test_token_url")
        self.assertTrue(isinstance(plugin.verifier, vep.DummyVerifier))
        self.assertTrue(isinstance(plugin.token_manager, vep.LocalVerifier))
        self.assertEquals(plugin.nonce_timeout, 42)

    def test_make_plugin_produces_sensible_defaults(self):
        # The "audiences" parameter must be set explicitly
        self.assertRaises(ValueError, make_plugin)
        plugin = make_plugin("one two")
        self.assertEquals(plugin.audiences, ["one", "two"])
        self.assertEquals(plugin.token_url, "/request_token")
        self.assertTrue(isinstance(plugin.verifier, vep.RemoteVerifier))
        self.assertTrue(isinstance(plugin.token_manager, SignedTokenManager))
        self.assertEquals(plugin.nonce_timeout, 60)

    def test_make_plugin_loads_urlopen_from_dotted_name(self):
        plugin = make_plugin("one two", verifier="vep:LocalVerifier",
                             verifier_urlopen="urllib2:urlopen")
        self.assertEquals(plugin.audiences, ["one", "two"])
        self.assertTrue(isinstance(plugin.verifier, vep.LocalVerifier))
        self.assertEquals(plugin.verifier.urlopen, urllib2.urlopen)

    def test_make_plugin_treats_empty_audiences_string_as_none(self):
        plugin = make_plugin("")
        self.assertEquals(plugin.audiences, None)
        plugin = make_plugin(" ")
        self.assertEquals(plugin.audiences, [])

    def test_make_plugin_errors_out_on_unexpected_keyword_args(self):
        self.assertRaises(TypeError, make_plugin, "",
                                     unexpected="spanish-inquisition")

    def test_make_plugin_errors_out_on_args_to_a_non_callable(self):
        self.assertRaises(ValueError, make_plugin, "",
                                     verifier="vep:__doc__",
                                     verifier_urlopen="urllib2:urlopen")

    def test_checking_for_silly_argument_errors(self):
        self.assertRaises(ValueError, VEPAuthPlugin, audiences="notalist")

    def test_remember_does_nothing(self):
        self.assertEquals(self.plugin.remember(make_environ(), {}), [])

    def test_forget_gives_a_challenge_header(self):
        headers = self.plugin.forget(make_environ(), {})
        self.assertEquals(len(headers), 1)
        self.assertEquals(headers[0][0], "WWW-Authenticate")
        self.assertTrue(headers[0][1].startswith("MAC+BrowserID "))
        self.assertTrue(self.plugin.token_url in headers[0][1])

    def test_unauthenticated_requests_get_a_challenge(self):
        # Requests to most URLs generate a 401, which is passed through
        # with the appropriate challenge.
        r = self.app.get("/", status=401)
        challenge = r.headers["WWW-Authenticate"]
        self.assertTrue(challenge.startswith("MAC+BrowserID"))
        self.assertTrue(self.plugin.token_url in challenge)
        # Requests to URLs with "forbidden" generate a 403 in the downstream
        # app, which should be converted into a 401 by the plugin.
        r = self.app.get("/forbidden", status=401)
        challenge = r.headers["WWW-Authenticate"]
        self.assertTrue(challenge.startswith("MAC+BrowserID"))
        self.assertTrue(self.plugin.token_url in challenge)

    def test_sending_an_assertion_creates_a_token(self):
        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}
        # This fails since we're not at the token-provisioning URL.
        r = self.app.get("/", headers=headers, status=401)
        self.assertTrue("id" not in r.body)
        # This works since we're at the postback url.
        r = self.app.get(self.plugin.token_url, headers=headers)
        self.assertTrue("id" in r.body)

    def test_that_an_empty_token_url_disables_provisioning(self):
        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}
        self.plugin.token_url = ""
        r = self.app.get("/", headers=headers, status=401)
        self.assertTrue("id" not in r.body)
        r = self.app.get("/request_token", headers=headers, status=401)
        self.assertTrue("id" not in r.body)

    def test_non_get_requests_give_405(self):
        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}
        self.app.post(self.plugin.token_url, headers=headers, status=405)

    def test_provisioning_with_malformed_assertion(self):
        authz = "Browser-ID I AINT NO ASSERTION, FOOL!"
        headers = {"Authorization": authz}
        r = self.app.get(self.plugin.token_url, headers=headers, status=400)
        self.assertTrue("assertion" in r.body)

    def test_provisioning_with_no_credentials_gives_401(self):
        headers = {}
        self.app.get(self.plugin.token_url, headers=headers, status=401)

    def test_provisioning_with_basic_credentials_gives_400(self):
        headers = {"Authorization": "Basic dTpw"}
        self.app.get(self.plugin.token_url, headers=headers, status=400)

    def test_provisioning_with_untrusted_assertion(self):
        assertion = self._make_assertion("test@moz", assertion_sig="X")
        headers = {"Authorization": "Browser-ID " + assertion}
        r = self.app.get(self.plugin.token_url, headers=headers, status=400)
        self.assertTrue("assertion" in r.body)

    def test_provisioning_with_invalid_audience(self):
        assertion = self._make_assertion("*****@*****.**", "http://evil.com")
        headers = {"Authorization": "Browser-ID " + assertion}
        r = self.app.get(self.plugin.token_url, headers=headers, status=400)
        self.assertTrue("audience" in r.body)
        # Setting audiences to None will allow it to pass
        # if it matches the HTTP_HOST header.
        self.plugin.audiences = None
        r = self.app.get(self.plugin.token_url, headers=headers, status=400)
        self.assertTrue("audience" in r.body)
        r = self.app.get(self.plugin.token_url, headers=headers,
                         extra_environ={"HTTP_HOST": "evil.com"})
        self.assertTrue("id" in r.body)

    def test_provisioning_with_unaccepted_email_address(self):
        assertion = self._make_assertion("*****@*****.**")
        headers = {"Authorization": "Browser-ID " + assertion}
        self.app.get(self.plugin.token_url, headers=headers, status=401)

    def test_authenticated_request_works(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        r = self.app.request(req)
        self.assertEquals(r.body, "*****@*****.**")

    def test_authentication_with_non_mac_scheme_fails(self):
        req = Request.blank("/")
        req.authorization = "OpenID hello=world"
        self.app.request(req, status=401)

    def test_authentication_without_consumer_key_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace("id", "idd")
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_without_timestamp_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace("ts", "typostamp")
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_without_nonce_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace("nonce", "typonce")
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_with_expired_timestamp_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        # Do an initial request so that the server can
        # calculate and cache our clock skew.
        ts = str(int(time.time()))
        req.authorization = ("MAC", {"ts": ts})
        sign_request(req, **session)
        self.app.request(req, status=200)
        # Now do one with a really old timestamp.
        ts = str(int(time.time() - 1000))
        req.authorization = ("MAC", {"ts": ts})
        sign_request(req, **session)
        self.app.request(req, status=401)

    def test_authentication_with_far_future_timestamp_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        # Do an initial request so that the server can
        # calculate and cache our clock skew.
        ts = str(int(time.time()))
        req.authorization = ("MAC", {"ts": ts})
        sign_request(req, **session)
        self.app.request(req, status=200)
        # Now do one with a far future timestamp.
        ts = str(int(time.time() + 1000))
        req.authorization = ("MAC", {"ts": ts})
        sign_request(req, **session)
        self.app.request(req, status=401)

    def test_authentication_with_reused_nonce_fails(self):
        session = self._start_session()
        # First request with that nonce should succeed.
        req = Request.blank("/")
        req.authorization = ("MAC", {"nonce": "PEPPER"})
        sign_request(req, **session)
        r = self.app.request(req)
        self.assertEquals(r.body, "*****@*****.**")
        # Second request with that nonce should fail.
        req = Request.blank("/")
        req.authorization = ("MAC", {"nonce": "PEPPER"})
        sign_request(req, **session)
        self.app.request(req, status=401)

    def test_authentication_with_busted_token_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        token = parse_authz_header(req)["id"]
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace(token, "XXX" + token)
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_with_busted_signature_fails(self):
        session = self._start_session()
        req = Request.blank("/")
        sign_request(req, **session)
        signature = parse_authz_header(req)["mac"]
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace(signature, "XXX" + signature)
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_access_to_public_urls(self):
        # Request with no credentials is allowed access.
        req = Request.blank("/public")
        resp = self.app.request(req)
        self.assertEquals(resp.body, "public")
        # Request with valid credentials is allowed access.
        session = self._start_session()
        req = Request.blank("/public")
        sign_request(req, **session)
        resp = self.app.request(req)
        self.assertEquals(resp.body, "public")
        # Request with invalid credentials gets a 401.
        req = Request.blank("/public")
        sign_request(req, **session)
        signature = parse_authz_header(req)["mac"]
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace(signature, "XXX" + signature)
        req.environ["HTTP_AUTHORIZATION"] = authz
        resp = self.app.request(req, status=401)

    def test_authenticate_only_accepts_mac_credentials(self):
        # Yes, this is a rather pointless test that boosts line coverage...
        self.assertEquals(self.plugin.authenticate(make_environ(), {}), None)

    def test_token_url_can_contain_placeholders(self):
        self.plugin.token_url = "/{application}/{version}/{test}/foo"

        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}

        # this doesn't match the pattern and should return a 401
        r = self.app.get("/foo/bar/bar", headers=headers, status=401)
        self.assertTrue("id" not in r.body)

        # valid pattern should return a consumer key
        r = self.app.get("/foo/1.0/bar/foo", headers=headers)
        self.assertTrue("id" in r.body)

        self.plugin.token_manager.applications = {"foo": ["1.0"],
                                                  "bar": ["2.1"],
                                                  "baz": ["1.2"]}

        # defining manually a set of applications an making a request for one
        # of them should work
        r = self.app.get("/foo/1.0/bar/foo", headers=headers)
        self.assertTrue("id" in r.body)

        # this doesn't match any of the defined applications and should return
        # a 404
        self.assertRaises(HTTPNotFound, self.app.get,
                          "/not_an_app/1.0/bar/foo", headers=headers)
        # bad version
        self.assertRaises(HTTPNotFound, self.app.get, "/foo/1.4/bar/foo",
                headers=headers)

    def test_extra_data_gets_added(self):
        self.plugin.token_manager = ExtraTokenManager()

        authz = "Browser-ID " + self._make_assertion("*****@*****.**")
        headers = {"Authorization": authz}
        resp = self.app.get(self.plugin.token_url, headers=headers, status=200)
        self.assertTrue('foo' in resp.json)
        self.assertEqual(resp.json['foo'], 'bar')