master
py 113 lines 4.4 KB
Raw
1 # SPDX-License-Identifier: MIT
2 """
3 NTLM authenticating pool, contributed by erikcederstran
4
5 Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
6 """
7 from __future__ import absolute_import
8
9 from logging import getLogger
10 from ntlm import ntlm
11
12 from .. import HTTPSConnectionPool
13 from ..packages.six.moves.http_client import HTTPSConnection
14
15
16 log = getLogger(__name__)
17
18
19 class NTLMConnectionPool(HTTPSConnectionPool):
20 """
21 Implements an NTLM authentication version of an urllib3 connection pool
22 """
23
24 scheme = 'https'
25
26 def __init__(self, user, pw, authurl, *args, **kwargs):
27 """
28 authurl is a random URL on the server that is protected by NTLM.
29 user is the Windows user, probably in the DOMAIN\\username format.
30 pw is the password for the user.
31 """
32 super(NTLMConnectionPool, self).__init__(*args, **kwargs)
33 self.authurl = authurl
34 self.rawuser = user
35 user_parts = user.split('\\', 1)
36 self.domain = user_parts[0].upper()
37 self.user = user_parts[1]
38 self.pw = pw
39
40 def _new_conn(self):
41 # Performs the NTLM handshake that secures the connection. The socket
42 # must be kept open while requests are performed.
43 self.num_connections += 1
44 log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s',
45 self.num_connections, self.host, self.authurl)
46
47 headers = {}
48 headers['Connection'] = 'Keep-Alive'
49 req_header = 'Authorization'
50 resp_header = 'www-authenticate'
51
52 conn = HTTPSConnection(host=self.host, port=self.port)
53
54 # Send negotiation message
55 headers[req_header] = (
56 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser))
57 log.debug('Request headers: %s', headers)
58 conn.request('GET', self.authurl, None, headers)
59 res = conn.getresponse()
60 reshdr = dict(res.getheaders())
61 log.debug('Response status: %s %s', res.status, res.reason)
62 log.debug('Response headers: %s', reshdr)
63 log.debug('Response data: %s [...]', res.read(100))
64
65 # Remove the reference to the socket, so that it can not be closed by
66 # the response object (we want to keep the socket open)
67 res.fp = None
68
69 # Server should respond with a challenge message
70 auth_header_values = reshdr[resp_header].split(', ')
71 auth_header_value = None
72 for s in auth_header_values:
73 if s[:5] == 'NTLM ':
74 auth_header_value = s[5:]
75 if auth_header_value is None:
76 raise Exception('Unexpected %s response header: %s' %
77 (resp_header, reshdr[resp_header]))
78
79 # Send authentication message
80 ServerChallenge, NegotiateFlags = \
81 ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value)
82 auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge,
83 self.user,
84 self.domain,
85 self.pw,
86 NegotiateFlags)
87 headers[req_header] = 'NTLM %s' % auth_msg
88 log.debug('Request headers: %s', headers)
89 conn.request('GET', self.authurl, None, headers)
90 res = conn.getresponse()
91 log.debug('Response status: %s %s', res.status, res.reason)
92 log.debug('Response headers: %s', dict(res.getheaders()))
93 log.debug('Response data: %s [...]', res.read()[:100])
94 if res.status != 200:
95 if res.status == 401:
96 raise Exception('Server rejected request: wrong '
97 'username or password')
98 raise Exception('Wrong server response: %s %s' %
99 (res.status, res.reason))
100
101 res.fp = None
102 log.debug('Connection established')
103 return conn
104
105 def urlopen(self, method, url, body=None, headers=None, retries=3,
106 redirect=True, assert_same_host=True):
107 if headers is None:
108 headers = {}
109 headers['Connection'] = 'Keep-Alive'
110 return super(NTLMConnectionPool, self).urlopen(method, url, body,
111 headers, retries,
112 redirect,
113 assert_same_host)