Separate basic auth plugin outside of core server (#453)

* Separate basic auth plugin outside of core

* Put basic auth plugin at top
This commit is contained in:
Abhinav Singh 2020-10-14 20:00:29 +05:30 committed by GitHub
parent 0744cd8e7f
commit 137ce457bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 88 additions and 47 deletions

View File

@ -110,7 +110,7 @@ lib-coverage:
open htmlcov/index.html
lib-profile:
sudo py-spy -F -f profile.svg -d 3600 proxy.py
sudo py-spy record -o profile.svg -t -F -s -- python -m proxy
dashboard:
pushd dashboard && npm run build && popd

View File

@ -86,6 +86,7 @@ PLUGIN_PAC_FILE = 'proxy.http.server.HttpWebServerPacFilePlugin'
PLUGIN_DEVTOOLS_PROTOCOL = 'proxy.http.inspector.DevtoolsProtocolPlugin'
PLUGIN_DASHBOARD = 'proxy.dashboard.dashboard.ProxyDashboard'
PLUGIN_INSPECT_TRAFFIC = 'proxy.dashboard.inspect_traffic.InspectTrafficPlugin'
PLUGIN_PROXY_AUTH = 'proxy.http.proxy.AuthPlugin'
PY2_DEPRECATION_MESSAGE = '''DEPRECATION: proxy.py no longer supports Python 2.7. Kindly upgrade to Python 3+. '
'If for some reasons you cannot upgrade, use'

View File

@ -88,5 +88,6 @@ class TcpConnection(ABC):
self.buffer.pop(0)
else:
self.buffer[0] = memoryview(mv[sent:])
del mv
logger.debug('flushed %d bytes to %s' % (sent, self.tag))
return sent

View File

@ -10,8 +10,10 @@
"""
from .plugin import HttpProxyBasePlugin
from .server import HttpProxyPlugin
from .auth import AuthPlugin
__all__ = [
'HttpProxyBasePlugin',
'HttpProxyPlugin',
'AuthPlugin',
]

50
proxy/http/proxy/auth.py Normal file
View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
"""
proxy.py
~~~~~~~~
Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
Network monitoring, controls & Application development, testing, debugging.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
"""
from typing import Optional
from ..exception import ProxyAuthenticationFailed
from ...common.flag import flags
from ...common.constants import DEFAULT_BASIC_AUTH
from ...http.parser import HttpParser
from ...http.proxy import HttpProxyBasePlugin
flags.add_argument(
'--basic-auth',
type=str,
default=DEFAULT_BASIC_AUTH,
help='Default: No authentication. Specify colon separated user:password '
'to enable basic authentication.')
class AuthPlugin(HttpProxyBasePlugin):
"""Performs proxy authentication."""
def before_upstream_connection(
self, request: HttpParser) -> Optional[HttpParser]:
if self.flags.auth_code:
if b'proxy-authorization' not in request.headers:
raise ProxyAuthenticationFailed()
parts = request.headers[b'proxy-authorization'][1].split()
if len(parts) != 2 and parts[0].lower(
) != b'basic' and parts[1] != self.flags.auth_code:
raise ProxyAuthenticationFailed()
return request
def handle_client_request(
self, request: HttpParser) -> Optional[HttpParser]:
return request
def handle_upstream_chunk(self, chunk: memoryview) -> memoryview:
return chunk
def on_upstream_connection_close(self) -> None:
pass

View File

@ -20,7 +20,7 @@ from typing import Optional, List, Union, Dict, cast, Any, Tuple
from .plugin import HttpProxyBasePlugin
from ..plugin import HttpProtocolHandlerPlugin
from ..exception import HttpProtocolException, ProxyConnectionFailed, ProxyAuthenticationFailed
from ..exception import HttpProtocolException, ProxyConnectionFailed
from ..codes import httpStatusCodes
from ..parser import HttpParser, httpParserStates, httpParserTypes
from ..methods import httpMethods
@ -105,8 +105,7 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
reason=b'Connection established'
))
# Used to synchronize with other HttpProxyPlugin instances while
# generating certificates
# Used to synchronization during certificate generation.
lock = threading.Lock()
def __init__(
@ -322,8 +321,6 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
self.emit_request_complete()
self.authenticate()
# Note: can raise HttpRequestRejected exception
# Invoke plugin.before_upstream_connection
do_connect = True
@ -533,15 +530,6 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
logger.debug(
'TLS interception using %s', generated_cert)
def authenticate(self) -> None:
if self.flags.auth_code:
if b'proxy-authorization' not in self.request.headers:
raise ProxyAuthenticationFailed()
parts = self.request.headers[b'proxy-authorization'][1].split()
if len(parts) != 2 and parts[0].lower(
) != b'basic' and parts[1] != self.flags.auth_code:
raise ProxyAuthenticationFailed()
def connect_upstream(self) -> None:
host, port = self.request.host, self.request.port
if host and port:

View File

@ -32,7 +32,7 @@ from .common.version import __version__
from .core.acceptor import AcceptorPool
from .http.handler import HttpProtocolHandler
from .common.flag import flags
from .common.constants import COMMA, DEFAULT_BASIC_AUTH, DEFAULT_DATA_DIRECTORY_PATH
from .common.constants import COMMA, DEFAULT_DATA_DIRECTORY_PATH, PLUGIN_PROXY_AUTH
from .common.constants import DEFAULT_DEVTOOLS_WS_PATH, DEFAULT_DISABLE_HEADERS
from .common.constants import DEFAULT_DISABLE_HTTP_PROXY, DEFAULT_NUM_WORKERS
from .common.constants import DEFAULT_ENABLE_DASHBOARD, DEFAULT_ENABLE_DEVTOOLS
@ -54,12 +54,6 @@ flags.add_argument(
type=str,
default=DEFAULT_PID_FILE,
help='Default: None. Save parent process ID to a file.')
flags.add_argument(
'--basic-auth',
type=str,
default=DEFAULT_BASIC_AUTH,
help='Default: No authentication. Specify colon separated user:password '
'to enable basic authentication.')
flags.add_argument(
'--version',
'-v',
@ -360,9 +354,11 @@ class Proxy:
@staticmethod
def get_default_plugins(
args: argparse.Namespace) -> List[Tuple[str, bool]]:
# Prepare list of plugins to load based upon --enable-* and --disable-*
# flags
# Prepare list of plugins to load based upon
# --enable-*, --disable-* and --basic-auth flags.
default_plugins: List[Tuple[str, bool]] = []
if args.basic_auth is not None:
default_plugins.append((PLUGIN_PROXY_AUTH, True))
if args.enable_dashboard:
default_plugins.append((PLUGIN_WEB_SERVER, True))
args.enable_static_server = True

View File

@ -8,6 +8,8 @@
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
"""
from proxy.common.utils import bytes_
from proxy.common.constants import PLUGIN_HTTP_PROXY
import unittest
from typing import List, Dict
@ -86,7 +88,7 @@ class TestFlags(unittest.TestCase):
def test_unique_plugin_from_bytes(self) -> None:
self.flags = Proxy.initialize([], plugins=[
b'proxy.http.proxy.HttpProxyPlugin',
bytes_(PLUGIN_HTTP_PROXY),
])
self.assert_plugins({'HttpProtocolHandlerPlugin': [
HttpProxyPlugin,
@ -94,7 +96,7 @@ class TestFlags(unittest.TestCase):
def test_unique_plugin_from_args(self) -> None:
self.flags = Proxy.initialize([
'--plugins', 'proxy.http.proxy.HttpProxyPlugin',
'--plugins', PLUGIN_HTTP_PROXY,
])
self.assert_plugins({'HttpProtocolHandlerPlugin': [
HttpProxyPlugin,

View File

@ -18,7 +18,7 @@ from unittest import mock
from proxy.proxy import Proxy
from proxy.common.version import __version__
from proxy.common.utils import bytes_
from proxy.common.constants import CRLF
from proxy.common.constants import CRLF, PLUGIN_HTTP_PROXY, PLUGIN_PROXY_AUTH, PLUGIN_WEB_SERVER
from proxy.core.connection import TcpClientConnection
from proxy.http.parser import HttpParser
from proxy.http.proxy import HttpProxyPlugin
@ -41,8 +41,8 @@ class TestHttpProtocolHandler(unittest.TestCase):
self.http_server_port = 65535
self.flags = Proxy.initialize()
self.flags.plugins = Proxy.load_plugins([
b'proxy.http.proxy.HttpProxyPlugin',
b'proxy.http.server.HttpWebServerPlugin',
bytes_(PLUGIN_HTTP_PROXY),
bytes_(PLUGIN_WEB_SERVER),
])
self.mock_selector = mock_selector
@ -175,8 +175,9 @@ class TestHttpProtocolHandler(unittest.TestCase):
flags = Proxy.initialize(
auth_code=base64.b64encode(b'user:pass'))
flags.plugins = Proxy.load_plugins([
b'proxy.http.proxy.HttpProxyPlugin',
b'proxy.http.server.HttpWebServerPlugin',
bytes_(PLUGIN_HTTP_PROXY),
bytes_(PLUGIN_WEB_SERVER),
bytes_(PLUGIN_PROXY_AUTH),
])
self.protocol_handler = HttpProtocolHandler(
TcpClientConnection(self._conn, self._addr), flags=flags)
@ -208,8 +209,8 @@ class TestHttpProtocolHandler(unittest.TestCase):
flags = Proxy.initialize(
auth_code=base64.b64encode(b'user:pass'))
flags.plugins = Proxy.load_plugins([
b'proxy.http.proxy.HttpProxyPlugin',
b'proxy.http.server.HttpWebServerPlugin',
bytes_(PLUGIN_HTTP_PROXY),
bytes_(PLUGIN_WEB_SERVER),
])
self.protocol_handler = HttpProtocolHandler(
@ -257,8 +258,8 @@ class TestHttpProtocolHandler(unittest.TestCase):
flags = Proxy.initialize(
auth_code=base64.b64encode(b'user:pass'))
flags.plugins = Proxy.load_plugins([
b'proxy.http.proxy.HttpProxyPlugin',
b'proxy.http.server.HttpWebServerPlugin'
bytes_(PLUGIN_HTTP_PROXY),
bytes_(PLUGIN_WEB_SERVER)
])
self.protocol_handler = HttpProtocolHandler(

View File

@ -20,7 +20,7 @@ from proxy.core.connection import TcpClientConnection
from proxy.http.handler import HttpProtocolHandler
from proxy.http.parser import httpParserStates
from proxy.common.utils import build_http_response, build_http_request, bytes_, text_
from proxy.common.constants import CRLF, PROXY_PY_DIR
from proxy.common.constants import CRLF, PLUGIN_HTTP_PROXY, PLUGIN_PAC_FILE, PLUGIN_WEB_SERVER, PROXY_PY_DIR
from proxy.http.server import HttpWebServerPlugin
@ -35,8 +35,8 @@ class TestWebServerPlugin(unittest.TestCase):
self.mock_selector = mock_selector
self.flags = Proxy.initialize()
self.flags.plugins = Proxy.load_plugins([
b'proxy.http.proxy.HttpProxyPlugin',
b'proxy.http.server.HttpWebServerPlugin',
bytes_(PLUGIN_HTTP_PROXY),
bytes_(PLUGIN_WEB_SERVER),
])
self.protocol_handler = HttpProtocolHandler(
TcpClientConnection(self._conn, self._addr),
@ -98,8 +98,8 @@ class TestWebServerPlugin(unittest.TestCase):
data=None), selectors.EVENT_READ), ]
flags = Proxy.initialize()
flags.plugins = Proxy.load_plugins([
b'proxy.http.proxy.HttpProxyPlugin',
b'proxy.http.server.HttpWebServerPlugin',
bytes_(PLUGIN_HTTP_PROXY),
bytes_(PLUGIN_WEB_SERVER),
])
self.protocol_handler = HttpProtocolHandler(
TcpClientConnection(self._conn, self._addr),
@ -151,8 +151,8 @@ class TestWebServerPlugin(unittest.TestCase):
enable_static_server=True,
static_server_dir=static_server_dir)
flags.plugins = Proxy.load_plugins([
b'proxy.http.proxy.HttpProxyPlugin',
b'proxy.http.server.HttpWebServerPlugin',
bytes_(PLUGIN_HTTP_PROXY),
bytes_(PLUGIN_WEB_SERVER),
])
self.protocol_handler = HttpProtocolHandler(
@ -201,8 +201,8 @@ class TestWebServerPlugin(unittest.TestCase):
flags = Proxy.initialize(enable_static_server=True)
flags.plugins = Proxy.load_plugins([
b'proxy.http.proxy.HttpProxyPlugin',
b'proxy.http.server.HttpWebServerPlugin',
bytes_(PLUGIN_HTTP_PROXY),
bytes_(PLUGIN_WEB_SERVER),
])
self.protocol_handler = HttpProtocolHandler(
@ -239,9 +239,9 @@ class TestWebServerPlugin(unittest.TestCase):
def init_and_make_pac_file_request(self, pac_file: str) -> None:
flags = Proxy.initialize(pac_file=pac_file)
flags.plugins = Proxy.load_plugins([
b'proxy.http.proxy.HttpProxyPlugin',
b'proxy.http.server.HttpWebServerPlugin',
b'proxy.http.server.HttpWebServerPacFilePlugin',
bytes_(PLUGIN_HTTP_PROXY),
bytes_(PLUGIN_WEB_SERVER),
bytes_(PLUGIN_PAC_FILE),
])
self.protocol_handler = HttpProtocolHandler(
TcpClientConnection(self._conn, self._addr),