Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ntlm authentication #8029

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/1182.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support for ntlm authentication using ``--auth-ntlm`` option.
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,8 @@ def get_version(rel_path):
},

zip_safe=False,
extras_require={
'ntlm': ['requests_negotiate_sspi'],
},
python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*',
)
10 changes: 10 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,15 @@ def check_list_path_option(options):
) # type: Callable[..., Option]


auth_ntlm = partial(
Option,
'--auth-ntlm',
dest='auth_ntlm',
action='store_true',
default=False,
help='Authenticate on host using NTLM.')


##########
# groups #
##########
Expand Down Expand Up @@ -959,6 +968,7 @@ def check_list_path_option(options):
no_color,
no_python_version_warning,
unstable_feature,
auth_ntlm,
]
} # type: Dict[str, Any]

Expand Down
15 changes: 14 additions & 1 deletion src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.command_context import CommandContextMixIn
from pip._internal.exceptions import CommandError, PreviousBuildDirError
from pip._internal.exceptions import (
CommandError,
InstallationError,
PreviousBuildDirError,
)
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.network.auth import MultiDomainNtlmAuth
from pip._internal.network.download import Downloader
from pip._internal.network.session import PipSession
from pip._internal.operations.prepare import RequirementPreparer
Expand Down Expand Up @@ -121,6 +126,14 @@ def _build_session(self, options, retries=None, timeout=None):
"https": options.proxy,
}

if options.auth_ntlm:
try:
session.auth = MultiDomainNtlmAuth()
except InstallationError:
# Needed to allow pip to check for updates
options.auth_ntlm = False
raise

# Determine if we can prompt the user for authentication or not
session.auth.prompting = not options.no_input

Expand Down
37 changes: 34 additions & 3 deletions src/pip/_internal/network/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pip._vendor.requests.utils import get_netrc_auth
from pip._vendor.six.moves.urllib import parse as urllib_parse

from pip._internal.exceptions import InstallationError
from pip._internal.utils.misc import (
ask,
ask_input,
Expand All @@ -22,6 +23,11 @@
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

try:
from requests_negotiate_sspi import HttpNegotiateAuth # noqa
except ImportError:
HttpNegotiateAuth = None

if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import Dict, Optional, Tuple
Expand Down Expand Up @@ -72,7 +78,7 @@ def get_keyring_auth(url, username):
)


class MultiDomainBasicAuth(AuthBase):
class MultiDomainAuth(AuthBase):

def __init__(self, prompting=True, index_urls=None):
# type: (bool, Optional[Values]) -> None
Expand Down Expand Up @@ -206,7 +212,7 @@ def __call__(self, req):

if username is not None and password is not None:
# Send the basic auth with this request
req = HTTPBasicAuth(username, password)(req)
req = self.authlib(username, password)(req)

# Attach a hook to handle 401 responses
req.register_hook("response", self.handle_401)
Expand Down Expand Up @@ -260,7 +266,7 @@ def handle_401(self, resp, **kwargs):
resp.raw.release_conn()

# Add our new username and password to the request
req = HTTPBasicAuth(username or "", password or "")(resp.request)
req = self.authlib(username or "", password or "")(resp.request)
req.register_hook("response", self.warn_on_401)

# On successful request, save the credentials that were used to
Expand Down Expand Up @@ -296,3 +302,28 @@ def save_credentials(self, resp, **kwargs):
keyring.set_password(*creds)
except Exception:
logger.exception('Failed to save credentials')

@property
def authlib(self):
# Place holder for Authentication Class
raise NotImplementedError


class MultiDomainBasicAuth(MultiDomainAuth):
@property
def authlib(self):
return HTTPBasicAuth


class MultiDomainNtlmAuth(MultiDomainAuth):
def __init__(self, *args, **kwargs):
if HttpNegotiateAuth is None:
raise InstallationError(
"Dependencies for Ntlm authentication are missing. Install "
"dependencies via the 'pip install pip[ntlm]' command."
)
super(MultiDomainNtlmAuth, self).__init__(*args, **kwargs)

@property
def authlib(self):
return HttpNegotiateAuth
14 changes: 14 additions & 0 deletions tests/functional/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,20 @@ def test_pep518_forkbombs(script, data, common_wheels, command, package):
) in result.stderr, str(result)


def test_without_ntlm(script, data):
result = script.pip(
'install', '--auth-ntlm',
'-f', data.packages,
'INITools==0.2',
expect_error=True,
)
assert (
"Dependencies for Ntlm authentication are missing. Install "
"dependencies via the 'pip install pip[ntlm]' command."
in result.stderr
)


@pytest.mark.network
def test_pip_second_command_line_interface_works(
script, pip_src, data, common_wheels, deprecated_python):
Expand Down