Dot means empty when specifying empty authed-ops (-a .).

+ FIX check of password/authentication-list options combinations.
+ doc: Update auth-ops list instructions.
+ Report bad port.
This commit is contained in:
Kostis Anagnostopoulos 2015-09-17 13:52:00 +02:00
parent d4d0463db6
commit cda0fad7a9
3 changed files with 66 additions and 39 deletions

@ -81,7 +81,7 @@ Currently only password-protected uploads are supported!
http://www.htaccesstools.com/htpasswd-generator/ http://www.htaccesstools.com/htpasswd-generator/
It is also possible to disable authentication even for uploads. It is also possible to disable authentication even for uploads.
Read the help for ``-P`` and ``-a`` options to see how it is done. To avoid lazy security decisions, read help for ``-P`` and ``-a`` options.
#. You need to restart the server with the `-P` option only once #. You need to restart the server with the `-P` option only once
(but user/password pairs can later be added or updated on the fly):: (but user/password pairs can later be added or updated on the fly)::
@ -93,13 +93,13 @@ Currently only password-protected uploads are supported!
[distutils] [distutils]
index-servers = index-servers =
pypi pypi
internal local
[pypi] [pypi]
username:<your_pypi_username> username:<your_pypi_username>
password:<your_pypi_passwd> password:<your_pypi_passwd>
[internal] [local]
repository: http://localhost:8080 repository: http://localhost:8080
username: <some_username> username: <some_username>
password: <some_passwd> password: <some_passwd>
@ -107,7 +107,10 @@ Currently only password-protected uploads are supported!
#. Then from within the directory of the python-project you wish to upload, #. Then from within the directory of the python-project you wish to upload,
issue this command:: issue this command::
python setup.py sdist upload -r internal python setup.py sdist upload -r local
#. (optional) Use `twine <https://pypi.python.org/pypi/twine>`_ library
to avoid storing and sending passwords in clear text.
Client-side configurations Client-side configurations
@ -224,20 +227,22 @@ Running ``pypi-server -h`` will print a detailed usage message::
-a, --authenticate (UPDATE|download|list), ... -a, --authenticate (UPDATE|download|list), ...
comma-separated list of (case-insensitive) actions to authenticate comma-separated list of (case-insensitive) actions to authenticate
Requires -P option and cannot not be empty unless -P is '.' Use '.' or '' for empty. Requires to have set the password (-P option).
For example to password-protect package downloads (in addition to uploads) For example to password-protect package downloads (in addition to uploads)
while leaving listings public, give: while leaving listings public, give:
-P foo/htpasswd.txt -a update,download -P foo/htpasswd.txt -a update,download
To drop all authentications, use: To drop all authentications, use:
-P . -a '' -P . -a .
Note that when uploads are not protected, the `register` command
is not necessary, but `~/.pypirc` still need username and password fields,
even if bogus.
By default, only 'update' is password-protected. By default, only 'update' is password-protected.
-P, --passwords PASSWORD_FILE -P, --passwords PASSWORD_FILE
use apache htpasswd file PASSWORD_FILE to set usernames & passwords use apache htpasswd file PASSWORD_FILE to set usernames & passwords when
used for authentication of certain actions (see -a option). authenticating certain actions (see -a option).
Set it explicitly to '.' to allow empty list of actions to authenticate; If you want to allow un-authorized access, set this option and -a
then no `register` command is neccessary explicitly to empty (either '.' or'').
(but `~/.pypirc` still need username and password fields, even if bogus).
--disable-fallback --disable-fallback
disable redirect to real PyPI index for packages not found in the disable redirect to real PyPI index for packages not found in the

@ -41,20 +41,22 @@ def usage():
-a, --authenticate (UPDATE|download|list), ... -a, --authenticate (UPDATE|download|list), ...
comma-separated list of (case-insensitive) actions to authenticate comma-separated list of (case-insensitive) actions to authenticate
Requires -P option and cannot not be empty unless -P is '.' Use '.' or '' for empty. Requires to have set the password (-P option).
For example to password-protect package downloads (in addition to uploads) For example to password-protect package downloads (in addition to uploads)
while leaving listings public, give: while leaving listings public, give:
-P foo/htpasswd.txt -a update,download -P foo/htpasswd.txt -a update,download
To drop all authentications, use: To drop all authentications, use:
-P . -a '' -P . -a .
Note that when uploads are not protected, the `register` command
is not necessary, but `~/.pypirc` still need username and password fields,
even if bogus.
By default, only 'update' is password-protected. By default, only 'update' is password-protected.
-P, --passwords PASSWORD_FILE -P, --passwords PASSWORD_FILE
use apache htpasswd file PASSWORD_FILE to set usernames & passwords use apache htpasswd file PASSWORD_FILE to set usernames & passwords when
used for authentication of certain actions (see -a option). authenticating certain actions (see -a option).
Set it explicitly to '.' to allow empty list of actions to authenticate; If you want to allow un-authorized access, set this option and -a
then no `register` command is neccessary explicitly to empty (either '.' or'').
(but `~/.pypirc` still need username and password fields, even if bogus).
--disable-fallback --disable-fallback
disable redirect to real PyPI index for packages not found in the disable redirect to real PyPI index for packages not found in the
@ -147,7 +149,7 @@ def main(argv=None):
server = DEFAULT_SERVER server = DEFAULT_SERVER
redirect_to_fallback = True redirect_to_fallback = True
fallback_url = "http://pypi.python.org/simple" fallback_url = "http://pypi.python.org/simple"
authenticated = ['update'] authed_ops_list = ['update']
password_file = None password_file = None
overwrite = False overwrite = False
verbosity = 1 verbosity = 1
@ -190,17 +192,22 @@ def main(argv=None):
for k, v in opts: for k, v in opts:
if k in ("-p", "--port"): if k in ("-p", "--port"):
port = int(v) try:
port = int(v)
except Exception as ex:
sys.exit("Invalid port(%r)!" % v)
elif k in ("-a", "--authenticate"): elif k in ("-a", "--authenticate"):
authenticated = [a.lower() authed_ops_list = [a.lower()
for a in re.split("[, ]+", v.strip(" ,")) for a in re.split("[, ]+", v.strip(" ,"))
if a] if a]
actions = ("list", "download", "update") if authed_ops_list == ['.']:
for a in authenticated: authed_ops_list = []
if a not in actions: else:
errmsg = "Action '%s' for option `%s` not one of %s!" % ( actions = ("list", "download", "update")
a, k, actions) for a in authed_ops_list:
sys.exit(errmsg) if a not in actions:
errmsg = "Action '%s' for option `%s` not one of %s!"
sys.exit(errmsg % (a, k, actions))
elif k in ("-i", "--interface"): elif k in ("-i", "--interface"):
host = v host = v
elif k in ("-r", "--root"): elif k in ("-r", "--root"):
@ -247,18 +254,19 @@ def main(argv=None):
print(usage()) print(usage())
sys.exit(0) sys.exit(0)
if password_file and password_file != '.' and not authenticated: if (not authed_ops_list and password_file != '.' or
sys.exit( authed_ops_list and password_file == '.'):
"Actions to authenticate (-a) must not be empty, unless password file (-P) is '.'!") auth_err = "When auth-ops-list is empty (-a=.), password-file (-P=%r) must also be empty ('.')!"
sys.exit(auth_err % password_file)
if len(roots) == 0: if len(roots) == 0:
roots.append(os.path.expanduser("~/packages")) roots.append(os.path.expanduser("~/packages"))
roots = [os.path.abspath(x) for x in roots] roots=[os.path.abspath(x) for x in roots]
verbose_levels = [ verbose_levels=[
logging.WARNING, logging.INFO, logging.DEBUG, logging.NOTSET] logging.WARNING, logging.INFO, logging.DEBUG, logging.NOTSET]
log_level = list(zip(verbose_levels, range(verbosity)))[-1][0] log_level=list(zip(verbose_levels, range(verbosity)))[-1][0]
init_logging(level=log_level, filename=log_file, frmt=log_frmt) init_logging(level=log_level, filename=log_file, frmt=log_frmt)
if command == "update": if command == "update":
@ -280,10 +288,10 @@ def main(argv=None):
server, ", ".join(server_names.keys()))) server, ", ".join(server_names.keys())))
from pypiserver import __version__, app from pypiserver import __version__, app
a = app( a=app(
root=roots, root=roots,
redirect_to_fallback=redirect_to_fallback, redirect_to_fallback=redirect_to_fallback,
authenticated=authenticated, authenticated=authed_ops_list,
password_file=password_file, password_file=password_file,
fallback_url=fallback_url, fallback_url=fallback_url,
overwrite=overwrite, overwrite=overwrite,

@ -119,9 +119,20 @@ def test_welcome_file_default(main):
def test_password_without_auth_list(main, monkeypatch): def test_password_without_auth_list(main, monkeypatch):
sysexit = mock.MagicMock(side_effect=ValueError('BINGO')) sysexit = mock.MagicMock(side_effect=ValueError('BINGO'))
monkeypatch.setattr('sys.exit', sysexit) monkeypatch.setattr('sys.exit', sysexit)
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as ex:
main(["-P", "pswd-file", "-a", ""]) main(["-P", "pswd-file", "-a", ""])
assert excinfo.value.args[0] == 'BINGO' assert ex.value.args[0] == 'BINGO'
with pytest.raises(ValueError) as ex:
main(["-a", "."])
assert ex.value.args[0] == 'BINGO'
with pytest.raises(ValueError) as ex:
main(["-a", ""])
assert ex.value.args[0] == 'BINGO'
with pytest.raises(ValueError) as ex:
main(["-P", "."])
assert ex.value.args[0] == 'BINGO'
def test_password_alone(main, monkeypatch): def test_password_alone(main, monkeypatch):
monkeypatch.setitem(sys.modules, 'passlib', mock.MagicMock()) monkeypatch.setitem(sys.modules, 'passlib', mock.MagicMock())
@ -132,3 +143,6 @@ def test_password_alone(main, monkeypatch):
def test_dot_password_without_auth_list(main, monkeypatch): def test_dot_password_without_auth_list(main, monkeypatch):
main(["-P", ".", "-a", ""]) main(["-P", ".", "-a", ""])
assert main.app.module.config.authenticated == [] assert main.app.module.config.authenticated == []
main(["-P", ".", "-a", "."])
assert main.app.module.config.authenticated == []