mirror of
https://github.com/pypiserver/pypiserver
synced 2024-11-09 16:45:51 +01:00
Merge pull request #66 from koodaamo/auth_improvements
Merge Auth improvements (directly on github).
This commit is contained in:
commit
202c26fb75
18
README.rst
18
README.rst
@ -88,9 +88,16 @@ pypi-server -h will print a detailed usage message::
|
|||||||
-i INTERFACE, --interface INTERFACE
|
-i INTERFACE, --interface INTERFACE
|
||||||
listen on interface INTERFACE (default: 0.0.0.0, any interface)
|
listen on interface INTERFACE (default: 0.0.0.0, any interface)
|
||||||
|
|
||||||
|
-a (update|download|list), ... --authenticate (update|download|list), ...
|
||||||
|
comma-separated list of actions to authenticate (requires giving also
|
||||||
|
the -P option). For example to password-protect package uploads and
|
||||||
|
downloads while leaving listings public, give: -a update,download.
|
||||||
|
Note: make sure there is no space around the comma(s); otherwise, an
|
||||||
|
error will occur.
|
||||||
|
|
||||||
-P PASSWORD_FILE, --passwords PASSWORD_FILE
|
-P PASSWORD_FILE, --passwords PASSWORD_FILE
|
||||||
use apache htpasswd file PASSWORD_FILE in order to enable password
|
use apache htpasswd file PASSWORD_FILE to set usernames & passwords
|
||||||
protected uploads.
|
used for authentication (requires giving the -s option as well).
|
||||||
|
|
||||||
--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
|
||||||
@ -468,6 +475,13 @@ proxypypi (https://pypi.python.org/pypi/proxypypi)
|
|||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
1.1.7 (2015-01-10)
|
||||||
|
------------------
|
||||||
|
- support password protected package listings and downloads,
|
||||||
|
in addition to uploads (use the -a, --authenticate option
|
||||||
|
to specify which to protect).
|
||||||
|
|
||||||
1.1.6 (2014-03-05)
|
1.1.6 (2014-03-05)
|
||||||
------------------
|
------------------
|
||||||
- remove --index-url cli parameter introduced in 1.1.5
|
- remove --index-url cli parameter introduced in 1.1.5
|
||||||
|
@ -5,6 +5,7 @@ version = __version__ = "1.1.6"
|
|||||||
def app(root=None,
|
def app(root=None,
|
||||||
redirect_to_fallback=True,
|
redirect_to_fallback=True,
|
||||||
fallback_url=None,
|
fallback_url=None,
|
||||||
|
authenticated=[],
|
||||||
password_file=None,
|
password_file=None,
|
||||||
overwrite=False,
|
overwrite=False,
|
||||||
log_req_frmt="%(bottle.request)s",
|
log_req_frmt="%(bottle.request)s",
|
||||||
@ -25,7 +26,7 @@ def app(root=None,
|
|||||||
fallback_url = "http://pypi.python.org/simple"
|
fallback_url = "http://pypi.python.org/simple"
|
||||||
|
|
||||||
_app.configure(root=root, redirect_to_fallback=redirect_to_fallback, fallback_url=fallback_url,
|
_app.configure(root=root, redirect_to_fallback=redirect_to_fallback, fallback_url=fallback_url,
|
||||||
password_file=password_file, overwrite=overwrite,
|
authenticated=authenticated, password_file=password_file, overwrite=overwrite,
|
||||||
log_req_frmt=log_req_frmt, log_res_frmt=log_res_frmt, log_err_frmt=log_err_frmt)
|
log_req_frmt=log_req_frmt, log_res_frmt=log_res_frmt, log_err_frmt=log_err_frmt)
|
||||||
_app.app.module = _app
|
_app.app.module = _app
|
||||||
|
|
||||||
|
@ -33,9 +33,29 @@ def validate_user(username, password):
|
|||||||
return config.htpasswdfile.check_password(username, password)
|
return config.htpasswdfile.check_password(username, password)
|
||||||
|
|
||||||
|
|
||||||
|
class auth(object):
|
||||||
|
"decorator to apply authentication if specified for the decorated method & action"
|
||||||
|
|
||||||
|
def __init__(self, action):
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
def __call__(self, method):
|
||||||
|
|
||||||
|
def protector(*args, **kwargs):
|
||||||
|
if self.action in config.authenticated:
|
||||||
|
if not request.auth or request.auth[1] is None:
|
||||||
|
raise HTTPError(401, header={"WWW-Authenticate": 'Basic realm="pypi"'})
|
||||||
|
if not validate_user(*request.auth):
|
||||||
|
raise HTTPError(403)
|
||||||
|
return method(*args, **kwargs)
|
||||||
|
|
||||||
|
return protector
|
||||||
|
|
||||||
|
|
||||||
def configure(root=None,
|
def configure(root=None,
|
||||||
redirect_to_fallback=True,
|
redirect_to_fallback=True,
|
||||||
fallback_url=None,
|
fallback_url=None,
|
||||||
|
authenticated=[],
|
||||||
password_file=None,
|
password_file=None,
|
||||||
overwrite=False,
|
overwrite=False,
|
||||||
log_req_frmt=None,
|
log_req_frmt=None,
|
||||||
@ -46,12 +66,15 @@ def configure(root=None,
|
|||||||
log.info("Starting(%s)", dict(root=root,
|
log.info("Starting(%s)", dict(root=root,
|
||||||
redirect_to_fallback=redirect_to_fallback,
|
redirect_to_fallback=redirect_to_fallback,
|
||||||
fallback_url=fallback_url,
|
fallback_url=fallback_url,
|
||||||
|
authenticated=authenticated,
|
||||||
password_file=password_file,
|
password_file=password_file,
|
||||||
overwrite=overwrite,
|
overwrite=overwrite,
|
||||||
log_req_frmt=log_req_frmt,
|
log_req_frmt=log_req_frmt,
|
||||||
log_res_frmt=log_res_frmt,
|
log_res_frmt=log_res_frmt,
|
||||||
log_err_frmt=log_err_frmt))
|
log_err_frmt=log_err_frmt))
|
||||||
|
|
||||||
|
config.authenticated = authenticated
|
||||||
|
|
||||||
if root is None:
|
if root is None:
|
||||||
root = os.path.expanduser("~/packages")
|
root = os.path.expanduser("~/packages")
|
||||||
|
|
||||||
@ -146,13 +169,8 @@ easy_install -i %(URL)ssimple/ PACKAGE
|
|||||||
|
|
||||||
|
|
||||||
@app.post('/')
|
@app.post('/')
|
||||||
|
@auth("update")
|
||||||
def update():
|
def update():
|
||||||
if not request.auth or request.auth[1] is None:
|
|
||||||
raise HTTPError(401, header={"WWW-Authenticate": 'Basic realm="pypi"'})
|
|
||||||
|
|
||||||
if not validate_user(*request.auth):
|
|
||||||
raise HTTPError(403)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
action = request.forms[':action']
|
action = request.forms[':action']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -208,11 +226,13 @@ def update():
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/simple")
|
@app.route("/simple")
|
||||||
|
@auth("list")
|
||||||
def simpleindex_redirect():
|
def simpleindex_redirect():
|
||||||
return redirect(request.fullpath + "/")
|
return redirect(request.fullpath + "/")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/simple/")
|
@app.route("/simple/")
|
||||||
|
@auth("list")
|
||||||
def simpleindex():
|
def simpleindex():
|
||||||
res = ["<html><head><title>Simple Index</title></head><body>\n"]
|
res = ["<html><head><title>Simple Index</title></head><body>\n"]
|
||||||
for x in sorted(get_prefixes(packages())):
|
for x in sorted(get_prefixes(packages())):
|
||||||
@ -223,6 +243,7 @@ def simpleindex():
|
|||||||
|
|
||||||
@app.route("/simple/:prefix")
|
@app.route("/simple/:prefix")
|
||||||
@app.route("/simple/:prefix/")
|
@app.route("/simple/:prefix/")
|
||||||
|
@auth("list")
|
||||||
def simple(prefix=""):
|
def simple(prefix=""):
|
||||||
fp = request.fullpath
|
fp = request.fullpath
|
||||||
if not fp.endswith("/"):
|
if not fp.endswith("/"):
|
||||||
@ -246,6 +267,7 @@ def simple(prefix=""):
|
|||||||
|
|
||||||
@app.route('/packages')
|
@app.route('/packages')
|
||||||
@app.route('/packages/')
|
@app.route('/packages/')
|
||||||
|
@auth("list")
|
||||||
def list_packages():
|
def list_packages():
|
||||||
fp = request.fullpath
|
fp = request.fullpath
|
||||||
if not fp.endswith("/"):
|
if not fp.endswith("/"):
|
||||||
@ -262,6 +284,7 @@ def list_packages():
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/packages/:filename#.*#')
|
@app.route('/packages/:filename#.*#')
|
||||||
|
@auth("download")
|
||||||
def server_static(filename):
|
def server_static(filename):
|
||||||
entries = find_packages(packages())
|
entries = find_packages(packages())
|
||||||
for x in entries:
|
for x in entries:
|
||||||
|
@ -189,9 +189,16 @@ pypi-server understands the following options:
|
|||||||
-i INTERFACE, --interface INTERFACE
|
-i INTERFACE, --interface INTERFACE
|
||||||
listen on interface INTERFACE (default: 0.0.0.0, any interface)
|
listen on interface INTERFACE (default: 0.0.0.0, any interface)
|
||||||
|
|
||||||
|
-a (update|download|list), ... --authenticate (update|download|list), ...
|
||||||
|
comma-separated list of actions to authenticate (requires giving also
|
||||||
|
the -P option). For example to password-protect package uploads and
|
||||||
|
downloads while leaving listings public, give: -a update,download.
|
||||||
|
Note: make sure there is no space around the comma(s); otherwise, an
|
||||||
|
error will occur.
|
||||||
|
|
||||||
-P PASSWORD_FILE, --passwords PASSWORD_FILE
|
-P PASSWORD_FILE, --passwords PASSWORD_FILE
|
||||||
use apache htpasswd file PASSWORD_FILE in order to enable password
|
use apache htpasswd file PASSWORD_FILE to set usernames & passwords
|
||||||
protected uploads.
|
used for authentication (requires giving the -s option as well).
|
||||||
|
|
||||||
--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
|
||||||
@ -275,6 +282,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 = []
|
||||||
password_file = None
|
password_file = None
|
||||||
overwrite = False
|
overwrite = False
|
||||||
verbosity = 1
|
verbosity = 1
|
||||||
@ -289,9 +297,10 @@ def main(argv=None):
|
|||||||
update_stable_only = True
|
update_stable_only = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, roots = getopt.getopt(argv[1:], "i:p:r:d:P:Uuvxoh", [
|
opts, roots = getopt.getopt(argv[1:], "i:p:a:r:d:P:Uuvxoh", [
|
||||||
"interface=",
|
"interface=",
|
||||||
"passwords=",
|
"passwords=",
|
||||||
|
"authenticate=",
|
||||||
"port=",
|
"port=",
|
||||||
"root=",
|
"root=",
|
||||||
"server=",
|
"server=",
|
||||||
@ -313,6 +322,13 @@ 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)
|
port = int(v)
|
||||||
|
elif k in ("-a", "--authenticate"):
|
||||||
|
authenticated = [a.strip() for a in v.strip(',').split(',')]
|
||||||
|
actions = ("list", "download", "update")
|
||||||
|
for a in authenticated:
|
||||||
|
if a not in actions:
|
||||||
|
errmsg = "Incorrect action '%s' given with option '%s'" % (a, k)
|
||||||
|
sys.exit(errmsg)
|
||||||
elif k in ("-i", "--interface"):
|
elif k in ("-i", "--interface"):
|
||||||
host = v
|
host = v
|
||||||
elif k in ("-r", "--root"):
|
elif k in ("-r", "--root"):
|
||||||
@ -357,6 +373,9 @@ def main(argv=None):
|
|||||||
usage()
|
usage()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
if (password_file or authenticated) and not (password_file and authenticated):
|
||||||
|
sys.exit("Must give both password file (-P) and actions to authenticate (-a).")
|
||||||
|
|
||||||
if len(roots) == 0:
|
if len(roots) == 0:
|
||||||
roots.append(os.path.expanduser("~/packages"))
|
roots.append(os.path.expanduser("~/packages"))
|
||||||
|
|
||||||
@ -375,6 +394,7 @@ def main(argv=None):
|
|||||||
a = app(
|
a = app(
|
||||||
root=roots,
|
root=roots,
|
||||||
redirect_to_fallback=redirect_to_fallback,
|
redirect_to_fallback=redirect_to_fallback,
|
||||||
|
authenticated=authenticated,
|
||||||
password_file=password_file,
|
password_file=password_file,
|
||||||
fallback_url=fallback_url,
|
fallback_url=fallback_url,
|
||||||
overwrite=overwrite,
|
overwrite=overwrite,
|
||||||
|
Loading…
Reference in New Issue
Block a user