Skip to content

Commit

Permalink
Added support for auth plugins.
Browse files Browse the repository at this point in the history
  • Loading branch information
jkbrzt committed Sep 21, 2013
1 parent f7b703b commit 2acb303
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 25 deletions.
8 changes: 8 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,13 @@ Authorization information from your ``.netrc`` file is honored as well:
[...]
-------
Plugins
-------
* `httpie-ntlm <https://github.com/jkbr/httpie-ntlm>`_
=======
Proxies
=======
Expand Down Expand Up @@ -1206,6 +1213,7 @@ Changelog
* `0.7.0-dev`_
* Added ``--ignore-stdin``.
* Added support for auth plugins.
* `0.6.0`_ (2013-06-03)
* XML data is now formatted.

This comment has been minimized.

Copy link
@83anthony

83anthony Jan 29, 2015

looking good

* ``--session`` and ``--session-read-only`` now also accept paths to
Expand Down
2 changes: 1 addition & 1 deletion httpie/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

This comment has been minimized.

Copy link
@83anthony

83anthony Jan 29, 2015

hay

__author__ = 'Jakub Roztocil'
__version__ = '0.6.0'
__version__ = '0.7.0'
__licence__ = 'BSD'


Expand Down
26 changes: 23 additions & 3 deletions httpie/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from . import __doc__
from . import __version__
from .plugins.builtin import BuiltinAuthPlugin
from .plugins import plugin_manager
from .sessions import DEFAULT_SESSIONS_DIR
from .output import AVAILABLE_STYLES, DEFAULT_STYLE
from .input import (Parser, AuthCredentialsArgType, KeyValueArgType,
Expand Down Expand Up @@ -381,14 +383,32 @@ def _split_lines(self, text, width):
""",
)

_auth_plugins = plugin_manager.get_auth_plugins()
auth.add_argument(
'--auth-type',
choices=['basic', 'digest'],
default='basic',
choices=[plugin.auth_type for plugin in _auth_plugins],
default=_auth_plugins[0].auth_type,
help="""
The authentication mechanism to be used. Defaults to "basic".
The authentication mechanism to be used. Defaults to "{default}".
{types}
"""
.format(default=_auth_plugins[0].auth_type, types='\n '.join(
'"{type}": {name}{package}{description}'.format(
type=plugin.auth_type,
name=plugin.name,
package=(
'' if issubclass(plugin, BuiltinAuthPlugin)
else ' (provided by %s)' % plugin.package_name
),
description=(
'' if not plugin.description else
'\n ' + ('\n '.join(wrap(plugin.description)))
)
)
for plugin in _auth_plugins
)),
)


Expand Down
14 changes: 10 additions & 4 deletions httpie/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@

from . import sessions
from . import __version__
from .plugins import plugin_manager


FORM = 'application/x-www-form-urlencoded; charset=utf-8'
JSON = 'application/json; charset=utf-8'
DEFAULT_UA = 'HTTPie/%s' % __version__


class HTTPie(object):

def __init__(self, env, plugin_manager):
pass


def get_response(args, config_dir):
"""Send the request and return a `request.Response`."""

Expand All @@ -27,6 +34,7 @@ def get_response(args, config_dir):
response = requests.request(**requests_kwargs)
else:
response = sessions.get_response(
args=args,
config_dir=config_dir,
session_name=args.session or args.session_read_only,
requests_kwargs=requests_kwargs,
Expand Down Expand Up @@ -69,10 +77,8 @@ def get_requests_kwargs(args):

credentials = None
if args.auth:
credentials = {
'basic': requests.auth.HTTPBasicAuth,
'digest': requests.auth.HTTPDigestAuth,
}[args.auth_type](args.auth.key, args.auth.value)
auth_plugin = plugin_manager.get_auth_plugin(args.auth_type)()
credentials = auth_plugin.get_auth(args.auth.key, args.auth.value)

kwargs = {
'stream': True,
Expand Down
5 changes: 4 additions & 1 deletion httpie/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
from requests import __version__ as requests_version
from pygments import __version__ as pygments_version

from .cli import parser
from .compat import str, is_py3
from .client import get_response
from .downloads import Download
from .models import Environment
from .output import build_output_stream, write, write_with_colors_win_py3
from . import ExitStatus
from .plugins import plugin_manager


def get_exit_status(http_status, follow=False):
Expand Down Expand Up @@ -58,6 +58,9 @@ def main(args=sys.argv[1:], env=Environment()):
Return exit status code.
"""
plugin_manager.load_installed_plugins()
from .cli import parser

if env.config.default_options:
args = env.config.default_options + args

Expand Down
2 changes: 1 addition & 1 deletion httpie/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import re
import json
import mimetypes
import getpass
from getpass import getpass
from io import BytesIO
#noinspection PyCompatibility
from argparse import ArgumentParser, ArgumentTypeError, ArgumentError
Expand Down
9 changes: 9 additions & 0 deletions httpie/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .base import AuthPlugin
from .manager import PluginManager
from .builtin import BasicAuthPlugin, DigestAuthPlugin


plugin_manager = PluginManager()
plugin_manager.register(BasicAuthPlugin)
plugin_manager.register(DigestAuthPlugin)

28 changes: 28 additions & 0 deletions httpie/plugins/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class AuthPlugin(object):
"""
Base auth plugin class.
See <https://github.com/jkbr/httpie-ntlm> for an example auth plugin.
"""

# The value that should be passed to --auth-type
# to use this auth plugin. Eg. "my-auth"
auth_type = None

# The name of the plugin, eg. "My auth".
name = None

# Optional short description. Will be be shown in the help
# under --auth-type.
description = None

# This be set automatically once the plugin has been loaded.
package_name = None

def get_auth(self, username, password):
"""
Return a ``requests.auth.AuthBase`` subclass instance.
"""
raise NotImplementedError()
26 changes: 26 additions & 0 deletions httpie/plugins/builtin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import requests.auth

from .base import AuthPlugin


class BuiltinAuthPlugin(AuthPlugin):

package_name = '(builtin)'


class BasicAuthPlugin(BuiltinAuthPlugin):

name = 'Basic HTTP auth'
auth_type = 'basic'

def get_auth(self, username, password):
return requests.auth.HTTPBasicAuth(username, password)


class DigestAuthPlugin(BuiltinAuthPlugin):

name = 'Digest HTTP auth'
auth_type = 'digest'

def get_auth(self, username, password):
return requests.auth.HTTPDigestAuth(username, password)
35 changes: 35 additions & 0 deletions httpie/plugins/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from pkg_resources import iter_entry_points


ENTRY_POINT_NAMES = [
'httpie.plugins.auth.v1'
]


class PluginManager(object):

def __init__(self):
self._plugins = []

def __iter__(self):
return iter(self._plugins)

def register(self, plugin):
self._plugins.append(plugin)

def get_auth_plugins(self):
return list(self._plugins)

def get_auth_plugin_mapping(self):
return dict((plugin.auth_type, plugin) for plugin in self)

def get_auth_plugin(self, auth_type):
return self.get_auth_plugin_mapping()[auth_type]

def load_installed_plugins(self):

for entry_point_name in ENTRY_POINT_NAMES:
for entry_point in iter_entry_points(entry_point_name):
plugin = entry_point.load()
plugin.package_name = entry_point.dist.key
self.register(entry_point.load())
29 changes: 14 additions & 15 deletions httpie/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

import requests
from requests.cookies import RequestsCookieJar, create_cookie
from requests.auth import HTTPBasicAuth, HTTPDigestAuth

from .compat import urlsplit
from .config import BaseConfigDict, DEFAULT_CONFIG_DIR
from httpie.plugins import plugin_manager


SESSIONS_DIR_NAME = 'sessions'
Expand All @@ -21,7 +21,8 @@
SESSION_IGNORED_HEADER_PREFIXES = ['Content-', 'If-']


def get_response(session_name, requests_kwargs, config_dir, read_only=False):
def get_response(session_name, requests_kwargs, config_dir, args,
read_only=False):
"""Like `client.get_response`, but applies permanent
aspects of the session to the request.
Expand Down Expand Up @@ -50,9 +51,12 @@ def get_response(session_name, requests_kwargs, config_dir, read_only=False):
requests_kwargs['headers'] = dict(session.headers, **request_headers)
session.update_headers(request_headers)

auth = requests_kwargs.get('auth', None)
if auth:
session.auth = auth
if args.auth:
session.auth = {
'type': args.auth_type,
'username': args.auth.key,
'password': args.auth.value,
}
elif session.auth:
requests_kwargs['auth'] = session.auth

Expand Down Expand Up @@ -140,15 +144,10 @@ def auth(self):
auth = self.get('auth', None)
if not auth or not auth['type']:
return
Auth = {'basic': HTTPBasicAuth,
'digest': HTTPDigestAuth}[auth['type']]
return Auth(auth['username'], auth['password'])
auth_plugin = plugin_manager.get_auth_plugin(auth['type'])()
return auth_plugin.get_auth(auth['username'], auth['password'])

@auth.setter
def auth(self, cred):
self['auth'] = {
'type': {HTTPBasicAuth: 'basic',
HTTPDigestAuth: 'digest'}[type(cred)],
'username': cred.username,
'password': cred.password,
}
def auth(self, auth):
assert set(['type', 'username', 'password']) == set(auth.keys())
self['auth'] = auth

0 comments on commit 2acb303

Please sign in to comment.