ntlmpool.py 4.4 KB

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