def setUp(self):
     self.plugin = MACAuthPlugin()
     application = PluggableAuthenticationMiddleware(
         stub_application, [["mac", self.plugin]], [["mac", self.plugin]],
         [["mac", self.plugin]], [], stub_request_classifier,
         stub_challenge_decider)
     self.app = TestApp(application)
 def setUp(self):
     self.plugin = MACAuthPlugin()
     application = PluggableAuthenticationMiddleware(stub_application,
                              [["mac", self.plugin]],
                              [["mac", self.plugin]],
                              [["mac", self.plugin]],
                              [],
                              stub_request_classifier,
                              stub_challenge_decider)
     self.app = TestApp(application)
class TestMACAuthPlugin(unittest.TestCase):
    """Testcases for the main MACAuthPlugin class."""
    def setUp(self):
        self.plugin = MACAuthPlugin()
        application = PluggableAuthenticationMiddleware(
            stub_application, [["mac", self.plugin]], [["mac", self.plugin]],
            [["mac", self.plugin]], [], stub_request_classifier,
            stub_challenge_decider)
        self.app = TestApp(application)

    def _get_credentials(self, **data):
        id = tokenlib.make_token(data)
        key = tokenlib.get_token_secret(id)
        return {"id": id, "key": key}

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

    def test_make_plugin_can_explicitly_set_all_properties(self):
        plugin = make_plugin(decode_mac_id=dotted_name("stub_decode_mac_id"),
                             nonce_cache="macauthlib:NonceCache")
        self.assertEquals(plugin.decode_mac_id, stub_decode_mac_id)
        self.assertTrue(isinstance(plugin.nonce_cache, macauthlib.NonceCache))

    def test_make_plugin_passes_on_args_to_nonce_cache(self):
        plugin = make_plugin(nonce_cache="macauthlib:NonceCache",
                             nonce_cache_nonce_timeout=42)
        self.assertTrue(isinstance(plugin.nonce_cache, macauthlib.NonceCache))
        self.assertEquals(plugin.nonce_cache.nonce_timeout, 42)
        self.assertRaises(TypeError,
                          make_plugin,
                          nonce_cache="macauthlib:NonceCache",
                          nonce_cache_invalid_arg="WHAWHAWHAWHA")

    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,
                          nonce_cache=dotted_name("unittest"),
                          nonce_cache_arg="invalidarg")

    def test_make_plugin_errors_out_if_decode_mac_id_is_not_callable(self):
        self.assertRaises(ValueError,
                          make_plugin,
                          decode_mac_id=dotted_name("unittest"))

    def test_make_plugin_produces_sensible_defaults(self):
        plugin = make_plugin()
        self.assertEquals(plugin.decode_mac_id.im_func,
                          MACAuthPlugin.decode_mac_id.im_func)
        self.assertTrue(isinstance(plugin.nonce_cache, macauthlib.NonceCache))

    def test_make_plugin_curries_args_to_decode_mac_id(self):
        plugin = make_plugin(decode_mac_id=dotted_name("stub_decode_mac_id"),
                             decode_mac_id_hello="hi")
        self.assertEquals(plugin.decode_mac_id(None, "id")[0], "id")
        self.assertEquals(plugin.decode_mac_id(None, "id")[1]["hello"], "hi")

    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] == "MAC")

    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"))
        # 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"))

    def test_authenticated_request_works(self):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        r = self.app.request(req)
        self.assertEquals(r.body, "*****@*****.**")

    def test_authentication_fails_when_macid_has_no_userid(self):
        creds = self._get_credentials(hello="world")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        self.app.request(req, status=401)

    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_macid_fails(self):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        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):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        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):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        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):
        creds = self._get_credentials(username="******")
        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})
        macauthlib.sign_request(req, **creds)
        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})
        macauthlib.sign_request(req, **creds)
        self.app.request(req, status=401)

    def test_authentication_with_far_future_timestamp_fails(self):
        creds = self._get_credentials(username="******")
        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})
        macauthlib.sign_request(req, **creds)
        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})
        macauthlib.sign_request(req, **creds)
        self.app.request(req, status=401)

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

    def test_authentication_with_busted_macid_fails(self):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        id = macauthlib.utils.parse_authz_header(req)["id"]
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace(id, "XXX" + id)
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_with_busted_signature_fails(self):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        signature = macauthlib.utils.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.
        creds = self._get_credentials(username="******")
        req = Request.blank("/public")
        macauthlib.sign_request(req, **creds)
        resp = self.app.request(req)
        self.assertEquals(resp.body, "public")
        # Request with invalid credentials gets a 401.
        req = Request.blank("/public")
        macauthlib.sign_request(req, **creds)
        signature = macauthlib.utils.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)
class TestMACAuthPlugin(unittest.TestCase):
    """Testcases for the main MACAuthPlugin class."""

    def setUp(self):
        self.plugin = MACAuthPlugin()
        application = PluggableAuthenticationMiddleware(stub_application,
                                 [["mac", self.plugin]],
                                 [["mac", self.plugin]],
                                 [["mac", self.plugin]],
                                 [],
                                 stub_request_classifier,
                                 stub_challenge_decider)
        self.app = TestApp(application)

    def _get_credentials(self, **data):
        id = tokenlib.make_token(data)
        key = tokenlib.get_token_secret(id)
        return {"id": id, "key": key}

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

    def test_make_plugin_can_explicitly_set_all_properties(self):
        plugin = make_plugin(
            decode_mac_id=dotted_name("stub_decode_mac_id"),
            nonce_cache="macauthlib:NonceCache")
        self.assertEquals(plugin.decode_mac_id, stub_decode_mac_id)
        self.assertTrue(isinstance(plugin.nonce_cache, macauthlib.NonceCache))

    def test_make_plugin_passes_on_args_to_nonce_cache(self):
        plugin = make_plugin(
            nonce_cache="macauthlib:NonceCache",
            nonce_cache_nonce_timeout=42)
        self.assertTrue(isinstance(plugin.nonce_cache, macauthlib.NonceCache))
        self.assertEquals(plugin.nonce_cache.nonce_timeout, 42)
        self.assertRaises(TypeError, make_plugin,
            nonce_cache="macauthlib:NonceCache",
            nonce_cache_invalid_arg="WHAWHAWHAWHA")

    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,
                                      nonce_cache=dotted_name("unittest"),
                                      nonce_cache_arg="invalidarg")

    def test_make_plugin_errors_out_if_decode_mac_id_is_not_callable(self):
        self.assertRaises(ValueError, make_plugin,
                                      decode_mac_id=dotted_name("unittest"))

    def test_make_plugin_produces_sensible_defaults(self):
        plugin = make_plugin()
        self.assertEquals(plugin.decode_mac_id.im_func,
                          MACAuthPlugin.decode_mac_id.im_func)
        self.assertTrue(isinstance(plugin.nonce_cache, macauthlib.NonceCache))

    def test_make_plugin_curries_args_to_decode_mac_id(self):
        plugin = make_plugin(
            decode_mac_id=dotted_name("stub_decode_mac_id"),
            decode_mac_id_hello="hi")
        self.assertEquals(plugin.decode_mac_id(None, "id")[0], "id")
        self.assertEquals(plugin.decode_mac_id(None, "id")[1]["hello"], "hi")

    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] == "MAC")

    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"))
        # 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"))

    def test_authenticated_request_works(self):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        r = self.app.request(req)
        self.assertEquals(r.body, "*****@*****.**")

    def test_authentication_fails_when_macid_has_no_userid(self):
        creds = self._get_credentials(hello="world")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        self.app.request(req, status=401)

    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_macid_fails(self):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        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):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        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):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        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):
        creds = self._get_credentials(username="******")
        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})
        macauthlib.sign_request(req, **creds)
        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})
        macauthlib.sign_request(req, **creds)
        self.app.request(req, status=401)

    def test_authentication_with_far_future_timestamp_fails(self):
        creds = self._get_credentials(username="******")
        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})
        macauthlib.sign_request(req, **creds)
        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})
        macauthlib.sign_request(req, **creds)
        self.app.request(req, status=401)

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

    def test_authentication_with_busted_macid_fails(self):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        id = macauthlib.utils.parse_authz_header(req)["id"]
        authz = req.environ["HTTP_AUTHORIZATION"]
        authz = authz.replace(id, "XXX" + id)
        req.environ["HTTP_AUTHORIZATION"] = authz
        self.app.request(req, status=401)

    def test_authentication_with_busted_signature_fails(self):
        creds = self._get_credentials(username="******")
        req = Request.blank("/")
        macauthlib.sign_request(req, **creds)
        signature = macauthlib.utils.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.
        creds = self._get_credentials(username="******")
        req = Request.blank("/public")
        macauthlib.sign_request(req, **creds)
        resp = self.app.request(req)
        self.assertEquals(resp.body, "public")
        # Request with invalid credentials gets a 401.
        req = Request.blank("/public")
        macauthlib.sign_request(req, **creds)
        signature = macauthlib.utils.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)