Removed passlib to plugin, no-auth defaults, integration tests

This commit is contained in:
Matthew Planchard 2018-08-04 21:22:14 -05:00
parent 84158ab881
commit 824c2dd24f
5 changed files with 84 additions and 90 deletions

@ -148,6 +148,10 @@ class _PypiserverParser(ArgumentParser):
parsed = super(_PypiserverParser, self).parse_args(
args=args, namespace=namespace
)
if not hasattr(parsed, 'authenticate'):
# Ensure a useful value is present even when no auth
# plugins are installed.
parsed.authenticate = _Defaults.authenticate
for attr, parser in self.extra_parsers.items():
if hasattr(parsed, attr):
setattr(parsed, attr, parser(getattr(parsed, attr)))
@ -410,31 +414,42 @@ class Config(object):
:param ArgumentParser parser: an ArgumentParser instance
"""
auth_plugins_available = bool(
set(self._plugins['authenticators']).difference(
set(('no-auth',))
)
)
security = parser.add_argument_group(
title='Security',
description='Configure pypiserver access controls'
)
# TODO: pull some of this long stuff out into an epilog
security.add_argument(
'-a', '--authenticate',
default=environ.get(
'PYPISERVER_AUTHENTICATE',
_Defaults.authenticate,
),
help=dedent('''\
comma-separated list of (case-insensitive) actions to
authenticate. Use "." for no authentication. Requires the
password (-P option) to be set. For example to password-protect
package downloads (in addition to uploads), while leaving
listings public, use: `-P foo/htpasswd.txt` -a update,download
To drop all authentications, use: `-P . -a `.
Note that when uploads are not protected, the `register`
command is not necessary, but `~/.pypirc` still requires
username and password fields, even if bogus. By default,
only %(default)s is password-protected
''')
)
if auth_plugins_available or self.parser_type == 'pypi-server':
# Do not bother to show authentication arguments when no
# non-dummy auth plugins are installed.
security.add_argument(
'-a', '--authenticate',
default=environ.get(
'PYPISERVER_AUTHENTICATE',
_Defaults.authenticate,
),
# TODO: pull some of this long stuff out into an epilog
help=dedent('''\
comma-separated list of (case-insensitive) actions to
authenticate. Use "." for no authentication. Requires the
password (-P option) to be set. For example to
password-protect package downloads (in addition to
uploads), while leaving listings public, use:
`-P foo/htpasswd.txt -a update,download`.
To drop all authentications, use: `-P . -a .`.
Note that when uploads are not protected, the `register`
command is not necessary, but `~/.pypirc` still requires
username and password fields, even if bogus. By default,
only %(default)s is password-protected
''')
)
if self.parser_type == 'pypi-server':
# This argument is created by the `pypiserver-passlib` plugin
# for pypiserver>=2.0
security.add_argument(
'-P', '--passwords',
dest='password_file',

@ -1,44 +0,0 @@
"""Authentication based on an htpasswd file."""
from os import environ
from textwrap import dedent
from passlib.apache import HtpasswdFile
from .interface import AuthenticatorInterface
class HtpasswdAuthenticator(AuthenticatorInterface):
"""Authenticate using passlib and an htpasswd file."""
plugin_name = 'Htpasswd Authenticator'
plugin_help = 'Authenticate using an Apache htpasswd file'
def __init__(self, config):
"""Instantiate the authenticator."""
self.config = config
@classmethod
def update_parser(cls, parser):
"""Add htpasswd arguments to the config parser.
:param argparse.ArgumentParser parser: the config parser
"""
parser.add_argument(
'-P', '--passwords',
dest='password_file',
default=environ.get('PYPISERVER_PASSWORD_FILE'),
help=dedent('''\
use apache htpasswd file PASSWORD_FILE to set usernames &
passwords when authenticating certain actions (see -a option).
''')
)
def authenticate(self, request):
"""Authenticate the provided request."""
if (self.config.password_file is None or
self.config.password_file == '.'):
return True
pwd_file = HtpasswdFile(self.config.password_file)
pwd_file.load_if_changed()
return pwd_file.check_password(*request.auth)

@ -19,4 +19,4 @@ tox
twine>=1.7
virtualenv
webtest
wheel>=0.25.0
wheel>=0.25.0

@ -42,12 +42,12 @@ setup(
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
setup_requires=setup_requires,
extras_require={
'passlib': ['passlib>=1.6'],
'passlib': ['pypiserver-passlib'],
'cache': ['watchdog']
},
tests_require=tests_require,
url="https://github.com/pypiserver/pypiserver",
maintainer=("Kostis Anagnostopoulos <ankostis@gmail.com>"
maintainer=("Kostis Anagnostopoulos <ankostis@gmail.com> "
"Matthew Planchard <mplanchard@gmail.com>"),
maintainer_email="ankostis@gmail.com",
classifiers=[
@ -70,7 +70,8 @@ setup(
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Build Tools",
"Topic :: System :: Software Distribution"],
"Topic :: System :: Software Distribution"
],
zip_safe=True,
entry_points={
'paste.app_factory': ['main=pypiserver.paste:paste_app_factory'],
@ -79,9 +80,6 @@ setup(
'pypiserver=pypiserver.__main__:main',
],
'pypiserver.authenticators': [
'htpasswd = '
'pypiserver.plugins.authenticators.htpasswd:HtpasswdAuthenticator '
'[passlib]',
'no-auth = '
'pypiserver.plugins.authenticators.no_auth:NoAuthAuthenticator'
]

@ -2,7 +2,7 @@
import sys
from contextlib import contextmanager
from os import chdir, getcwd, environ, path, remove
from os import chdir, getcwd, environ, listdir, path, remove
from shutil import copy2, rmtree
from subprocess import PIPE, Popen
from tempfile import mkdtemp
@ -52,35 +52,52 @@ def activate_venv(venv_dir):
environ['VIRTUAL_ENV'] = start_venv
def pypiserver_cmd(venv_dir, root, *args):
def pypiserver_cmd(root, *args):
"""Yield a command to run pypiserver.
:param str exc: the path to the python executable to use in
running pypiserver.
:param args: extra arguments for ``pypiserver run``
"""
yield '{}/bin/pypiserver'.format(venv_dir)
# yield '{}/bin/pypiserver'.format(venv_dir)
yield 'pypiserver'
yield 'run'
yield root
for arg in args:
yield arg
def pip_cmd(venv_dir, *args):
def pip_cmd(*args):
"""Yield a command to run pip.
:param str bindir: the path to the bin directory where the pip
command can be found.
:param args: extra arguments for ``pip``
"""
yield '{}/bin/pip'.format(venv_dir)
yield 'pip'
for arg in args:
yield arg
if 'install' in args or 'download' in args:
yield '--index-url'
if any(i in args for i in ('install', 'download', 'search')):
yield '-i'
yield 'http://localhost:8080'
def twine_cmd(*args):
"""Yield a command to run twine.
:param args: arguments for `twine`
"""
yield 'twine'
for arg in args:
yield arg
for part in ('--repository-url', 'http://localhost:8080'):
yield part
if '-u' not in args:
for part in ('-u', 'username'):
yield part
if '-p' not in args:
for part in ('-p', 'password'):
yield part
def run(args, raise_on_err=True, capture=False, **kwargs):
"""Straightforward implementation to run subprocesses.
@ -112,7 +129,6 @@ def venv():
venv_dir,
))
with activate_venv(venv_dir):
import ipdb; ipdb.set_trace()
run(
(
'python',
@ -161,9 +177,7 @@ class TestNoAuth:
"""Run pypiserver with no auth."""
pkg_root = mkdtemp()
with activate_venv(venv):
proc = Popen(pypiserver_cmd(
venv, pkg_root, '--auth-backend', 'no-auth'
), env=environ)
proc = Popen(pypiserver_cmd(pkg_root), env=environ)
yield pkg_root
proc.kill()
rmtree(pkg_root)
@ -182,10 +196,21 @@ class TestNoAuth:
yield
remove(path.join(pkg_root, SIMPLE_DEV_PKG))
@pytest.mark.usefixtures('venv_active')
def test_install(self, venv, simple_pkg):
@pytest.mark.usefixtures('venv_active', 'simple_pkg')
def test_install(self):
"""Test pulling a package with pip from the repo."""
run(('pip', 'install', 'simple_pkg'))
assert 'simple-pkg' in run(
('pip', 'freeze'), capture=True, env=environ
)
run(pip_cmd('install', 'simple_pkg'))
assert 'simple-pkg' in run(pip_cmd('freeze'), capture=True)
@pytest.mark.usefixtures('venv_active')
def test_upload(self, pkg_root):
"""Test putting a package into the rpeo."""
assert SIMPLE_PKG not in listdir(pkg_root)
run(twine_cmd('upload', SIMPLE_PKG_PATH))
assert SIMPLE_PKG in listdir(pkg_root)
@pytest.mark.usefixtures('venv_active', 'simple_pkg')
def test_search(self):
"""Test results of pip search."""
out = run(pip_cmd('search', 'simple_pkg'), capture=True)
assert 'simple_pkg' in out