mirror of
https://github.com/httpie/cli.git
synced 2024-11-22 07:43:20 +01:00
Added netrc support for auth plugins.
Enabled for --auth-type=basic and digest, 3rd parties may opt in. This closes #718, closes #719, closes #852, and also closes #934
This commit is contained in:
parent
c240162cab
commit
b86598886e
@ -15,6 +15,8 @@ This project adheres to `Semantic Versioning <https://semver.org/>`_.
|
||||
* Added support for ``$XDG_CONFIG_HOME`` (`#920`_).
|
||||
* Added support for custom content types for uploaded files (`#668`_).
|
||||
* Added support for ``Set-Cookie``-triggered cookie expiration (`#853`_).
|
||||
* Added ``netrc`` support for auth plugins.
|
||||
Enabled for ``--auth-type=basic`` and ``digest``, 3rd parties may opt in (`#718`_, `#719`_, `#852`_, `#934`_).
|
||||
|
||||
|
||||
`2.1.0`_ (2020-04-18)
|
||||
@ -439,9 +441,13 @@ This project adheres to `Semantic Versioning <https://semver.org/>`_.
|
||||
.. _#128: https://github.com/jakubroztocil/httpie/issues/128
|
||||
.. _#488: https://github.com/jakubroztocil/httpie/issues/488
|
||||
.. _#668: https://github.com/jakubroztocil/httpie/issues/668
|
||||
.. _#718: https://github.com/jakubroztocil/httpie/issues/718
|
||||
.. _#719: https://github.com/jakubroztocil/httpie/issues/719
|
||||
.. _#840: https://github.com/jakubroztocil/httpie/issues/840
|
||||
.. _#853: https://github.com/jakubroztocil/httpie/issues/853
|
||||
.. _#852: https://github.com/jakubroztocil/httpie/issues/852
|
||||
.. _#870: https://github.com/jakubroztocil/httpie/issues/870
|
||||
.. _#895: https://github.com/jakubroztocil/httpie/issues/895
|
||||
.. _#920: https://github.com/jakubroztocil/httpie/issues/920
|
||||
.. _#925: https://github.com/jakubroztocil/httpie/issues/925
|
||||
.. _#934: https://github.com/jakubroztocil/httpie/issues/934
|
||||
|
@ -7,6 +7,8 @@ from argparse import RawDescriptionHelpFormatter
|
||||
from textwrap import dedent
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from requests.utils import get_netrc_auth
|
||||
|
||||
from httpie.cli.argtypes import AuthCredentials, KeyValueArgType, parse_auth
|
||||
from httpie.cli.constants import (
|
||||
HTTP_GET, HTTP_POST, OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT,
|
||||
@ -154,7 +156,7 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
|
||||
self.env.stdout_isatty = False
|
||||
|
||||
def _process_auth(self):
|
||||
# TODO: refactor
|
||||
# TODO: refactor & simplify this method.
|
||||
self.args.auth_plugin = None
|
||||
default_auth_plugin = plugin_manager.get_auth_plugins()[0]
|
||||
auth_type_set = self.args.auth_type is not None
|
||||
@ -177,6 +179,19 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
|
||||
self.args.auth_type = default_auth_plugin.auth_type
|
||||
plugin = plugin_manager.get_auth_plugin(self.args.auth_type)()
|
||||
|
||||
if (not self.args.ignore_netrc
|
||||
and self.args.auth is None
|
||||
and plugin.netrc_parse):
|
||||
# Only host needed, so it’s OK URL not finalized.
|
||||
netrc_credentials = get_netrc_auth(self.args.url)
|
||||
if netrc_credentials:
|
||||
self.args.auth = AuthCredentials(
|
||||
key=netrc_credentials[0],
|
||||
value=netrc_credentials[1],
|
||||
sep=SEPARATOR_CREDENTIALS,
|
||||
orig=SEPARATOR_CREDENTIALS.join(netrc_credentials)
|
||||
)
|
||||
|
||||
if plugin.auth_require and self.args.auth is None:
|
||||
self.error('--auth required')
|
||||
|
||||
|
@ -35,13 +35,22 @@ class AuthPlugin(BasePlugin):
|
||||
# Set this to `False` to disable the parsing and error handling.
|
||||
auth_parse = True
|
||||
|
||||
# Set to `True` to make it possible for this auth
|
||||
# plugin to acquire credentials from the user’s netrc file(s).
|
||||
# It is used as a fallback when the credentials are not provided explicitly
|
||||
# through `--auth, -a`. Enabling this will allow skipping `--auth, -a`
|
||||
# even when `auth_require` is set `True` (provided that netrc provides
|
||||
# credential for a given host).
|
||||
netrc_parse = False
|
||||
|
||||
# If both `auth_parse` and `prompt_password` are set to `True`,
|
||||
# and the value of `-a` lacks the password part,
|
||||
# then the user will be prompted to type the password in.
|
||||
prompt_password = True
|
||||
|
||||
# Will be set to the raw value of `-a` (if provided) before
|
||||
# `get_auth()` gets called.
|
||||
# `get_auth()` gets called. If the credentials came from a netrc file,
|
||||
# then this is `None`.
|
||||
raw_auth = None
|
||||
|
||||
def get_auth(self, username=None, password=None):
|
||||
|
@ -22,6 +22,7 @@ class HTTPBasicAuth(requests.auth.HTTPBasicAuth):
|
||||
See https://github.com/jakubroztocil/httpie/issues/212
|
||||
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
request.headers['Authorization'] = type(self).make_header(
|
||||
self.username, self.password).encode('latin1')
|
||||
return request
|
||||
@ -36,6 +37,7 @@ class HTTPBasicAuth(requests.auth.HTTPBasicAuth):
|
||||
class BasicAuthPlugin(BuiltinAuthPlugin):
|
||||
name = 'Basic HTTP auth'
|
||||
auth_type = 'basic'
|
||||
netrc_parse = True
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
def get_auth(self, username: str, password: str) -> HTTPBasicAuth:
|
||||
@ -43,9 +45,9 @@ class BasicAuthPlugin(BuiltinAuthPlugin):
|
||||
|
||||
|
||||
class DigestAuthPlugin(BuiltinAuthPlugin):
|
||||
|
||||
name = 'Digest HTTP auth'
|
||||
auth_type = 'digest'
|
||||
netrc_parse = True
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
def get_auth(
|
||||
|
@ -3,6 +3,7 @@ import mock
|
||||
import pytest
|
||||
|
||||
from httpie.plugins.builtin import HTTPBasicAuth
|
||||
from httpie.status import ExitStatus
|
||||
from httpie.utils import ExplicitNullAuth
|
||||
from utils import http, add_auth, HTTP_OK, MockEnvironment
|
||||
import httpie.cli.constants
|
||||
@ -15,6 +16,7 @@ def test_basic_auth(httpbin_both):
|
||||
assert HTTP_OK in r
|
||||
assert r.json == {'authenticated': True, 'user': 'user'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('argument_name', ['--auth-type', '-A'])
|
||||
def test_digest_auth(httpbin_both, argument_name):
|
||||
r = http(argument_name + '=digest', '--auth=user:password',
|
||||
@ -77,6 +79,8 @@ def test_missing_auth(httpbin):
|
||||
|
||||
|
||||
def test_netrc(httpbin_both):
|
||||
# This one gets handled by requests (no --auth, --auth-type present),
|
||||
# that’s why we patch inside `requests.sessions`.
|
||||
with mock.patch('requests.sessions.get_netrc_auth') as get_netrc_auth:
|
||||
get_netrc_auth.return_value = ('httpie', 'password')
|
||||
r = http(httpbin_both + '/basic-auth/httpie/password')
|
||||
@ -85,21 +89,13 @@ def test_netrc(httpbin_both):
|
||||
|
||||
|
||||
def test_ignore_netrc(httpbin_both):
|
||||
with mock.patch('requests.sessions.get_netrc_auth') as get_netrc_auth:
|
||||
with mock.patch('httpie.cli.argparser.get_netrc_auth') as get_netrc_auth:
|
||||
get_netrc_auth.return_value = ('httpie', 'password')
|
||||
r = http('--ignore-netrc', httpbin_both + '/basic-auth/httpie/password')
|
||||
assert get_netrc_auth.call_count == 0
|
||||
assert 'HTTP/1.1 401 UNAUTHORIZED' in r
|
||||
|
||||
|
||||
def test_ignore_netrc_null_auth():
|
||||
args = httpie.cli.definition.parser.parse_args(
|
||||
args=['--ignore-netrc', 'example.org'],
|
||||
env=MockEnvironment(),
|
||||
)
|
||||
assert isinstance(args.auth, ExplicitNullAuth)
|
||||
|
||||
|
||||
def test_ignore_netrc_together_with_auth():
|
||||
args = httpie.cli.definition.parser.parse_args(
|
||||
args=['--ignore-netrc', '--auth=username:password', 'example.org'],
|
||||
@ -107,10 +103,40 @@ def test_ignore_netrc_together_with_auth():
|
||||
)
|
||||
assert isinstance(args.auth, HTTPBasicAuth)
|
||||
|
||||
def test_honor_netrc_authtype(httpbin_both):
|
||||
with mock.patch('requests.sessions.get_netrc_auth') as get_netrc_auth:
|
||||
get_netrc_auth.return_value = ('httpie', 'password')
|
||||
r = http('--auth-type=basic','GET', httpbin_both + '/basic-auth/httpie/password')
|
||||
assert get_netrc_auth.call_count == 1
|
||||
assert HTTP_OK in r
|
||||
|
||||
def test_ignore_netrc_with_auth_type_resulting_in_missing_auth(httpbin):
|
||||
with mock.patch('httpie.cli.argparser.get_netrc_auth') as get_netrc_auth:
|
||||
get_netrc_auth.return_value = ('httpie', 'password')
|
||||
r = http(
|
||||
'--ignore-netrc',
|
||||
'--auth-type=basic',
|
||||
httpbin + '/basic-auth/httpie/password',
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
assert get_netrc_auth.call_count == 0
|
||||
assert r.exit_status == ExitStatus.ERROR
|
||||
assert '--auth required' in r.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
argnames=['auth_type', 'endpoint'],
|
||||
argvalues=[
|
||||
('basic', '/basic-auth/httpie/password'),
|
||||
('digest', '/digest-auth/auth/httpie/password'),
|
||||
],
|
||||
)
|
||||
def test_auth_plugin_netrc_parse(auth_type, endpoint, httpbin):
|
||||
# Test
|
||||
with mock.patch('httpie.cli.argparser.get_netrc_auth') as get_netrc_auth:
|
||||
get_netrc_auth.return_value = ('httpie', 'password')
|
||||
r = http('--auth-type', auth_type, httpbin + endpoint)
|
||||
assert get_netrc_auth.call_count == 1
|
||||
assert HTTP_OK in r
|
||||
|
||||
|
||||
def test_ignore_netrc_null_auth():
|
||||
args = httpie.cli.definition.parser.parse_args(
|
||||
args=['--ignore-netrc', 'example.org'],
|
||||
env=MockEnvironment(),
|
||||
)
|
||||
assert isinstance(args.auth, ExplicitNullAuth)
|
||||
|
Loading…
Reference in New Issue
Block a user