forked from github.com/pypiserver
Removed passlib to plugin, no-auth defaults, integration tests
This commit is contained in:
parent
84158ab881
commit
824c2dd24f
@ -148,6 +148,10 @@ class _PypiserverParser(ArgumentParser):
|
|||||||
parsed = super(_PypiserverParser, self).parse_args(
|
parsed = super(_PypiserverParser, self).parse_args(
|
||||||
args=args, namespace=namespace
|
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():
|
for attr, parser in self.extra_parsers.items():
|
||||||
if hasattr(parsed, attr):
|
if hasattr(parsed, attr):
|
||||||
setattr(parsed, attr, parser(getattr(parsed, attr)))
|
setattr(parsed, attr, parser(getattr(parsed, attr)))
|
||||||
@ -410,31 +414,42 @@ class Config(object):
|
|||||||
|
|
||||||
:param ArgumentParser parser: an ArgumentParser instance
|
:param ArgumentParser parser: an ArgumentParser instance
|
||||||
"""
|
"""
|
||||||
|
auth_plugins_available = bool(
|
||||||
|
set(self._plugins['authenticators']).difference(
|
||||||
|
set(('no-auth',))
|
||||||
|
)
|
||||||
|
)
|
||||||
security = parser.add_argument_group(
|
security = parser.add_argument_group(
|
||||||
title='Security',
|
title='Security',
|
||||||
description='Configure pypiserver access controls'
|
description='Configure pypiserver access controls'
|
||||||
)
|
)
|
||||||
# TODO: pull some of this long stuff out into an epilog
|
if auth_plugins_available or self.parser_type == 'pypi-server':
|
||||||
security.add_argument(
|
# Do not bother to show authentication arguments when no
|
||||||
'-a', '--authenticate',
|
# non-dummy auth plugins are installed.
|
||||||
default=environ.get(
|
security.add_argument(
|
||||||
'PYPISERVER_AUTHENTICATE',
|
'-a', '--authenticate',
|
||||||
_Defaults.authenticate,
|
default=environ.get(
|
||||||
),
|
'PYPISERVER_AUTHENTICATE',
|
||||||
help=dedent('''\
|
_Defaults.authenticate,
|
||||||
comma-separated list of (case-insensitive) actions to
|
),
|
||||||
authenticate. Use "." for no authentication. Requires the
|
# TODO: pull some of this long stuff out into an epilog
|
||||||
password (-P option) to be set. For example to password-protect
|
help=dedent('''\
|
||||||
package downloads (in addition to uploads), while leaving
|
comma-separated list of (case-insensitive) actions to
|
||||||
listings public, use: `-P foo/htpasswd.txt` -a update,download
|
authenticate. Use "." for no authentication. Requires the
|
||||||
To drop all authentications, use: `-P . -a `.
|
password (-P option) to be set. For example to
|
||||||
Note that when uploads are not protected, the `register`
|
password-protect package downloads (in addition to
|
||||||
command is not necessary, but `~/.pypirc` still requires
|
uploads), while leaving listings public, use:
|
||||||
username and password fields, even if bogus. By default,
|
`-P foo/htpasswd.txt -a update,download`.
|
||||||
only %(default)s is password-protected
|
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':
|
if self.parser_type == 'pypi-server':
|
||||||
|
# This argument is created by the `pypiserver-passlib` plugin
|
||||||
|
# for pypiserver>=2.0
|
||||||
security.add_argument(
|
security.add_argument(
|
||||||
'-P', '--passwords',
|
'-P', '--passwords',
|
||||||
dest='password_file',
|
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
|
twine>=1.7
|
||||||
virtualenv
|
virtualenv
|
||||||
webtest
|
webtest
|
||||||
wheel>=0.25.0
|
wheel>=0.25.0
|
||||||
|
10
setup.py
10
setup.py
@ -42,12 +42,12 @@ setup(
|
|||||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||||
setup_requires=setup_requires,
|
setup_requires=setup_requires,
|
||||||
extras_require={
|
extras_require={
|
||||||
'passlib': ['passlib>=1.6'],
|
'passlib': ['pypiserver-passlib'],
|
||||||
'cache': ['watchdog']
|
'cache': ['watchdog']
|
||||||
},
|
},
|
||||||
tests_require=tests_require,
|
tests_require=tests_require,
|
||||||
url="https://github.com/pypiserver/pypiserver",
|
url="https://github.com/pypiserver/pypiserver",
|
||||||
maintainer=("Kostis Anagnostopoulos <ankostis@gmail.com>"
|
maintainer=("Kostis Anagnostopoulos <ankostis@gmail.com> "
|
||||||
"Matthew Planchard <mplanchard@gmail.com>"),
|
"Matthew Planchard <mplanchard@gmail.com>"),
|
||||||
maintainer_email="ankostis@gmail.com",
|
maintainer_email="ankostis@gmail.com",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
@ -70,7 +70,8 @@ setup(
|
|||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
"Topic :: Software Development :: Build Tools",
|
"Topic :: Software Development :: Build Tools",
|
||||||
"Topic :: System :: Software Distribution"],
|
"Topic :: System :: Software Distribution"
|
||||||
|
],
|
||||||
zip_safe=True,
|
zip_safe=True,
|
||||||
entry_points={
|
entry_points={
|
||||||
'paste.app_factory': ['main=pypiserver.paste:paste_app_factory'],
|
'paste.app_factory': ['main=pypiserver.paste:paste_app_factory'],
|
||||||
@ -79,9 +80,6 @@ setup(
|
|||||||
'pypiserver=pypiserver.__main__:main',
|
'pypiserver=pypiserver.__main__:main',
|
||||||
],
|
],
|
||||||
'pypiserver.authenticators': [
|
'pypiserver.authenticators': [
|
||||||
'htpasswd = '
|
|
||||||
'pypiserver.plugins.authenticators.htpasswd:HtpasswdAuthenticator '
|
|
||||||
'[passlib]',
|
|
||||||
'no-auth = '
|
'no-auth = '
|
||||||
'pypiserver.plugins.authenticators.no_auth:NoAuthAuthenticator'
|
'pypiserver.plugins.authenticators.no_auth:NoAuthAuthenticator'
|
||||||
]
|
]
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
from contextlib import contextmanager
|
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 shutil import copy2, rmtree
|
||||||
from subprocess import PIPE, Popen
|
from subprocess import PIPE, Popen
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
@ -52,35 +52,52 @@ def activate_venv(venv_dir):
|
|||||||
environ['VIRTUAL_ENV'] = start_venv
|
environ['VIRTUAL_ENV'] = start_venv
|
||||||
|
|
||||||
|
|
||||||
def pypiserver_cmd(venv_dir, root, *args):
|
def pypiserver_cmd(root, *args):
|
||||||
"""Yield a command to run pypiserver.
|
"""Yield a command to run pypiserver.
|
||||||
|
|
||||||
:param str exc: the path to the python executable to use in
|
:param str exc: the path to the python executable to use in
|
||||||
running pypiserver.
|
running pypiserver.
|
||||||
:param args: extra arguments for ``pypiserver run``
|
:param args: extra arguments for ``pypiserver run``
|
||||||
"""
|
"""
|
||||||
yield '{}/bin/pypiserver'.format(venv_dir)
|
# yield '{}/bin/pypiserver'.format(venv_dir)
|
||||||
|
yield 'pypiserver'
|
||||||
yield 'run'
|
yield 'run'
|
||||||
yield root
|
yield root
|
||||||
for arg in args:
|
for arg in args:
|
||||||
yield arg
|
yield arg
|
||||||
|
|
||||||
|
|
||||||
def pip_cmd(venv_dir, *args):
|
def pip_cmd(*args):
|
||||||
"""Yield a command to run pip.
|
"""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``
|
:param args: extra arguments for ``pip``
|
||||||
"""
|
"""
|
||||||
yield '{}/bin/pip'.format(venv_dir)
|
yield 'pip'
|
||||||
for arg in args:
|
for arg in args:
|
||||||
yield arg
|
yield arg
|
||||||
if 'install' in args or 'download' in args:
|
if any(i in args for i in ('install', 'download', 'search')):
|
||||||
yield '--index-url'
|
yield '-i'
|
||||||
yield 'http://localhost:8080'
|
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):
|
def run(args, raise_on_err=True, capture=False, **kwargs):
|
||||||
"""Straightforward implementation to run subprocesses.
|
"""Straightforward implementation to run subprocesses.
|
||||||
|
|
||||||
@ -112,7 +129,6 @@ def venv():
|
|||||||
venv_dir,
|
venv_dir,
|
||||||
))
|
))
|
||||||
with activate_venv(venv_dir):
|
with activate_venv(venv_dir):
|
||||||
import ipdb; ipdb.set_trace()
|
|
||||||
run(
|
run(
|
||||||
(
|
(
|
||||||
'python',
|
'python',
|
||||||
@ -161,9 +177,7 @@ class TestNoAuth:
|
|||||||
"""Run pypiserver with no auth."""
|
"""Run pypiserver with no auth."""
|
||||||
pkg_root = mkdtemp()
|
pkg_root = mkdtemp()
|
||||||
with activate_venv(venv):
|
with activate_venv(venv):
|
||||||
proc = Popen(pypiserver_cmd(
|
proc = Popen(pypiserver_cmd(pkg_root), env=environ)
|
||||||
venv, pkg_root, '--auth-backend', 'no-auth'
|
|
||||||
), env=environ)
|
|
||||||
yield pkg_root
|
yield pkg_root
|
||||||
proc.kill()
|
proc.kill()
|
||||||
rmtree(pkg_root)
|
rmtree(pkg_root)
|
||||||
@ -182,10 +196,21 @@ class TestNoAuth:
|
|||||||
yield
|
yield
|
||||||
remove(path.join(pkg_root, SIMPLE_DEV_PKG))
|
remove(path.join(pkg_root, SIMPLE_DEV_PKG))
|
||||||
|
|
||||||
@pytest.mark.usefixtures('venv_active')
|
@pytest.mark.usefixtures('venv_active', 'simple_pkg')
|
||||||
def test_install(self, venv, simple_pkg):
|
def test_install(self):
|
||||||
"""Test pulling a package with pip from the repo."""
|
"""Test pulling a package with pip from the repo."""
|
||||||
run(('pip', 'install', 'simple_pkg'))
|
run(pip_cmd('install', 'simple_pkg'))
|
||||||
assert 'simple-pkg' in run(
|
assert 'simple-pkg' in run(pip_cmd('freeze'), capture=True)
|
||||||
('pip', 'freeze'), capture=True, env=environ
|
|
||||||
)
|
@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
|
||||||
|
Loading…
Reference in New Issue
Block a user