def _setUp(self, request: Any, mocker: MockerFixture) -> None: self.mock_fromfd = mocker.patch('socket.fromfd') self.mock_selector = mocker.patch('selectors.DefaultSelector') self.mock_server_conn = mocker.patch( 'proxy.http.proxy.server.TcpServerConnection', ) self.fileno = 10 self._addr = ('127.0.0.1', 54382) adblock_json_path = Path( __file__, ).parent.parent.parent / "proxy" / "plugin" / "adblock.json" self.flags = FlagParser.initialize( input_args=[ "--filtered-url-regex-config", str(adblock_json_path), ], threaded=True, ) self.plugin = mock.MagicMock() plugin = get_plugin_by_test_name(request.param) self.flags.plugins = { b'HttpProtocolHandlerPlugin': [HttpProxyPlugin], b'HttpProxyBasePlugin': [plugin], } self._conn = self.mock_fromfd.return_value self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=self.flags, ) self.protocol_handler.initialize()
async def test_proxy_authentication_failed(self) -> None: self._conn = self.mock_fromfd.return_value mock_selector_for_client_read(self) flags = FlagParser.initialize( auth_code=base64.b64encode(b'user:pass'), threaded=True, ) flags.plugins = Plugins.load([ bytes_(PLUGIN_HTTP_PROXY), bytes_(PLUGIN_WEB_SERVER), bytes_(PLUGIN_PROXY_AUTH), ]) self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=flags, ) self.protocol_handler.initialize() self._conn.recv.return_value = CRLF.join([ b'GET http://abhinavsingh.com HTTP/1.1', b'Host: abhinavsingh.com', CRLF, ]) await self.protocol_handler._run_once() self.assertEqual( self.protocol_handler.work.buffer[0], PROXY_AUTH_FAILED_RESPONSE_PKT, )
def _setUp(self, mocker: MockerFixture) -> None: self.mock_fromfd = mocker.patch('socket.fromfd') self.mock_selector = mocker.patch('selectors.DefaultSelector') self.fileno = 10 self._addr = ('127.0.0.1', 54382) self._conn = self.mock_fromfd.return_value # Setup a static directory self.static_server_dir = os.path.join(tempfile.gettempdir(), 'static') self.index_file_path = os.path.join( self.static_server_dir, 'index.html', ) self.html_file_content = b'''<html><head></head><body><h1>Proxy.py Testing</h1></body></html>''' os.makedirs(self.static_server_dir, exist_ok=True) with open(self.index_file_path, 'wb') as f: f.write(self.html_file_content) # flags = FlagParser.initialize( enable_static_server=True, static_server_dir=self.static_server_dir, threaded=True, ) flags.plugins = Plugins.load([ bytes_(PLUGIN_HTTP_PROXY), bytes_(PLUGIN_WEB_SERVER), ]) self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=flags, ) self.protocol_handler.initialize()
def _setUp(self, request: Any, mocker: MockerFixture) -> None: self.mock_fromfd = mocker.patch('socket.fromfd') self.mock_selector = mocker.patch('selectors.DefaultSelector') self.fileno = 10 self._addr = ('127.0.0.1', 54382) self._conn = self.mock_fromfd.return_value self.pac_file = request.param if isinstance(self.pac_file, str): with open(self.pac_file, 'rb') as f: self.expected_response = f.read() else: self.expected_response = PAC_FILE_CONTENT self.flags = FlagParser.initialize( pac_file=self.pac_file, threaded=True, ) self.flags.plugins = Plugins.load([ bytes_(PLUGIN_HTTP_PROXY), bytes_(PLUGIN_WEB_SERVER), bytes_(PLUGIN_PAC_FILE), ]) self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=self.flags, ) self.protocol_handler.initialize() self._conn.recv.return_value = CRLF.join([ b'GET / HTTP/1.1', CRLF, ]) mock_selector_for_client_read(self)
async def test_authenticated_proxy_http_get(self) -> None: self._conn = self.mock_fromfd.return_value mock_selector_for_client_read(self) server = self.mock_server_connection.return_value server.connect.return_value = True server.buffer_size.return_value = 0 flags = FlagParser.initialize( auth_code=base64.b64encode(b'user:pass'), threaded=True, ) flags.plugins = Plugins.load([ bytes_(PLUGIN_HTTP_PROXY), bytes_(PLUGIN_WEB_SERVER), ]) self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=flags, ) self.protocol_handler.initialize() assert self.http_server_port is not None self._conn.recv.return_value = b'GET http://localhost:%d HTTP/1.1' % self.http_server_port await self.protocol_handler._run_once() self.assertEqual( self.protocol_handler.request.state, httpParserStates.INITIALIZED, ) self._conn.recv.return_value = CRLF await self.protocol_handler._run_once() self.assertEqual( self.protocol_handler.request.state, httpParserStates.LINE_RCVD, ) assert self.http_server_port is not None self._conn.recv.return_value = CRLF.join([ b'User-Agent: proxy.py/%s' % bytes_(__version__), b'Host: localhost:%d' % self.http_server_port, b'Accept: */*', httpHeaders.PROXY_CONNECTION + b': Keep-Alive', httpHeaders.PROXY_AUTHORIZATION + b': Basic dXNlcjpwYXNz', CRLF, ]) await self.assert_data_queued(server)
def _setUp(self, mocker: MockerFixture) -> None: self.mock_fromfd = mocker.patch('socket.fromfd') self.mock_selector = mocker.patch('selectors.DefaultSelector') self.fileno = 10 self._addr = ('127.0.0.1', 54382) self._conn = self.mock_fromfd.return_value self.flags = FlagParser.initialize(threaded=True) self.flags.plugins = Plugins.load([ bytes_(PLUGIN_HTTP_PROXY), bytes_(PLUGIN_WEB_SERVER), ]) self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=self.flags, ) self.protocol_handler.initialize()
def test_on_client_connection_called_on_teardown(mocker: MockerFixture) -> None: plugin = mocker.MagicMock() mock_fromfd = mocker.patch('socket.fromfd') flags = FlagParser.initialize(threaded=True) flags.plugins = {b'HttpProtocolHandlerPlugin': [plugin]} _conn = mock_fromfd.return_value _addr = ('127.0.0.1', 54382) protocol_handler = HttpProtocolHandler( HttpClientConnection(_conn, _addr), flags=flags, ) protocol_handler.initialize() plugin.assert_not_called() mock_run_once = mocker.patch.object(protocol_handler, '_run_once') mock_run_once.return_value = True protocol_handler.run() assert _conn.closed
def _setUp(self, mocker: MockerFixture) -> None: self.mock_fromfd = mocker.patch('socket.fromfd') self.mock_selector = mocker.patch('selectors.DefaultSelector') self.mock_server_conn = mocker.patch( 'proxy.http.proxy.server.TcpServerConnection', ) self.fileno = 10 self._addr = ('127.0.0.1', 54382) self.flags = FlagParser.initialize( ["--basic-auth", "user:pass"], threaded=True, ) self._conn = self.mock_fromfd.return_value self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=self.flags, ) self.protocol_handler.initialize()
def _setUp(self, mocker: MockerFixture) -> None: self.mock_server_conn = mocker.patch( 'proxy.http.proxy.server.TcpServerConnection', ) self.mock_selector = mocker.patch('selectors.DefaultSelector') self.mock_fromfd = mocker.patch('socket.fromfd') self.fileno = 10 self._addr = ('127.0.0.1', 54382) self.flags = FlagParser.initialize(threaded=True) self.plugin = mocker.MagicMock() self.flags.plugins = { b'HttpProtocolHandlerPlugin': [HttpProxyPlugin], b'HttpProxyBasePlugin': [self.plugin], } self._conn = self.mock_fromfd.return_value self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=self.flags, ) self.protocol_handler.initialize()
async def test_authenticated_proxy_http_tunnel(self) -> None: server = self.mock_server_connection.return_value server.connect.return_value = True server.buffer_size.return_value = 0 self._conn = self.mock_fromfd.return_value self.mock_selector_for_client_read_and_server_write(server) flags = FlagParser.initialize( auth_code=base64.b64encode(b'user:pass'), threaded=True, ) flags.plugins = Plugins.load([ bytes_(PLUGIN_HTTP_PROXY), bytes_(PLUGIN_WEB_SERVER), ]) self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=flags, ) self.protocol_handler.initialize() assert self.http_server_port is not None self._conn.recv.return_value = CRLF.join([ b'CONNECT localhost:%d HTTP/1.1' % self.http_server_port, b'Host: localhost:%d' % self.http_server_port, b'User-Agent: proxy.py/%s' % bytes_(__version__), httpHeaders.PROXY_CONNECTION + b': Keep-Alive', httpHeaders.PROXY_AUTHORIZATION + b': Basic dXNlcjpwYXNz', CRLF, ]) await self.assert_tunnel_response(server) self.protocol_handler.work.flush() await self.assert_data_queued_to_server(server) await self.protocol_handler._run_once() server.flush.assert_called_once()
async def test_default_web_server_returns_404(self) -> None: self._conn = self.mock_fromfd.return_value self.mock_selector.return_value.select.return_value = [ ( selectors.SelectorKey( fileobj=self._conn.fileno(), fd=self._conn.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, ), ] flags = FlagParser.initialize(threaded=True) flags.plugins = Plugins.load([ bytes_(PLUGIN_HTTP_PROXY), bytes_(PLUGIN_WEB_SERVER), ]) self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=flags, ) self.protocol_handler.initialize() self._conn.recv.return_value = CRLF.join([ b'GET /hello HTTP/1.1', CRLF, ]) await self.protocol_handler._run_once() self.assertEqual( self.protocol_handler.request.state, httpParserStates.COMPLETE, ) self.assertEqual( self.protocol_handler.work.buffer[0], NOT_FOUND_RESPONSE_PKT, )
async def test_e2e(self, mocker: MockerFixture) -> None: host, port = uuid.uuid4().hex, 443 netloc = '{0}:{1}'.format(host, port) self.mock_fromfd = mocker.patch('socket.fromfd') self.mock_selector = mocker.patch('selectors.DefaultSelector') self.mock_sign_csr = mocker.patch('proxy.http.proxy.server.sign_csr') self.mock_gen_csr = mocker.patch('proxy.http.proxy.server.gen_csr') self.mock_gen_public_key = mocker.patch( 'proxy.http.proxy.server.gen_public_key', ) self.mock_server_conn = mocker.patch( 'proxy.http.proxy.server.TcpServerConnection', ) self.mock_ssl_context = mocker.patch('ssl.create_default_context') self.mock_ssl_wrap = mocker.patch('ssl.wrap_socket') self.mock_sign_csr.return_value = True self.mock_gen_csr.return_value = True self.mock_gen_public_key.return_value = True ssl_connection = mock.MagicMock(spec=ssl.SSLSocket) self.mock_ssl_context.return_value.wrap_socket.return_value = ssl_connection self.mock_ssl_wrap.return_value = mock.MagicMock(spec=ssl.SSLSocket) plain_connection = mock.MagicMock(spec=socket.socket) def mock_connection() -> Any: if self.mock_ssl_context.return_value.wrap_socket.called: return ssl_connection return plain_connection # Do not mock the original wrap method self.mock_server_conn.return_value.wrap.side_effect = \ lambda x, y: TcpServerConnection.wrap( self.mock_server_conn.return_value, x, y, ) type(self.mock_server_conn.return_value).connection = \ mock.PropertyMock(side_effect=mock_connection) self.fileno = 10 self._addr = ('127.0.0.1', 54382) self.flags = FlagParser.initialize( ca_cert_file='ca-cert.pem', ca_key_file='ca-key.pem', ca_signing_key_file='ca-signing-key.pem', threaded=True, ) self.plugin = mock.MagicMock() self.proxy_plugin = mock.MagicMock() self.flags.plugins = { b'HttpProtocolHandlerPlugin': [self.plugin, HttpProxyPlugin], b'HttpProxyBasePlugin': [self.proxy_plugin], } self._conn = self.mock_fromfd.return_value self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=self.flags, ) self.protocol_handler.initialize() self.plugin.assert_not_called() self.proxy_plugin.assert_not_called() connect_request = build_http_request( httpMethods.CONNECT, bytes_(netloc), headers={ b'Host': bytes_(netloc), }, ) self._conn.recv.return_value = connect_request async def asyncReturnBool(val: bool) -> bool: return val # Prepare mocked HttpProtocolHandlerPlugin # self.plugin.return_value.get_descriptors.return_value = ([], []) # self.plugin.return_value.write_to_descriptors.return_value = asyncReturnBool(False) # self.plugin.return_value.read_from_descriptors.return_value = asyncReturnBool(False) # self.plugin.return_value.on_client_data.side_effect = lambda raw: raw # self.plugin.return_value.on_request_complete.return_value = False # self.plugin.return_value.on_response_chunk.side_effect = lambda chunk: chunk # self.plugin.return_value.on_client_connection_close.return_value = None # Prepare mocked HttpProxyBasePlugin self.proxy_plugin.return_value.write_to_descriptors.return_value = \ asyncReturnBool(False) self.proxy_plugin.return_value.read_from_descriptors.return_value = \ asyncReturnBool(False) self.proxy_plugin.return_value.before_upstream_connection.side_effect = lambda r: r self.proxy_plugin.return_value.handle_client_request.side_effect = lambda r: r self.proxy_plugin.return_value.resolve_dns.return_value = None, None self.mock_selector.return_value.select.side_effect = [ [( selectors.SelectorKey( fileobj=self._conn.fileno(), fd=self._conn.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], ] await self.protocol_handler._run_once() # Assert correct plugin was initialized self.plugin.assert_not_called() self.proxy_plugin.assert_called_once() self.assertEqual(self.proxy_plugin.call_args[0][1], self.flags) # Actual call arg must be `_conn` object # but because internally the reference is updated # we assert it against `mock_ssl_wrap` which is # called during proxy plugin initialization # for interception self.assertEqual( self.proxy_plugin.call_args[0][2].connection, self.mock_ssl_wrap.return_value, ) # Assert our mocked plugins invocations # self.plugin.return_value.get_descriptors.assert_called() # self.plugin.return_value.write_to_descriptors.assert_called_with([]) # # on_client_data is only called after initial request has completed # self.plugin.return_value.on_client_data.assert_not_called() # self.plugin.return_value.on_request_complete.assert_called() # self.plugin.return_value.read_from_descriptors.assert_called_with([ # self._conn.fileno(), # ]) self.proxy_plugin.return_value.before_upstream_connection.assert_called( ) self.proxy_plugin.return_value.handle_client_request.assert_called() self.mock_server_conn.assert_called_with(host, port) self.mock_server_conn.return_value.connection.setblocking.assert_called_with( False, ) self.mock_ssl_context.assert_called_with( ssl.Purpose.SERVER_AUTH, cafile=str(DEFAULT_CA_FILE), ) # self.assertEqual(self.mock_ssl_context.return_value.options, # ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | # ssl.OP_NO_TLSv1_1) self.assertEqual(plain_connection.setblocking.call_count, 2) self.mock_ssl_context.return_value.wrap_socket.assert_called_with( plain_connection, server_hostname=host, ) self.assertEqual(self.mock_sign_csr.call_count, 1) self.assertEqual(self.mock_gen_csr.call_count, 1) self.assertEqual(self.mock_gen_public_key.call_count, 1) self.assertEqual(ssl_connection.setblocking.call_count, 1) self.assertEqual( self.mock_server_conn.return_value._conn, ssl_connection, ) self._conn.send.assert_called_with( PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT, ) assert self.flags.ca_cert_dir is not None self.mock_ssl_wrap.assert_called_with( self._conn, server_side=True, keyfile=self.flags.ca_signing_key_file, certfile=HttpProxyPlugin.generated_cert_file_path( self.flags.ca_cert_dir, host, ), ssl_version=ssl.PROTOCOL_TLS, ) self.assertEqual(self._conn.setblocking.call_count, 2) self.assertEqual( self.protocol_handler.work.connection, self.mock_ssl_wrap.return_value, ) # Assert connection references for all other plugins is updated # self.assertEqual( # self.plugin.return_value.client._conn, # self.mock_ssl_wrap.return_value, # ) self.assertEqual( self.proxy_plugin.return_value.client._conn, self.mock_ssl_wrap.return_value, )
async def test_e2e(self, mocker: MockerFixture) -> None: host, port = uuid.uuid4().hex, 443 netloc = '{0}:{1}'.format(host, port) self.mock_fromfd = mocker.patch('socket.fromfd') self.mock_selector = mocker.patch('selectors.DefaultSelector') self.mock_sign_csr = mocker.patch('proxy.http.proxy.server.sign_csr') self.mock_gen_csr = mocker.patch('proxy.http.proxy.server.gen_csr') self.mock_gen_public_key = mocker.patch( 'proxy.http.proxy.server.gen_public_key', ) self.mock_server_conn = mocker.patch( 'proxy.http.proxy.server.TcpServerConnection', ) self.mock_sign_csr.return_value = True self.mock_gen_csr.return_value = True self.mock_gen_public_key.return_value = True # Used for server side wrapping self.mock_ssl_context = mocker.patch('ssl.create_default_context') upstream_tls_sock = mock.MagicMock(spec=ssl.SSLSocket) self.mock_ssl_context.return_value.wrap_socket.return_value = upstream_tls_sock # Used for client wrapping self.mock_ssl_wrap = mocker.patch('ssl.wrap_socket') client_tls_sock = mock.MagicMock(spec=ssl.SSLSocket) self.mock_ssl_wrap.return_value = client_tls_sock plain_connection = mock.MagicMock(spec=socket.socket) def mock_connection() -> Any: if self.mock_ssl_context.return_value.wrap_socket.called: return upstream_tls_sock return plain_connection # Do not mock the original wrap method self.mock_server_conn.return_value.wrap.side_effect = \ lambda x, y, as_non_blocking: TcpServerConnection.wrap( self.mock_server_conn.return_value, x, y, as_non_blocking=as_non_blocking, ) type(self.mock_server_conn.return_value).connection = \ mock.PropertyMock(side_effect=mock_connection) type(self.mock_server_conn.return_value).closed = \ mock.PropertyMock(return_value=False) self.fileno = 10 self._addr = ('127.0.0.1', 54382) self.flags = FlagParser.initialize( ca_cert_file='ca-cert.pem', ca_key_file='ca-key.pem', ca_signing_key_file='ca-signing-key.pem', threaded=True, ) self.assertTrue(tls_interception_enabled(self.flags)) # In this test we enable a mock http protocol handler plugin # and a mock http proxy plugin. Internally, http protocol # handler will only initialize proxy plugin as we'll never # make any other request. self.plugin = mock.MagicMock() self.proxy_plugin = mock.MagicMock() self.flags.plugins = { b'HttpProtocolHandlerPlugin': [self.plugin, HttpProxyPlugin], b'HttpProxyBasePlugin': [self.proxy_plugin], } self._conn = self.mock_fromfd.return_value self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=self.flags, ) self.protocol_handler.initialize() self.plugin.assert_not_called() self.proxy_plugin.assert_not_called() # Mock a CONNECT request followed by a GET request # from client connection headers = { b'Host': bytes_(netloc), } connect_request = build_http_request( httpMethods.CONNECT, bytes_(netloc), headers=headers, no_ua=True, ) self._conn.recv.return_value = connect_request get_request = build_http_request( httpMethods.GET, b'/', headers=headers, ) client_tls_sock.recv.return_value = get_request T = TypeVar('T') # noqa: N806 async def asyncReturn(val: T) -> T: return val # Prepare mocked HttpProxyBasePlugin # 1. Mock descriptor mixin methods # # NOTE: We need multiple async result otherwise # we will end up with cannot await on already # awaited coroutine. self.proxy_plugin.return_value.get_descriptors.side_effect = \ [asyncReturn(([], [])), asyncReturn(([], []))] self.proxy_plugin.return_value.write_to_descriptors.side_effect = \ [asyncReturn(False), asyncReturn(False)] self.proxy_plugin.return_value.read_from_descriptors.side_effect = \ [asyncReturn(False), asyncReturn(False)] # 2. Mock plugin lifecycle methods self.proxy_plugin.return_value.resolve_dns.return_value = None, None self.proxy_plugin.return_value.before_upstream_connection.side_effect = lambda r: r self.proxy_plugin.return_value.handle_client_data.side_effect = lambda r: r self.proxy_plugin.return_value.handle_client_request.side_effect = lambda r: r self.proxy_plugin.return_value.handle_upstream_chunk.side_effect = lambda r: r self.proxy_plugin.return_value.on_upstream_connection_close.return_value = None self.proxy_plugin.return_value.on_access_log.side_effect = lambda r: r self.proxy_plugin.return_value.do_intercept.return_value = True self.mock_selector.return_value.select.side_effect = [ # Trigger read on plain socket [( selectors.SelectorKey( fileobj=self._conn.fileno(), fd=self._conn.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], # Trigger read on encrypted socket [( selectors.SelectorKey( fileobj=client_tls_sock.fileno(), fd=client_tls_sock.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], ] await self.protocol_handler._run_once() # Assert correct plugin was initialized self.plugin.assert_not_called() self.proxy_plugin.assert_called_once() self.assertEqual(self.proxy_plugin.call_args[0][1], self.flags) # Actual call arg must be `_conn` object # but because internally the reference is updated # we assert it against `mock_ssl_wrap` which is # called during proxy plugin initialization # for interception self.assertEqual( self.proxy_plugin.call_args[0][2].connection, client_tls_sock, ) # Invoked lifecycle callbacks self.proxy_plugin.return_value.resolve_dns.assert_called_once_with( host, port, ) self.proxy_plugin.return_value.before_upstream_connection.assert_called( ) self.proxy_plugin.return_value.handle_client_request.assert_called_once( ) self.proxy_plugin.return_value.do_intercept.assert_called_once() # All the invoked lifecycle callbacks will receive the CONNECT request # packet with / as the path callback_request: HttpParser = \ self.proxy_plugin.return_value.before_upstream_connection.call_args_list[0][0][0] callback_request1: HttpParser = \ self.proxy_plugin.return_value.handle_client_request.call_args_list[0][0][0] callback_request2: HttpParser = \ self.proxy_plugin.return_value.do_intercept.call_args_list[0][0][0] self.assertEqual(callback_request.host, bytes_(host)) self.assertEqual(callback_request.port, 443) self.assertEqual(callback_request.header(b'Host'), headers[b'Host']) assert callback_request._url self.assertEqual(callback_request._url.remainder, None) self.assertEqual(callback_request.method, httpMethods.CONNECT) self.assertEqual(callback_request.is_https_tunnel, True) self.assertEqual(callback_request.build(), callback_request1.build()) self.assertEqual(callback_request.build(), callback_request2.build()) # Lifecycle callbacks not invoked self.proxy_plugin.return_value.handle_client_data.assert_not_called() self.proxy_plugin.return_value.handle_upstream_chunk.assert_not_called( ) self.proxy_plugin.return_value.on_upstream_connection_close.assert_not_called( ) self.proxy_plugin.return_value.on_access_log.assert_not_called() self.mock_server_conn.assert_called_with(host, port) self.mock_server_conn.return_value.connection.setblocking.assert_called_with( False, ) self.mock_ssl_context.assert_called_with( ssl.Purpose.SERVER_AUTH, cafile=str(DEFAULT_CA_FILE), ) self.assertEqual(plain_connection.setblocking.call_count, 2) self.mock_ssl_context.return_value.wrap_socket.assert_called_with( plain_connection, server_hostname=host, ) self.assertEqual(self.mock_sign_csr.call_count, 1) self.assertEqual(self.mock_gen_csr.call_count, 1) self.assertEqual(self.mock_gen_public_key.call_count, 1) self.assertEqual(upstream_tls_sock.setblocking.call_count, 1) self.assertEqual( self.mock_server_conn.return_value._conn, upstream_tls_sock, ) self._conn.send.assert_called_with( PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT, ) assert self.flags.ca_cert_dir is not None self.mock_ssl_wrap.assert_called_with( self._conn, server_side=True, keyfile=self.flags.ca_signing_key_file, certfile=HttpProxyPlugin.generated_cert_file_path( self.flags.ca_cert_dir, host, ), ssl_version=ssl.PROTOCOL_TLS, ) self.assertEqual(self._conn.setblocking.call_count, 2) self.assertEqual( self.protocol_handler.work.connection, client_tls_sock, ) # Assert connection references for all other plugins is updated self.assertEqual( self.proxy_plugin.return_value.client._conn, client_tls_sock, ) # Now process the GET request await self.protocol_handler._run_once() self.plugin.assert_not_called() self.proxy_plugin.assert_called_once() # Lifecycle callbacks still not invoked self.proxy_plugin.return_value.handle_client_data.assert_not_called() self.proxy_plugin.return_value.handle_upstream_chunk.assert_not_called( ) self.proxy_plugin.return_value.on_upstream_connection_close.assert_not_called( ) self.proxy_plugin.return_value.on_access_log.assert_not_called() # Only handle client request lifecycle must be called again self.proxy_plugin.return_value.resolve_dns.assert_called_once_with( host, port, ) self.proxy_plugin.return_value.before_upstream_connection.assert_called( ) self.assertEqual( self.proxy_plugin.return_value.handle_client_request.call_count, 2, ) self.proxy_plugin.return_value.do_intercept.assert_called_once() callback_request = \ self.proxy_plugin.return_value.handle_client_request.call_args_list[1][0][0] self.assertEqual(callback_request.host, None) self.assertEqual(callback_request.port, 80) self.assertEqual(callback_request.header(b'Host'), headers[b'Host']) assert callback_request._url self.assertEqual(callback_request._url.remainder, b'/') self.assertEqual(callback_request.method, httpMethods.GET) self.assertEqual(callback_request.is_https_tunnel, False)
def _setUp(self, request: Any, mocker: MockerFixture) -> None: self.mock_fromfd = mocker.patch('socket.fromfd') self.mock_selector = mocker.patch('selectors.DefaultSelector') self.mock_sign_csr = mocker.patch('proxy.http.proxy.server.sign_csr') self.mock_gen_csr = mocker.patch('proxy.http.proxy.server.gen_csr') self.mock_gen_public_key = mocker.patch( 'proxy.http.proxy.server.gen_public_key', ) self.mock_server_conn = mocker.patch( 'proxy.http.proxy.server.TcpServerConnection', ) self.mock_ssl_context = mocker.patch('ssl.create_default_context') self.mock_ssl_wrap = mocker.patch('ssl.wrap_socket') self.mock_sign_csr.return_value = True self.mock_gen_csr.return_value = True self.mock_gen_public_key.return_value = True self.fileno = 10 self._addr = ('127.0.0.1', 54382) self.flags = FlagParser.initialize( ca_cert_file='ca-cert.pem', ca_key_file='ca-key.pem', ca_signing_key_file='ca-signing-key.pem', threaded=True, ) self.plugin = mocker.MagicMock() plugin = get_plugin_by_test_name(request.param) self.flags.plugins = { b'HttpProtocolHandlerPlugin': [HttpProxyPlugin], b'HttpProxyBasePlugin': [plugin], } self._conn = mocker.MagicMock(spec=socket.socket) self.mock_fromfd.return_value = self._conn self.protocol_handler = HttpProtocolHandler( HttpClientConnection(self._conn, self._addr), flags=self.flags, ) self.protocol_handler.initialize() self.server = self.mock_server_conn.return_value self.server_ssl_connection = mocker.MagicMock(spec=ssl.SSLSocket) self.mock_ssl_context.return_value.wrap_socket.return_value = self.server_ssl_connection self.client_ssl_connection = mocker.MagicMock(spec=ssl.SSLSocket) self.mock_ssl_wrap.return_value = self.client_ssl_connection def has_buffer() -> bool: return cast(bool, self.server.queue.called) def closed() -> bool: return not self.server.connect.called def mock_connection() -> Any: if self.mock_ssl_context.return_value.wrap_socket.called: return self.server_ssl_connection return self._conn # Do not mock the original wrap method self.server.wrap.side_effect = \ lambda x, y, as_non_blocking: TcpServerConnection.wrap( self.server, x, y, as_non_blocking=as_non_blocking, ) self.server.has_buffer.side_effect = has_buffer type(self.server).closed = mocker.PropertyMock(side_effect=closed) type(self.server, ).connection = mocker.PropertyMock( side_effect=mock_connection, ) self.mock_selector.return_value.select.side_effect = [ [( selectors.SelectorKey( fileobj=self._conn.fileno(), fd=self._conn.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], [( selectors.SelectorKey( fileobj=self.client_ssl_connection.fileno(), fd=self.client_ssl_connection.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], [( selectors.SelectorKey( fileobj=self.server_ssl_connection.fileno(), fd=self.server_ssl_connection.fileno(), events=selectors.EVENT_WRITE, data=None, ), selectors.EVENT_WRITE, )], [( selectors.SelectorKey( fileobj=self.server_ssl_connection.fileno(), fd=self.server_ssl_connection.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], ] # Connect def send(raw: bytes) -> int: return len(raw) self._conn.send.side_effect = send self._conn.recv.return_value = build_http_request( httpMethods.CONNECT, b'uni.corn:443', no_ua=True, )