aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/authentication.py
blob: 500ead6b81c22890382f2561188f8b95153818e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import binascii
import contrib.md5crypt as md5crypt

class NullProxyAuth():
    """
        No proxy auth at all (returns empty challange headers)
    """
    def __init__(self, password_manager):
        self.password_manager = password_manager
        self.username = ""

    def clean(self, headers):
        """
            Clean up authentication headers, so they're not passed upstream.
        """
        pass

    def authenticate(self, headers):
        """
            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 BasicProxyAuth(NullProxyAuth):
    CHALLENGE_HEADER = 'Proxy-Authenticate'
    AUTH_HEADER = 'Proxy-Authorization'
    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):
        auth_value = headers.get(self.AUTH_HEADER, [])
        if not auth_value:
            return False
        try:
            scheme, username, password = self.parse_auth_value(auth_value[0])
        except ValueError:
            return False
        if scheme.lower()!='basic':
            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}

    def unparse_auth_value(self, scheme, username, password):
        v = binascii.b2a_base64(username + ":" + password)
        return scheme + " " + v

    def parse_auth_value(self, auth_value):
        words = auth_value.split()
        if len(words) != 2:
            raise ValueError("Invalid basic auth credential.")
        scheme = words[0]
        try:
            user = binascii.a2b_base64(words[1])
        except binascii.Error:
            raise ValueError("Invalid basic auth credential: user:password pair not valid base64: %s"%words[1])
        parts = user.split(':')
        if len(parts) != 2:
            raise ValueError("Invalid basic auth credential: decoded user:password pair not valid: %s"%user)
        return scheme, parts[0], parts[1]


class PasswordManager():
    def __init__(self):
        pass

    def test(self, username, password_token):
        return False


class PermissivePasswordManager(PasswordManager):
    def __init__(self):
        PasswordManager.__init__(self)

    def test(self, username, password_token):
        if username:
            return True
        return False


class HtpasswdPasswordManager(PasswordManager):
    """
        Read usernames and passwords from a file created by Apache htpasswd
    """
    def __init__(self, filehandle):
        PasswordManager.__init__(self)
        entries = (line.strip().split(':') for line in filehandle)
        valid_entries = (entry for entry in entries if len(entry)==2)
        self.usernames = {username:token for username,token in valid_entries}

    def test(self, username, password_token):
        if username not in self.usernames:
            return False
        full_token = self.usernames[username]
        dummy, magic, salt, hashed_password = full_token.split('$')
        expected = md5crypt.md5crypt(password_token, salt, '$'+magic+'$')
        return expected==full_token


class SingleUserPasswordManager(PasswordManager):
    def __init__(self, username, password):
        PasswordManager.__init__(self)
        self.username = username
        self.password = password

    def test(self, username, password_token):
        return self.username==username and self.password==password_token