././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1717977115.150179 requests_ntlm-1.3.0/0000755000175100001770000000000014631440033014023 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/CONTRIBUTORS.rst0000644000175100001770000000024214631440022016506 0ustar00runnerdockerContributors ------- - `Carson Lam`_ .. _Carson Lam: https://github.com/rbcarson - `Jordan Borean`_ .. _Jordan Borean: https://github.com/jborean93 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/LICENSE0000644000175100001770000000134314631440022015027 0ustar00runnerdockerISC License Copyright (c) 2013 Ben Toews Permission to use, copy, modify and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS-IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/MANIFEST.in0000644000175100001770000000017214631440022015557 0ustar00runnerdockerinclude CONTRIBUTORS.rst include LICENSE include requirements.txt recursive-include tests * recursive-exclude tests *.pyc././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1717977115.150179 requests_ntlm-1.3.0/PKG-INFO0000644000175100001770000000450614631440033015125 0ustar00runnerdockerMetadata-Version: 2.1 Name: requests_ntlm Version: 1.3.0 Summary: This package allows for HTTP NTLM authentication using the requests library. Home-page: https://github.com/requests/requests-ntlm Author: Ben Toews Author-email: mastahyeti@gmail.com License: ISC Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: License :: OSI Approved :: ISC License (ISCL) Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: cryptography>=1.3 Requires-Dist: pyspnego>=0.4.0 Requires-Dist: requests>=2.0.0 requests-ntlm ============= .. image:: https://github.com/requests/requests-ntlm/actions/workflows/ci.yml/badge.svg :target: https://github.com/requests/requests-ntlm/actions/workflows/ci.yml This package allows for HTTP NTLM authentication using the requests library. Usage ----- ``HttpNtlmAuth`` extends requests ``AuthBase``, so usage is simple: .. code:: python import requests from requests_ntlm import HttpNtlmAuth requests.get("http://ntlm_protected_site.com",auth=HttpNtlmAuth('domain\\username','password')) ``HttpNtlmAuth`` can be used in conjunction with a ``Session`` in order to make use of connection pooling. Since NTLM authenticates connections, this is more efficient. Otherwise, each request will go through a new NTLM challenge-response. .. code:: python import requests from requests_ntlm import HttpNtlmAuth session = requests.Session() session.auth = HttpNtlmAuth('domain\\username','password') session.get('http://ntlm_protected_site.com') Installation ------------ pip install requests_ntlm Requirements ------------ - requests_ - pyspnego_ .. _requests: https://github.com/kennethreitz/requests/ .. _pyspnego: https://github.com/jborean93/pyspnego/ Authors ------- - `Ben Toews`_ .. _Ben Toews: https://github.com/mastahyeti - `Ian Cordasco`_ .. _Ian Cordasco: https://github.com/sigmavirus24 - `Cory Benfield`_ .. _Cory Benfield: https://github.com/Lukasa ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/README.rst0000644000175100001770000000264514631440022015517 0ustar00runnerdockerrequests-ntlm ============= .. image:: https://github.com/requests/requests-ntlm/actions/workflows/ci.yml/badge.svg :target: https://github.com/requests/requests-ntlm/actions/workflows/ci.yml This package allows for HTTP NTLM authentication using the requests library. Usage ----- ``HttpNtlmAuth`` extends requests ``AuthBase``, so usage is simple: .. code:: python import requests from requests_ntlm import HttpNtlmAuth requests.get("http://ntlm_protected_site.com",auth=HttpNtlmAuth('domain\\username','password')) ``HttpNtlmAuth`` can be used in conjunction with a ``Session`` in order to make use of connection pooling. Since NTLM authenticates connections, this is more efficient. Otherwise, each request will go through a new NTLM challenge-response. .. code:: python import requests from requests_ntlm import HttpNtlmAuth session = requests.Session() session.auth = HttpNtlmAuth('domain\\username','password') session.get('http://ntlm_protected_site.com') Installation ------------ pip install requests_ntlm Requirements ------------ - requests_ - pyspnego_ .. _requests: https://github.com/kennethreitz/requests/ .. _pyspnego: https://github.com/jborean93/pyspnego/ Authors ------- - `Ben Toews`_ .. _Ben Toews: https://github.com/mastahyeti - `Ian Cordasco`_ .. _Ian Cordasco: https://github.com/sigmavirus24 - `Cory Benfield`_ .. _Cory Benfield: https://github.com/Lukasa ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/pyproject.toml0000644000175100001770000000141014631440022016731 0ustar00runnerdocker[build-system] requires = [ "setuptools >= 42.0.0", # Supports license_files ] build-backend = "setuptools.build_meta" [tool.black] exclude = ''' /( \.git | \.venv | build | dist )/ ''' [tool.isort] profile = "black" [tool.mypy] exclude = "setup\\.py|build/|tests/" mypy_path = "$MYPY_CONFIG_FILE_DIR" python_version = "3.8" show_error_codes = true show_column_numbers = true disallow_any_unimported = true disallow_untyped_defs = true disallow_incomplete_defs = true check_untyped_defs = true disallow_untyped_decorators = true no_implicit_reexport = true warn_redundant_casts = true warn_unused_ignores = true warn_no_return = true warn_unreachable = true [[tool.mypy.overrides]] module = "requests.packages.urllib3.*" ignore_missing_imports = true ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1717977115.150179 requests_ntlm-1.3.0/requests_ntlm/0000755000175100001770000000000014631440033016730 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/requests_ntlm/__init__.py0000644000175100001770000000010514631440022021033 0ustar00runnerdockerfrom .requests_ntlm import HttpNtlmAuth __all__ = ("HttpNtlmAuth",) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/requests_ntlm/py.typed0000644000175100001770000000000014631440022020413 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/requests_ntlm/requests_ntlm.py0000644000175100001770000002616714631440022022221 0ustar00runnerdockerfrom __future__ import annotations import base64 import typing as t import warnings from urllib.parse import urlparse import requests import spnego from cryptography import x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from requests.auth import AuthBase from requests.packages.urllib3.response import HTTPResponse class ShimSessionSecurity: """Shim used for backwards compatibility with ntlm-auth.""" def __init__(self, context: spnego.ContextProxy) -> None: self._context = context def wrap(self, message: bytes) -> tuple[bytes, bytes]: wrap_res = self._context.wrap(message, encrypt=True) signature = wrap_res.data[:16] data = wrap_res.data[16:] return data, signature def unwrap(self, message: bytes, signature: bytes) -> bytes: data = signature + message return self._context.unwrap(data).data def get_signature(self, message: bytes) -> bytes: return self._context.sign(message) def verify_signature(self, message: bytes, signature: bytes) -> None: self._context.verify(message, signature) class HttpNtlmAuth(AuthBase): """ HTTP NTLM Authentication Handler for Requests. Supports pass-the-hash. """ def __init__( self, username: str | None, password: str | None, session: None = None, send_cbt: bool = True, ) -> None: """Create an authentication handler for NTLM over HTTP. :param str username: Username in 'domain\\username' format :param str password: Password :param str session: Unused. Kept for backwards-compatibility. :param bool send_cbt: Will send the channel bindings over a HTTPS channel (Default: True) """ self.username = username self.password = password self.send_cbt = send_cbt # This exposes the encrypt/decrypt methods used to encrypt and decrypt messages # sent after ntlm authentication. These methods are utilised by libraries that # call requests_ntlm to encrypt and decrypt the messages sent after authentication self.session_security: ShimSessionSecurity | None = None def retry_using_http_NTLM_auth( self, auth_header_field: str, auth_header: str, response: requests.Response, auth_type: str, args: t.Any, ) -> requests.Response: # Get the certificate of the server if using HTTPS for CBT server_certificate_hash = self._get_server_cert(response) cbt = None if server_certificate_hash: cbt = spnego.channel_bindings.GssChannelBindings( application_data=b"tls-server-end-point:" + server_certificate_hash ) """Attempt to authenticate using HTTP NTLM challenge/response.""" if auth_header in response.request.headers: return response content_length = int( response.request.headers.get("Content-Length", "0"), base=10 ) if response.request.body and hasattr(response.request.body, "seek"): if content_length > 0: response.request.body.seek(-content_length, 1) else: response.request.body.seek(0, 0) # Consume content and release the original connection # to allow our new request to reuse the same one. response.content response.raw.release_conn() request = response.request.copy() target_hostname = t.cast(str, urlparse(response.url).hostname) spnego_options = spnego.NegotiateOptions.none if self.username and self.password: # If a username and password are specified force spnego to use the # pure NTLM code. This is for backwards compatibility with older # requests-ntlm versions which never used SSPI. If no username and # password are specified we need to rely on the cached SSPI # behaviour. # https://github.com/requests/requests-ntlm/issues/136#issuecomment-1751677055 spnego_options = spnego.NegotiateOptions.use_ntlm client = spnego.client( self.username, self.password, protocol="ntlm", channel_bindings=cbt, hostname=target_hostname, service="http", options=spnego_options, ) # Perform the first step of the NTLM authentication negotiate_message = base64.b64encode(client.step() or b"").decode() auth = "%s %s" % (auth_type, negotiate_message) request.headers[auth_header] = auth # A streaming response breaks authentication. # This can be fixed by not streaming this request, which is safe # because the returned response3 will still have stream=True set if # specified in args. In addition, we expect this request to give us a # challenge and not the real content, so the content will be short # anyway. args_nostream = dict(args, stream=False) response2 = response.connection.send(request, **args_nostream) # type: ignore[attr-defined] # needed to make NTLM auth compatible with requests-2.3.0 # Consume content and release the original connection # to allow our new request to reuse the same one. response2.content response2.raw.release_conn() request = response2.request.copy() # this is important for some web applications that store # authentication-related info in cookies (it took a long time to # figure out) if response2.headers.get("set-cookie"): request.headers["Cookie"] = response2.headers.get("set-cookie") # get the challenge auth_header_value = response2.headers[auth_header_field] auth_strip = auth_type + " " ntlm_header_value = next( ( s.strip() for s in (val.lstrip() for val in auth_header_value.split(",")) if s.startswith(auth_strip) ), None, ) if not ntlm_header_value: raise PermissionError( "Access denied: Server did not respond with NTLM challenge token" ) # Parse the challenge in the ntlm context and perform # the second step of authentication val = base64.b64decode(ntlm_header_value[len(auth_strip) :].encode()) authenticate_message = base64.b64encode(client.step(val) or b"").decode() auth = "%s %s" % (auth_type, authenticate_message) request.headers[auth_header] = auth response3 = response2.connection.send(request, **args) # Update the history. response3.history.append(response) response3.history.append(response2) self.session_security = ShimSessionSecurity(client) return response3 def response_hook(self, r: requests.Response, **kwargs: t.Any) -> requests.Response: """The actual hook handler.""" if r.status_code == 401: # Handle server auth. www_authenticate = r.headers.get("www-authenticate", "").lower() auth_type = _auth_type_from_header(www_authenticate) if auth_type is not None: return self.retry_using_http_NTLM_auth( "www-authenticate", "Authorization", r, auth_type, kwargs, ) elif r.status_code == 407: # If we didn't have server auth, do proxy auth. proxy_authenticate = r.headers.get("proxy-authenticate", "").lower() auth_type = _auth_type_from_header(proxy_authenticate) if auth_type is not None: return self.retry_using_http_NTLM_auth( "proxy-authenticate", "Proxy-authorization", r, auth_type, kwargs, ) return r def _get_server_cert(self, response: requests.Response) -> bytes | None: """ Get the certificate at the request_url and return it as a hash. Will get the raw socket from the original response from the server. This socket is then checked if it is an SSL socket and then used to get the hash of the certificate. The certificate hash is then used with NTLMv2 authentication for Channel Binding Tokens support. If the raw object is not a urllib3 HTTPReponse (default with requests) then no certificate will be returned. :param response: The original 401 response from the server :return: The hash of the DER encoded certificate at the request_url or None if not a HTTPS endpoint """ if self.send_cbt: raw_response = response.raw if isinstance(raw_response, HTTPResponse): socket = raw_response._fp.fp.raw._sock try: server_certificate = socket.getpeercert(True) except AttributeError: pass else: return _get_certificate_hash(server_certificate) else: warnings.warn( "Requests is running with a non urllib3 backend, cannot retrieve server certificate for CBT", NoCertificateRetrievedWarning, ) return None def __call__(self, r: requests.PreparedRequest) -> requests.PreparedRequest: # we must keep the connection because NTLM authenticates the # connection, not single requests r.headers["Connection"] = "Keep-Alive" r.register_hook("response", self.response_hook) return r def _auth_type_from_header(header: str) -> str | None: """ Given a WWW-Authenticate or Proxy-Authenticate header, returns the authentication type to use. We prefer NTLM over Negotiate if the server suppports it. """ if "ntlm" in header: return "NTLM" elif "negotiate" in header: return "Negotiate" return None def _get_certificate_hash(certificate_der: bytes) -> bytes | None: # https://tools.ietf.org/html/rfc5929#section-4.1 cert = x509.load_der_x509_certificate(certificate_der, default_backend()) try: hash_algorithm = cert.signature_hash_algorithm except UnsupportedAlgorithm as ex: warnings.warn( "Failed to get signature algorithm from certificate, " "unable to pass channel bindings: %s" % str(ex), UnknownSignatureAlgorithmOID, ) return None # if the cert signature algorithm is either md5 or sha1 then use sha256 # otherwise use the signature algorithm if not hash_algorithm or hash_algorithm.name in ["md5", "sha1"]: digest = hashes.Hash(hashes.SHA256(), default_backend()) else: digest = hashes.Hash(hash_algorithm, default_backend()) digest.update(certificate_der) certificate_hash_bytes = digest.finalize() return certificate_hash_bytes class NoCertificateRetrievedWarning(Warning): pass class UnknownSignatureAlgorithmOID(Warning): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1717977115.150179 requests_ntlm-1.3.0/requests_ntlm.egg-info/0000755000175100001770000000000014631440033020422 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977115.0 requests_ntlm-1.3.0/requests_ntlm.egg-info/PKG-INFO0000644000175100001770000000450614631440033021524 0ustar00runnerdockerMetadata-Version: 2.1 Name: requests_ntlm Version: 1.3.0 Summary: This package allows for HTTP NTLM authentication using the requests library. Home-page: https://github.com/requests/requests-ntlm Author: Ben Toews Author-email: mastahyeti@gmail.com License: ISC Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: License :: OSI Approved :: ISC License (ISCL) Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: cryptography>=1.3 Requires-Dist: pyspnego>=0.4.0 Requires-Dist: requests>=2.0.0 requests-ntlm ============= .. image:: https://github.com/requests/requests-ntlm/actions/workflows/ci.yml/badge.svg :target: https://github.com/requests/requests-ntlm/actions/workflows/ci.yml This package allows for HTTP NTLM authentication using the requests library. Usage ----- ``HttpNtlmAuth`` extends requests ``AuthBase``, so usage is simple: .. code:: python import requests from requests_ntlm import HttpNtlmAuth requests.get("http://ntlm_protected_site.com",auth=HttpNtlmAuth('domain\\username','password')) ``HttpNtlmAuth`` can be used in conjunction with a ``Session`` in order to make use of connection pooling. Since NTLM authenticates connections, this is more efficient. Otherwise, each request will go through a new NTLM challenge-response. .. code:: python import requests from requests_ntlm import HttpNtlmAuth session = requests.Session() session.auth = HttpNtlmAuth('domain\\username','password') session.get('http://ntlm_protected_site.com') Installation ------------ pip install requests_ntlm Requirements ------------ - requests_ - pyspnego_ .. _requests: https://github.com/kennethreitz/requests/ .. _pyspnego: https://github.com/jborean93/pyspnego/ Authors ------- - `Ben Toews`_ .. _Ben Toews: https://github.com/mastahyeti - `Ian Cordasco`_ .. _Ian Cordasco: https://github.com/sigmavirus24 - `Cory Benfield`_ .. _Cory Benfield: https://github.com/Lukasa ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977115.0 requests_ntlm-1.3.0/requests_ntlm.egg-info/SOURCES.txt0000644000175100001770000000107514631440033022311 0ustar00runnerdockerCONTRIBUTORS.rst LICENSE MANIFEST.in README.rst pyproject.toml requirements.txt setup.cfg setup.py requests_ntlm/__init__.py requests_ntlm/py.typed requests_ntlm/requests_ntlm.py requests_ntlm.egg-info/PKG-INFO requests_ntlm.egg-info/SOURCES.txt requests_ntlm.egg-info/dependency_links.txt requests_ntlm.egg-info/requires.txt requests_ntlm.egg-info/top_level.txt tests/__init__.py tests/test_server.py tests/test_utils.py tests/functional/__init__.py tests/functional/setup_iis.ps1 tests/functional/test_functional.py tests/unit/__init__.py tests/unit/test_requests_ntlm.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977115.0 requests_ntlm-1.3.0/requests_ntlm.egg-info/dependency_links.txt0000644000175100001770000000000114631440033024470 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977115.0 requests_ntlm-1.3.0/requests_ntlm.egg-info/requires.txt0000644000175100001770000000006214631440033023020 0ustar00runnerdockercryptography>=1.3 pyspnego>=0.4.0 requests>=2.0.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977115.0 requests_ntlm-1.3.0/requests_ntlm.egg-info/top_level.txt0000644000175100001770000000001614631440033023151 0ustar00runnerdockerrequests_ntlm ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/requirements.txt0000644000175100001770000000020114631440022017276 0ustar00runnerdockerblack == 24.4.2 isort == 5.13.2 requests>=2.0.0 pyspnego cryptography>=1.3 flask mypy == 1.10.0 pytest pytest-cov types-requests ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1717977115.1541789 requests_ntlm-1.3.0/setup.cfg0000644000175100001770000000171314631440033015646 0ustar00runnerdocker[metadata] name = requests_ntlm version = 1.3.0 url = https://github.com/requests/requests-ntlm author = Ben Toews author_email = mastahyeti@gmail.com license = ISC license_files = LICENSE description = This package allows for HTTP NTLM authentication using the requests library. long_description = file: README.rst long_description_content_type = text/x-rst classifiers = Development Status :: 4 - Beta Intended Audience :: Developers Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 License :: OSI Approved :: ISC License (ISCL) [options] python_requires = >= 3.8 install_requires = cryptography >= 1.3 pyspnego >= 0.4.0 requests >= 2.0.0 [tools.setuptools.package-data] "requests_ntlm" = ["py.typed"] [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/setup.py0000644000175100001770000000021214631440022015526 0ustar00runnerdocker#!/usr/bin/env python from setuptools import setup # Kept for editable install compatibility with older setuptools/pip versions setup() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1717977115.150179 requests_ntlm-1.3.0/tests/0000755000175100001770000000000014631440033015165 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/tests/__init__.py0000644000175100001770000000000014631440022017262 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1717977115.150179 requests_ntlm-1.3.0/tests/functional/0000755000175100001770000000000014631440033017327 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/tests/functional/__init__.py0000644000175100001770000000000014631440022021424 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/tests/functional/setup_iis.ps10000644000175100001770000000662414631440022021766 0ustar00runnerdocker# Script to install an IIS website with windows authentication only as well as setting up a local admin account # Authors: Jordan Borean # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ function SetupUser() { $computername = $env:computername $username = 'User' $password = 'Password01' $desc = 'Automatically created local admin account' $computer = [ADSI]"WinNT://$computername,computer" $user = $computer.Create("user", $username) $user.SetPassword($password) $user.Setinfo() $user.description = $desc $user.setinfo() $user.UserFlags = 65536 $user.SetInfo() $group = [ADSI]("WinNT://$computername/administrators,group") $group.add("WinNT://$username,user") } function SetupIIS () { Import-Module WebAdministration $cert = New-SelfSignedCertificate -DnsName ("127.0.0.1") -CertStoreLocation cert:\LocalMachine\My $rootStore = Get-Item cert:\LocalMachine\Root $rootStore.Open("ReadWrite") $rootStore.Add($cert) $rootStore.Close(); New-Item C:\temp -Type Directory -Force New-Item C:\temp\iisroot -Type Directory -Force New-Item C:\temp\iisroot\contents.txt -Type File -Force -Value "contents" $iisExec = "C:\Windows\System32\inetsrv\appcmd.exe" Start-Process -FilePath $iisExec -ArgumentList "add site /name:""Site1"" /id:11 /physicalPath:""C:\temp\iisroot"" /bindings:http/*:81:" -Wait Start-Process -FilePath $iisExec -ArgumentList "set config ""Site1"" -section:system.webServer/security/authentication/anonymousAuthentication /enabled:""False"" /commit:apphost" -Wait Start-Process -FilePath $iisExec -ArgumentList "set config ""Site1"" -section:system.webServer/security/authentication/windowsAuthentication /enabled:""True"" /commit:apphost" -Wait Start-Process -FilePath $iisExec -ArgumentList "set config ""Site1"" -section:system.webServer/security/authentication/windowsAuthentication /extendedProtection.tokenChecking:""Require"" /extendedProtection.flags:""None"" /commit:apphost" -Wait Start-Process -FilePath $iisExec -ArgumentList "stop site /site.name:""Site1"" " -Wait Start-Process -FilePath $iisExec -ArgumentList "start site /site.name:""Site1"" " -Wait Start-Process -FilePath $iisExec -ArgumentList "add site /name:""Site2"" /id:12 /physicalPath:""C:\temp\iisroot"" /bindings:http/*:82:" -Wait Start-Process -FilePath $iisExec -ArgumentList "set config ""Site2"" -section:system.webServer/security/authentication/anonymousAuthentication /enabled:""False"" /commit:apphost" -Wait Start-Process -FilePath $iisExec -ArgumentList "set config ""Site2"" -section:system.webServer/security/authentication/windowsAuthentication /enabled:""True"" /commit:apphost" -Wait Start-Process -FilePath $iisExec -ArgumentList "set config ""Site2"" -section:system.webServer/security/authentication/windowsAuthentication /extendedProtection.tokenChecking:""None"" /extendedProtection.flags:""None"" /commit:apphost" -Wait Start-Process -FilePath $iisExec -ArgumentList "stop site /site.name:""Site2"" " -Wait Start-Process -FilePath $iisExec -ArgumentList "start site /site.name:""Site2"" " -Wait Set-Location IIS:\SslBindings New-WebBinding -Name "Site1" -IP "*" -Port 441 -Protocol https New-WebBinding -Name "Site2" -IP "*" -Port 442 -Protocol https $cert | New-Item 0.0.0.0!441 $cert | New-Item 0.0.0.0!442 } function main () { SetupUser SetupIIS } main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/tests/functional/test_functional.py0000644000175100001770000000376714631440022023115 0ustar00runnerdockerimport requests import requests_ntlm """ This test is meant to run with Appveyor but until the integration is solved it can only be run locally. The script setup_iis.ps1 can set up an IIS server with the 4 scenarios tested below if you wish to run a sanity check """ username = ".\\User" password = "Password01" http_with_cbt = "http://127.0.0.1:81/contents.txt" http_without_cbt = "http://127.0.0.1:82/contents.txt" https_with_cbt = "https://127.0.0.1:441/contents.txt" https_without_cbt = "https://127.0.0.1:442/contents.txt" expected = "contents" class Test_Functional: def test_ntlm_http_with_cbt(self): actual = send_request(http_with_cbt, username, password) actual_content = actual.content.decode("utf-8") actual_code = actual.status_code assert actual_code == 200 assert actual_content == expected def test_ntlm_http_without_cbt(self): actual = send_request(http_without_cbt, username, password) actual_content = actual.content.decode("utf-8") actual_code = actual.status_code assert actual_code == 200 assert actual_content == expected def test_ntlm_https_with_cbt(self): actual = send_request(https_with_cbt, username, password) actual_content = actual.content.decode("utf-8") actual_code = actual.status_code assert actual_code == 200 assert actual_content == expected def test_ntlm_https_without_cbt(self): actual = send_request(https_without_cbt, username, password) actual_content = actual.content.decode("utf-8") actual_code = actual.status_code assert actual_code == 200 assert actual_content == expected def send_request(url, username, password): """ Sends a request to the url with the credentials specified. Returns the final response """ session = requests.Session() session.verify = False session.auth = requests_ntlm.HttpNtlmAuth(username, password) response = session.get(url) return response ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1717977106.0 requests_ntlm-1.3.0/tests/test_server.py0000644000175100001770000000616714631440022020114 0ustar00runnerdockerimport base64 import struct from flask import Flask, request from tests.test_utils import domain, password, username app = Flask(__name__) @app.route("/ntlm") def ntlm_auth(): return get_auth_response("NTLM") @app.route("/negotiate") def negotiate_auth(): return get_auth_response("Negotiate") @app.route("/both") def negotiate_and_ntlm_auth(): return get_auth_response("NTLM", advertise_nego_and_ntlm=True) @app.route("/no_challenge") def no_challenge(): return get_auth_response("Negotiate", no_challenge=True) def get_auth_response(auth_type, advertise_nego_and_ntlm=False, no_challenge=False): # Get the actual header that is returned by requests_ntlm actual_header = request.headers.get("Authorization", "") # Check what the message type is from the header if actual_header == "": # This is the initial connection, need to return a 401 response_headers = { "WWW-Authenticate": ( auth_type if not advertise_nego_and_ntlm else "Negotiate, NTLM" ) } status_code = 401 response = "auth with '%s\\%s':'%s'" % (domain, username, password) else: # Set human readable names for message types # see https://msdn.microsoft.com/en-us/library/cc236639.aspx for more details expected_signature = b"NTLMSSP\x00" negotiate_message_type = 1 authenticate_message_type = 3 msg = base64.b64decode(actual_header[len(auth_type) :]) signature = msg[0:8] if signature != expected_signature: raise ValueError( "Mismatch on NTLM message signature, expecting: %s, actual: %s" % (expected_signature, signature) ) # Get the NTLM version number (bytes 9 - 12) message_type = struct.unpack("