1
0
mirror of https://github.com/pypiserver/pypiserver synced 2024-11-09 16:45:51 +01:00

implement auth for listings and downloads

This commit is contained in:
Petri Savolainen 2015-01-09 10:59:56 +02:00
parent d0946ebbcf
commit ffd366fa3e
3 changed files with 51 additions and 11 deletions

@ -5,6 +5,7 @@ version = __version__ = "1.1.6"
def app(root=None,
redirect_to_fallback=True,
fallback_url=None,
authenticated=[],
password_file=None,
overwrite=False,
log_req_frmt="%(bottle.request)s",
@ -25,7 +26,7 @@ def app(root=None,
fallback_url = "http://pypi.python.org/simple"
_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)
_app.app.module = _app

@ -1,4 +1,4 @@
import sys, os, itertools, zipfile, mimetypes, logging
import sys, os, itertools, zipfile, mimetypes, logging, contextlib
try:
from io import BytesIO
@ -33,9 +33,29 @@ def validate_user(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,
redirect_to_fallback=True,
fallback_url=None,
authenticated=[],
password_file=None,
overwrite=False,
log_req_frmt=None,
@ -46,12 +66,15 @@ def configure(root=None,
log.info("Starting(%s)", dict(root=root,
redirect_to_fallback=redirect_to_fallback,
fallback_url=fallback_url,
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))
config.authenticated = authenticated
if root is None:
root = os.path.expanduser("~/packages")
@ -146,13 +169,8 @@ easy_install -i %(URL)ssimple/ PACKAGE
@app.post('/')
@auth("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:
action = request.forms[':action']
except KeyError:
@ -208,11 +226,13 @@ def update():
@app.route("/simple")
@auth("list")
def simpleindex_redirect():
return redirect(request.fullpath + "/")
@app.route("/simple/")
@auth("list")
def simpleindex():
res = ["<html><head><title>Simple Index</title></head><body>\n"]
for x in sorted(get_prefixes(packages())):
@ -223,6 +243,7 @@ def simpleindex():
@app.route("/simple/:prefix")
@app.route("/simple/:prefix/")
@auth("list")
def simple(prefix=""):
fp = request.fullpath
if not fp.endswith("/"):
@ -246,6 +267,7 @@ def simple(prefix=""):
@app.route('/packages')
@app.route('/packages/')
@auth("list")
def list_packages():
fp = request.fullpath
if not fp.endswith("/"):
@ -262,6 +284,7 @@ def list_packages():
@app.route('/packages/:filename#.*#')
@auth("download")
def server_static(filename):
entries = find_packages(packages())
for x in entries:

@ -189,9 +189,15 @@ pypi-server understands the following options:
-i INTERFACE, --interface 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). The "update" action sets up authentication of any
repository update given via python setup.py command, such as package
upload or removal.
-P PASSWORD_FILE, --passwords PASSWORD_FILE
use apache htpasswd file PASSWORD_FILE in order to enable password
protected uploads.
use apache htpasswd file PASSWORD_FILE to set usernames & passwords
used for authentication (requires giving the -s option as well).
--disable-fallback
disable redirect to real PyPI index for packages not found in the
@ -275,6 +281,7 @@ def main(argv=None):
server = DEFAULT_SERVER
redirect_to_fallback = True
fallback_url = "http://pypi.python.org/simple"
authenticated = []
password_file = None
overwrite = False
verbosity = 1
@ -289,9 +296,10 @@ def main(argv=None):
update_stable_only = True
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=",
"passwords=",
"authenticate=",
"port=",
"root=",
"server=",
@ -313,6 +321,13 @@ def main(argv=None):
for k, v in opts:
if k in ("-p", "--port"):
port = int(v)
elif k in ("-a", "--authenticate"):
authenticated = [a.strip() for a in v.split(',')]
actions = ("list", "download", "update")
for a in authenticated:
if a not in actions:
errmsg = "Incorrect action '%s' given with option '%s'" % (k, a)
sys.exit(errmsg)
elif k in ("-i", "--interface"):
host = v
elif k in ("-r", "--root"):
@ -375,6 +390,7 @@ def main(argv=None):
a = app(
root=roots,
redirect_to_fallback=redirect_to_fallback,
authenticated=authenticated,
password_file=password_file,
fallback_url=fallback_url,
overwrite=overwrite,