mitmproxy/netlib/http/authentication.py

178 lines
4.5 KiB
Python
Raw Normal View History

2014-08-16 13:53:07 +00:00
from __future__ import (absolute_import, print_function, division)
import argparse
import binascii
2015-07-14 21:02:14 +00:00
2013-03-02 21:37:28 +00:00
def parse_http_basic_auth(s):
words = s.split()
if len(words) != 2:
return None
scheme = words[0]
try:
user = binascii.a2b_base64(words[1]).decode("utf8", "replace")
except binascii.Error:
return None
parts = user.split(':')
if len(parts) != 2:
return None
return scheme, parts[0], parts[1]
def assemble_http_basic_auth(scheme, username, password):
v = binascii.b2a_base64((username + ":" + password).encode("utf8")).decode("ascii")
return scheme + " " + v
2013-03-02 21:37:28 +00:00
2015-04-09 00:09:33 +00:00
class NullProxyAuth(object):
2013-03-02 21:37:28 +00:00
"""
No proxy auth at all (returns empty challange headers)
"""
2013-03-02 21:37:28 +00:00
def __init__(self, password_manager):
self.password_manager = password_manager
2015-06-18 13:32:52 +00:00
def clean(self, headers_):
2013-03-02 21:37:28 +00:00
"""
Clean up authentication headers, so they're not passed upstream.
"""
2015-06-18 13:32:52 +00:00
def authenticate(self, headers_):
2013-03-02 21:37:28 +00:00
"""
Tests that the user is allowed to use the proxy
"""
return True
def auth_challenge_headers(self):
"""
Returns a dictionary containing the headers require to challenge the user
"""
return {}
class BasicAuth(NullProxyAuth):
CHALLENGE_HEADER = None
AUTH_HEADER = None
2013-12-08 00:37:45 +00:00
2013-03-02 21:37:28 +00:00
def __init__(self, password_manager, realm):
NullProxyAuth.__init__(self, password_manager)
self.realm = realm
def clean(self, headers):
del headers[self.AUTH_HEADER]
def authenticate(self, headers):
2015-09-05 16:15:47 +00:00
auth_value = headers.get(self.AUTH_HEADER)
2013-03-02 21:37:28 +00:00
if not auth_value:
return False
2015-09-05 16:15:47 +00:00
parts = parse_http_basic_auth(auth_value)
2013-03-02 21:37:28 +00:00
if not parts:
return False
scheme, username, password = parts
if scheme.lower() != 'basic':
2013-03-02 21:37:28 +00:00
return False
if not self.password_manager.test(username, password):
return False
self.username = username
return True
def auth_challenge_headers(self):
return {self.CHALLENGE_HEADER: 'Basic realm="%s"' % self.realm}
2013-03-02 21:37:28 +00:00
class BasicWebsiteAuth(BasicAuth):
CHALLENGE_HEADER = 'WWW-Authenticate'
AUTH_HEADER = 'Authorization'
class BasicProxyAuth(BasicAuth):
CHALLENGE_HEADER = 'Proxy-Authenticate'
AUTH_HEADER = 'Proxy-Authorization'
2015-04-09 00:09:33 +00:00
class PassMan(object):
2015-06-18 13:32:52 +00:00
def test(self, username_, password_token_):
2013-03-02 21:37:28 +00:00
return False
2015-04-09 00:09:33 +00:00
class PassManNonAnon(PassMan):
2013-03-02 21:37:28 +00:00
"""
Ensure the user specifies a username, accept any password.
"""
2015-06-18 13:32:52 +00:00
def test(self, username, password_token_):
2013-03-02 21:37:28 +00:00
if username:
return True
return False
2015-04-09 00:09:33 +00:00
class PassManHtpasswd(PassMan):
2013-03-02 21:37:28 +00:00
"""
Read usernames and passwords from an htpasswd file
"""
2014-08-16 13:28:09 +00:00
def __init__(self, path):
2013-03-02 21:37:28 +00:00
"""
Raises ValueError if htpasswd file is invalid.
"""
import passlib.apache
self.htpasswd = passlib.apache.HtpasswdFile(path)
2013-03-02 23:16:09 +00:00
2013-03-02 21:37:28 +00:00
def test(self, username, password_token):
2014-08-16 13:28:09 +00:00
return bool(self.htpasswd.check_password(username, password_token))
2013-03-02 21:37:28 +00:00
2015-04-09 00:09:33 +00:00
class PassManSingleUser(PassMan):
2013-03-02 21:37:28 +00:00
def __init__(self, username, password):
self.username, self.password = username, password
def test(self, username, password_token):
return self.username == username and self.password == password_token
class AuthAction(argparse.Action):
"""
Helper class to allow seamless integration int argparse. Example usage:
parser.add_argument(
"--nonanonymous",
action=NonanonymousAuthAction, nargs=0,
help="Allow access to any user long as a credentials are specified."
)
"""
def __call__(self, parser, namespace, values, option_string=None):
passman = self.getPasswordManager(values)
2013-12-08 00:37:45 +00:00
authenticator = BasicProxyAuth(passman, "mitmproxy")
setattr(namespace, self.dest, authenticator)
def getPasswordManager(self, s): # pragma: no cover
raise NotImplementedError()
class SingleuserAuthAction(AuthAction):
def getPasswordManager(self, s):
if len(s.split(':')) != 2:
raise argparse.ArgumentTypeError(
2013-12-08 00:35:42 +00:00
"Invalid single-user specification. Please use the format username:password"
)
username, password = s.split(':')
return PassManSingleUser(username, password)
class NonanonymousAuthAction(AuthAction):
def getPasswordManager(self, s):
return PassManNonAnon()
class HtpasswdAuthAction(AuthAction):
def getPasswordManager(self, s):
2014-08-16 13:28:09 +00:00
return PassManHtpasswd(s)