diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/mitmproxy/addons/test_proxyauth.py | 174 | ||||
-rw-r--r-- | test/mitmproxy/addons/test_upstream_auth.py | 65 | ||||
-rw-r--r-- | test/mitmproxy/net/http/test_authentication.py | 122 | ||||
-rw-r--r-- | test/mitmproxy/protocol/test_http1.py | 2 | ||||
-rw-r--r-- | test/mitmproxy/test_eventsequence.py | 81 | ||||
-rw-r--r-- | test/mitmproxy/test_proxy.py | 31 | ||||
-rw-r--r-- | test/mitmproxy/test_proxy_config.py | 21 | ||||
-rw-r--r-- | test/mitmproxy/test_server.py | 103 |
8 files changed, 364 insertions, 235 deletions
diff --git a/test/mitmproxy/addons/test_proxyauth.py b/test/mitmproxy/addons/test_proxyauth.py new file mode 100644 index 00000000..494a992f --- /dev/null +++ b/test/mitmproxy/addons/test_proxyauth.py @@ -0,0 +1,174 @@ +import binascii + +from mitmproxy import exceptions +from mitmproxy.test import taddons +from mitmproxy.test import tflow +from mitmproxy.test import tutils +from mitmproxy.addons import proxyauth + + +def test_parse_http_basic_auth(): + assert proxyauth.parse_http_basic_auth( + proxyauth.mkauth("test", "test") + ) == ("basic", "test", "test") + assert not proxyauth.parse_http_basic_auth("") + assert not proxyauth.parse_http_basic_auth("foo bar") + v = "basic " + binascii.b2a_base64(b"foo").decode("ascii") + assert not proxyauth.parse_http_basic_auth(v) + + +def test_configure(): + up = proxyauth.ProxyAuth() + with taddons.context() as ctx: + tutils.raises( + exceptions.OptionsError, + ctx.configure, up, auth_singleuser="foo" + ) + + ctx.configure(up, auth_singleuser="foo:bar") + assert up.singleuser == ["foo", "bar"] + + ctx.configure(up, auth_singleuser=None) + assert up.singleuser is None + + ctx.configure(up, auth_nonanonymous=True) + assert up.nonanonymous + ctx.configure(up, auth_nonanonymous=False) + assert not up.nonanonymous + + tutils.raises( + exceptions.OptionsError, + ctx.configure, + up, + auth_htpasswd = tutils.test_data.path( + "mitmproxy/net/data/server.crt" + ) + ) + tutils.raises( + exceptions.OptionsError, + ctx.configure, + up, + auth_htpasswd = "nonexistent" + ) + + ctx.configure( + up, + auth_htpasswd = tutils.test_data.path( + "mitmproxy/net/data/htpasswd" + ) + ) + assert up.htpasswd + assert up.htpasswd.check_password("test", "test") + assert not up.htpasswd.check_password("test", "foo") + ctx.configure(up, auth_htpasswd = None) + assert not up.htpasswd + + tutils.raises( + exceptions.OptionsError, + ctx.configure, + up, + auth_nonanonymous = True, + mode = "transparent" + ) + tutils.raises( + exceptions.OptionsError, + ctx.configure, + up, + auth_nonanonymous = True, + mode = "socks5" + ) + + +def test_check(): + up = proxyauth.ProxyAuth() + with taddons.context() as ctx: + ctx.configure(up, auth_nonanonymous=True) + f = tflow.tflow() + assert not up.check(f) + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" + ) + assert up.check(f) + + f.request.headers["Proxy-Authorization"] = "invalid" + assert not up.check(f) + + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test", scheme = "unknown" + ) + assert not up.check(f) + + ctx.configure(up, auth_nonanonymous=False, auth_singleuser="test:test") + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" + ) + assert up.check(f) + ctx.configure(up, auth_nonanonymous=False, auth_singleuser="test:foo") + assert not up.check(f) + + ctx.configure( + up, + auth_singleuser = None, + auth_htpasswd = tutils.test_data.path( + "mitmproxy/net/data/htpasswd" + ) + ) + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" + ) + assert up.check(f) + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "foo" + ) + assert not up.check(f) + + +def test_authenticate(): + up = proxyauth.ProxyAuth() + with taddons.context() as ctx: + ctx.configure(up, auth_nonanonymous=True) + + f = tflow.tflow() + assert not f.response + up.authenticate(f) + assert f.response.status_code == 407 + + f = tflow.tflow() + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" + ) + up.authenticate(f) + assert not f.response + assert not f.request.headers.get("Proxy-Authorization") + + f = tflow.tflow() + f.mode = "transparent" + assert not f.response + up.authenticate(f) + assert f.response.status_code == 401 + + f = tflow.tflow() + f.mode = "transparent" + f.request.headers["Authorization"] = proxyauth.mkauth( + "test", "test" + ) + up.authenticate(f) + assert not f.response + assert not f.request.headers.get("Authorization") + + +def test_handlers(): + up = proxyauth.ProxyAuth() + with taddons.context() as ctx: + ctx.configure(up, auth_nonanonymous=True) + + f = tflow.tflow() + assert not f.response + up.requestheaders(f) + assert f.response.status_code == 407 + + f = tflow.tflow() + f.request.method = "CONNECT" + assert not f.response + up.http_connect(f) + assert f.response.status_code == 407 diff --git a/test/mitmproxy/addons/test_upstream_auth.py b/test/mitmproxy/addons/test_upstream_auth.py new file mode 100644 index 00000000..985b13a7 --- /dev/null +++ b/test/mitmproxy/addons/test_upstream_auth.py @@ -0,0 +1,65 @@ +import base64 + +from mitmproxy import exceptions +from mitmproxy.test import taddons +from mitmproxy.test import tflow +from mitmproxy.test import tutils +from mitmproxy.addons import upstream_auth + + +def test_configure(): + up = upstream_auth.UpstreamAuth() + with taddons.context() as tctx: + tctx.configure(up, upstream_auth="test:test") + assert up.auth == b"Basic" + b" " + base64.b64encode(b"test:test") + + tctx.configure(up, upstream_auth="test:") + assert up.auth == b"Basic" + b" " + base64.b64encode(b"test:") + + tctx.configure(up, upstream_auth=None) + assert not up.auth + + tutils.raises( + exceptions.OptionsError, + tctx.configure, + up, + upstream_auth="" + ) + tutils.raises( + exceptions.OptionsError, + tctx.configure, + up, + upstream_auth=":" + ) + tutils.raises( + exceptions.OptionsError, + tctx.configure, + up, + upstream_auth=":test" + ) + + +def test_simple(): + up = upstream_auth.UpstreamAuth() + with taddons.context() as tctx: + tctx.configure(up, upstream_auth="foo:bar") + + f = tflow.tflow() + f.mode = "upstream" + up.requestheaders(f) + assert "proxy-authorization" in f.request.headers + + f = tflow.tflow() + up.requestheaders(f) + assert "proxy-authorization" not in f.request.headers + + tctx.configure(up, mode="reverse") + f = tflow.tflow() + f.mode = "transparent" + up.requestheaders(f) + assert "proxy-authorization" in f.request.headers + + f = tflow.tflow() + f.mode = "upstream" + up.http_connect(f) + assert "proxy-authorization" in f.request.headers diff --git a/test/mitmproxy/net/http/test_authentication.py b/test/mitmproxy/net/http/test_authentication.py deleted file mode 100644 index 01eae52d..00000000 --- a/test/mitmproxy/net/http/test_authentication.py +++ /dev/null @@ -1,122 +0,0 @@ -import binascii - -from mitmproxy.test import tutils -from mitmproxy.net.http import authentication, Headers - - -def test_parse_http_basic_auth(): - vals = ("basic", "foo", "bar") - assert authentication.parse_http_basic_auth( - authentication.assemble_http_basic_auth(*vals) - ) == vals - assert not authentication.parse_http_basic_auth("") - assert not authentication.parse_http_basic_auth("foo bar") - v = "basic " + binascii.b2a_base64(b"foo").decode("ascii") - assert not authentication.parse_http_basic_auth(v) - - -class TestPassManNonAnon: - - def test_simple(self): - p = authentication.PassManNonAnon() - assert not p.test("", "") - assert p.test("user", "") - - -class TestPassManHtpasswd: - - def test_file_errors(self): - tutils.raises( - "malformed htpasswd file", - authentication.PassManHtpasswd, - tutils.test_data.path("mitmproxy/net/data/server.crt")) - - def test_simple(self): - pm = authentication.PassManHtpasswd(tutils.test_data.path("mitmproxy/net/data/htpasswd")) - - vals = ("basic", "test", "test") - authentication.assemble_http_basic_auth(*vals) - assert pm.test("test", "test") - assert not pm.test("test", "foo") - assert not pm.test("foo", "test") - assert not pm.test("test", "") - assert not pm.test("", "") - - -class TestPassManSingleUser: - - def test_simple(self): - pm = authentication.PassManSingleUser("test", "test") - assert pm.test("test", "test") - assert not pm.test("test", "foo") - assert not pm.test("foo", "test") - - -class TestNullProxyAuth: - - def test_simple(self): - na = authentication.NullProxyAuth(authentication.PassManNonAnon()) - assert not na.auth_challenge_headers() - assert na.authenticate("foo") - na.clean({}) - - -class TestBasicProxyAuth: - - def test_simple(self): - ba = authentication.BasicProxyAuth(authentication.PassManNonAnon(), "test") - headers = Headers() - assert ba.auth_challenge_headers() - assert not ba.authenticate(headers) - - def test_authenticate_clean(self): - ba = authentication.BasicProxyAuth(authentication.PassManNonAnon(), "test") - - headers = Headers() - vals = ("basic", "foo", "bar") - headers[ba.AUTH_HEADER] = authentication.assemble_http_basic_auth(*vals) - assert ba.authenticate(headers) - - ba.clean(headers) - assert ba.AUTH_HEADER not in headers - - headers[ba.AUTH_HEADER] = "" - assert not ba.authenticate(headers) - - headers[ba.AUTH_HEADER] = "foo" - assert not ba.authenticate(headers) - - vals = ("foo", "foo", "bar") - headers[ba.AUTH_HEADER] = authentication.assemble_http_basic_auth(*vals) - assert not ba.authenticate(headers) - - ba = authentication.BasicProxyAuth(authentication.PassMan(), "test") - vals = ("basic", "foo", "bar") - headers[ba.AUTH_HEADER] = authentication.assemble_http_basic_auth(*vals) - assert not ba.authenticate(headers) - - -class Bunch: - pass - - -class TestAuthAction: - - def test_nonanonymous(self): - m = Bunch() - aa = authentication.NonanonymousAuthAction(None, "authenticator") - aa(None, m, None, None) - assert m.authenticator - - def test_singleuser(self): - m = Bunch() - aa = authentication.SingleuserAuthAction(None, "authenticator") - aa(None, m, "foo:bar", None) - assert m.authenticator - tutils.raises("invalid", aa, None, m, "foo", None) - - def test_httppasswd(self): - m = Bunch() - aa = authentication.HtpasswdAuthAction(None, "authenticator") - aa(None, m, tutils.test_data.path("mitmproxy/net/data/htpasswd"), None) - assert m.authenticator diff --git a/test/mitmproxy/protocol/test_http1.py b/test/mitmproxy/protocol/test_http1.py index 5026bef1..cd937ada 100644 --- a/test/mitmproxy/protocol/test_http1.py +++ b/test/mitmproxy/protocol/test_http1.py @@ -20,7 +20,7 @@ class TestInvalidRequests(tservers.HTTPProxyTest): with p.connect(): r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port)) assert r.status_code == 400 - assert b"Invalid HTTP request form" in r.content + assert b"Unexpected CONNECT" in r.content def test_relative_request(self): p = self.pathoc_raw() diff --git a/test/mitmproxy/test_eventsequence.py b/test/mitmproxy/test_eventsequence.py new file mode 100644 index 00000000..262df4b0 --- /dev/null +++ b/test/mitmproxy/test_eventsequence.py @@ -0,0 +1,81 @@ +from mitmproxy import events +import contextlib +from . import tservers + + +class Eventer: + def __init__(self, **handlers): + self.failure = None + self.called = [] + self.handlers = handlers + for i in events.Events - {"tick"}: + def mkprox(): + evt = i + + def prox(*args, **kwargs): + self.called.append(evt) + if evt in self.handlers: + try: + handlers[evt](*args, **kwargs) + except AssertionError as e: + self.failure = e + return prox + setattr(self, i, mkprox()) + + def fail(self): + pass + + +class SequenceTester: + @contextlib.contextmanager + def addon(self, addon): + self.master.addons.add(addon) + yield + self.master.addons.remove(addon) + if addon.failure: + raise addon.failure + + +class TestBasic(tservers.HTTPProxyTest, SequenceTester): + ssl = True + + def test_requestheaders(self): + + def hdrs(f): + assert f.request.headers + assert not f.request.content + + def req(f): + assert f.request.headers + assert f.request.content + + with self.addon(Eventer(requestheaders=hdrs, request=req)): + p = self.pathoc() + with p.connect(): + assert p.request("get:'/p/200':b@10").status_code == 200 + + def test_100_continue_fail(self): + e = Eventer() + with self.addon(e): + p = self.pathoc() + with p.connect(): + p.request( + """ + get:'/p/200' + h'expect'='100-continue' + h'content-length'='1000' + da + """ + ) + assert "requestheaders" in e.called + assert "responseheaders" not in e.called + + def test_connect(self): + e = Eventer() + with self.addon(e): + p = self.pathoc() + with p.connect(): + p.request("get:'/p/200:b@1'") + assert "http_connect" in e.called + assert e.called.count("requestheaders") == 1 + assert e.called.count("request") == 1 diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py index 7cadb6c2..aa3b8979 100644 --- a/test/mitmproxy/test_proxy.py +++ b/test/mitmproxy/test_proxy.py @@ -107,23 +107,12 @@ class TestProcessProxyOptions: self.assert_noerr("-T") self.assert_noerr("-U", "http://localhost") - self.assert_err("expected one argument", "-U") self.assert_err("Invalid server specification", "-U", "upstream") self.assert_noerr("--upstream-auth", "test:test") self.assert_err("expected one argument", "--upstream-auth") - self.assert_err( - "Invalid upstream auth specification", "--upstream-auth", "test" - ) self.assert_err("mutually exclusive", "-R", "http://localhost", "-T") - def test_socks_auth(self): - self.assert_err( - "Proxy Authentication not supported in SOCKS mode.", - "--socks", - "--nonanonymous" - ) - def test_client_certs(self): with tutils.tmpdir() as cadir: self.assert_noerr("--client-certs", cadir) @@ -141,26 +130,6 @@ class TestProcessProxyOptions: tutils.test_data.path("mitmproxy/data/testkey.pem")) self.assert_err("does not exist", "--cert", "nonexistent") - def test_auth(self): - p = self.assert_noerr("--nonanonymous") - assert p.authenticator - - p = self.assert_noerr( - "--htpasswd", - tutils.test_data.path("mitmproxy/data/htpasswd")) - assert p.authenticator - self.assert_err( - "malformed htpasswd file", - "--htpasswd", - tutils.test_data.path("mitmproxy/data/htpasswd.invalid")) - - p = self.assert_noerr("--singleuser", "test:test") - assert p.authenticator - self.assert_err( - "invalid single-user specification", - "--singleuser", - "test") - def test_insecure(self): p = self.assert_noerr("--insecure") assert p.openssl_verification_mode_server == SSL.VERIFY_NONE diff --git a/test/mitmproxy/test_proxy_config.py b/test/mitmproxy/test_proxy_config.py index e012cb5e..e2c39846 100644 --- a/test/mitmproxy/test_proxy_config.py +++ b/test/mitmproxy/test_proxy_config.py @@ -1,5 +1,4 @@ from mitmproxy.test import tutils -import base64 from mitmproxy.proxy import config @@ -26,23 +25,3 @@ def test_parse_server_spec(): config.parse_server_spec, "http://" ) - - -def test_parse_upstream_auth(): - tutils.raises( - "Invalid upstream auth specification", - config.parse_upstream_auth, - "" - ) - tutils.raises( - "Invalid upstream auth specification", - config.parse_upstream_auth, - ":" - ) - tutils.raises( - "Invalid upstream auth specification", - config.parse_upstream_auth, - ":test" - ) - assert config.parse_upstream_auth("test:test") == b"Basic" + b" " + base64.b64encode(b"test:test") - assert config.parse_upstream_auth("test:") == b"Basic" + b" " + base64.b64encode(b"test:") diff --git a/test/mitmproxy/test_server.py b/test/mitmproxy/test_server.py index 9fa6ed06..9429ab0f 100644 --- a/test/mitmproxy/test_server.py +++ b/test/mitmproxy/test_server.py @@ -6,6 +6,7 @@ from mitmproxy.test import tutils from mitmproxy import controller from mitmproxy import options from mitmproxy.addons import script +from mitmproxy.addons import proxyauth from mitmproxy import http from mitmproxy.proxy.config import HostMatcher, parse_server_spec import mitmproxy.net.http @@ -13,7 +14,6 @@ from mitmproxy.net import tcp from mitmproxy.net import socks from mitmproxy import certs from mitmproxy import exceptions -from mitmproxy.net.http import authentication from mitmproxy.net.http import http1 from mitmproxy.net.tcp import Address from pathod import pathoc @@ -50,10 +50,7 @@ class CommonMixin: def test_replay(self): assert self.pathod("304").status_code == 304 - if isinstance(self, tservers.HTTPUpstreamProxyTest) and self.ssl: - assert len(self.master.state.flows) == 2 - else: - assert len(self.master.state.flows) == 1 + assert len(self.master.state.flows) == 1 l = self.master.state.flows[-1] assert l.response.status_code == 304 l.request.path = "/p/305" @@ -288,6 +285,7 @@ class TestHTTP(tservers.HTTPProxyTest, CommonMixin): class TestHTTPAuth(tservers.HTTPProxyTest): def test_auth(self): + self.master.addons.add(proxyauth.ProxyAuth()) self.master.options.auth_singleuser = "test:test" assert self.pathod("202").status_code == 407 p = self.pathoc() @@ -298,14 +296,15 @@ class TestHTTPAuth(tservers.HTTPProxyTest): h'%s'='%s' """ % ( self.server.port, - mitmproxy.net.http.authentication.BasicProxyAuth.AUTH_HEADER, - authentication.assemble_http_basic_auth("basic", "test", "test") + "Proxy-Authorization", + proxyauth.mkauth("test", "test") )) assert ret.status_code == 202 class TestHTTPReverseAuth(tservers.ReverseProxyTest): def test_auth(self): + self.master.addons.add(proxyauth.ProxyAuth()) self.master.options.auth_singleuser = "test:test" assert self.pathod("202").status_code == 401 p = self.pathoc() @@ -315,8 +314,8 @@ class TestHTTPReverseAuth(tservers.ReverseProxyTest): '/p/202' h'%s'='%s' """ % ( - mitmproxy.net.http.authentication.BasicWebsiteAuth.AUTH_HEADER, - authentication.assemble_http_basic_auth("basic", "test", "test") + "Authorization", + proxyauth.mkauth("test", "test") )) assert ret.status_code == 202 @@ -672,6 +671,13 @@ class TestProxySSL(tservers.HTTPProxyTest): first_flow = self.master.state.flows[0] assert first_flow.server_conn.timestamp_ssl_setup + def test_via(self): + # tests that the ssl timestamp is present when ssl is used + f = self.pathod("200:b@10") + assert f.status_code == 200 + first_flow = self.master.state.flows[0] + assert not first_flow.server_conn.via + class MasterRedirectRequest(tservers.TestMaster): redirect_port = None # Set by TestRedirectRequest @@ -952,12 +958,15 @@ class TestUpstreamProxySSL( assert req.status_code == 418 # CONNECT from pathoc to chain[0], - assert self.proxy.tmaster.state.flow_count() == 2 + assert self.proxy.tmaster.state.flow_count() == 1 + assert self.proxy.tmaster.state.flows[0].server_conn.via # request from pathoc to chain[0] # CONNECT from proxy to chain[1], - assert self.chain[0].tmaster.state.flow_count() == 2 + assert self.chain[0].tmaster.state.flow_count() == 1 + assert self.chain[0].tmaster.state.flows[0].server_conn.via # request from proxy to chain[1] # request from chain[0] (regular proxy doesn't store CONNECTs) + assert not self.chain[1].tmaster.state.flows[0].server_conn.via assert self.chain[1].tmaster.state.flow_count() == 1 @@ -978,21 +987,12 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest): def test_reconnect(self): """ Tests proper functionality of ConnectionHandler.server_reconnect mock. - If we have a disconnect on a secure connection that's transparently proxified to - an upstream http proxy, we need to send the CONNECT request again. + If we have a disconnect on a secure connection that's transparently + proxified to an upstream http proxy, we need to send the CONNECT + request again. """ - self.chain[1].tmaster.addons.add( - RequestKiller([2]) - ) - self.chain[0].tmaster.addons.add( - RequestKiller( - [ - 1, # CONNECT - 3, # reCONNECT - 4 # request - ] - ) - ) + self.chain[0].tmaster.addons.add(RequestKiller([1, 2])) + self.chain[1].tmaster.addons.add(RequestKiller([1])) p = self.pathoc() with p.connect(): @@ -1000,44 +1000,27 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest): assert req.content == b"content" assert req.status_code == 418 - assert self.proxy.tmaster.state.flow_count() == 2 # CONNECT and request - # CONNECT, failing request, - assert self.chain[0].tmaster.state.flow_count() == 4 - # reCONNECT, request - # failing request, request - assert self.chain[1].tmaster.state.flow_count() == 2 - # (doesn't store (repeated) CONNECTs from chain[0] - # as it is a regular proxy) - - assert not self.chain[1].tmaster.state.flows[0].response # killed - assert self.chain[1].tmaster.state.flows[1].response - - assert self.proxy.tmaster.state.flows[0].request.first_line_format == "authority" - assert self.proxy.tmaster.state.flows[1].request.first_line_format == "relative" - - assert self.chain[0].tmaster.state.flows[ - 0].request.first_line_format == "authority" - assert self.chain[0].tmaster.state.flows[ - 1].request.first_line_format == "relative" - assert self.chain[0].tmaster.state.flows[ - 2].request.first_line_format == "authority" - assert self.chain[0].tmaster.state.flows[ - 3].request.first_line_format == "relative" - - assert self.chain[1].tmaster.state.flows[ - 0].request.first_line_format == "relative" - assert self.chain[1].tmaster.state.flows[ - 1].request.first_line_format == "relative" + # First request goes through all three proxies exactly once + assert self.proxy.tmaster.state.flow_count() == 1 + assert self.chain[0].tmaster.state.flow_count() == 1 + assert self.chain[1].tmaster.state.flow_count() == 1 req = p.request("get:'/p/418:b\"content2\"'") - assert req.status_code == 502 - assert self.proxy.tmaster.state.flow_count() == 3 # + new request - # + new request, repeated CONNECT from chain[1] - assert self.chain[0].tmaster.state.flow_count() == 6 - # (both terminated) - # nothing happened here - assert self.chain[1].tmaster.state.flow_count() == 2 + + assert self.proxy.tmaster.state.flow_count() == 2 + assert self.chain[0].tmaster.state.flow_count() == 2 + # Upstream sees two requests due to reconnection attempt + assert self.chain[1].tmaster.state.flow_count() == 3 + assert not self.chain[1].tmaster.state.flows[-1].response + assert not self.chain[1].tmaster.state.flows[-2].response + + # Reconnection failed, so we're now disconnected + tutils.raises( + exceptions.HttpException, + p.request, + "get:'/p/418:b\"content3\"'" + ) class AddUpstreamCertsToClientChainMixin: |