Add matrix auth

This commit is contained in:
Kyle Hornberg 2016-04-29 07:35:46 -05:00
parent 51bf5ac961
commit 3d37c00999
5 changed files with 66 additions and 24 deletions

View File

@ -197,17 +197,33 @@ def main(argv=None):
err = sys.exc_info()[1]
sys.exit("Invalid port(%r) due to: %s" % (v, err))
elif k in ("-a", "--authenticate"):
c.authenticated = [a.lower()
for a in re.split("[, ]+", v.strip(" ,"))
if a]
if c.authenticated == ['.']:
c.authenticated = []
if '{' in v:
import ast
v = ast.literal_eval(v)
if isinstance(v, dict):
c.authenticated = {}
for user in v:
c.authenticated[user] = [a.lower() for a in v[user] if a]
if c.authenticated[user] == ['.']:
c.authenticated[user] = []
else:
actions = ("list", "download", "update")
for a in c.authenticated[user]:
if a not in actions:
errmsg = "Action '%s' for option `%s` not one of %s!"
sys.exit(errmsg % (a, k, actions))
else:
actions = ("list", "download", "update")
for a in c.authenticated:
if a not in actions:
errmsg = "Action '%s' for option `%s` not one of %s!"
sys.exit(errmsg % (a, k, actions))
c.authenticated = [a.lower()
for a in re.split("[, ]+", v.strip(" ,"))
if a]
if c.authenticated == ['.']:
c.authenticated = []
else:
actions = ("list", "download", "update")
for a in c.authenticated:
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"):
c.host = v
elif k in ("-r", "--root"):

View File

@ -42,17 +42,25 @@ class auth(object):
def __call__(self, method):
def protector(*args, **kwargs):
if self.action in config.authenticated:
if not request.auth or request.auth[1] is None:
auth = request.auth
if config.authenticated:
if not auth or auth[1] is None:
raise HTTPError(
401, headers={"WWW-Authenticate": 'Basic realm="pypi"'}
)
if not config.auther(*request.auth):
401, headers={"WWW-Authenticate": 'Basic realm="pypi"'})
if not config.auther(*auth):
raise HTTPError(403)
if self.authorized(auth):
return method(*args, **kwargs)
else:
raise HTTPError(403)
return method(*args, **kwargs)
return protector
def authorized(self, auth):
if isinstance(config.authenticated, dict):
return auth[0] in config.authenticated and self.action in config.authenticated[auth[0]]
return self.action in config.authenticated
@app.hook('before_request')
def log_request():

View File

@ -362,7 +362,7 @@ def test_no_cache_control_set(root, _app, testapp):
def test_cache_control_set(root):
from pypiserver import app
AGE = 86400
app_with_cache = webtest.TestApp(app(root=root.strpath, cache_control=AGE))
app_with_cache = webtest.TestApp(app(root=root.strpath, cache_control=AGE, authenticated=[]))
root.join("foo_bar-1.0.tar.gz").write("")
resp = app_with_cache.get("/packages/foo_bar-1.0.tar.gz")
assert "Cache-Control" in resp.headers
@ -381,8 +381,8 @@ def test_upload_badAction(root, testapp):
assert "Unsupported ':action' field: BAD" in hp.unescape(resp.text)
@pytest.mark.parametrize(("package"), [f[0]
for f in test_core.files
@pytest.mark.parametrize(("package"), [f[0]
for f in test_core.files
if f[1] and '/' not in f[0]])
def test_upload(package, root, testapp):
resp = testapp.post("/", params={':action': 'file_upload'},
@ -393,13 +393,13 @@ def test_upload(package, root, testapp):
assert uploaded_pkgs[0].lower() == package.lower()
@pytest.mark.parametrize(("package"), [f[0]
for f in test_core.files
@pytest.mark.parametrize(("package"), [f[0]
for f in test_core.files
if f[1] and '/' not in f[0]])
def test_upload_with_signature(package, root, testapp):
resp = testapp.post("/", params={':action': 'file_upload'},
upload_files=[
('content', package, b''),
('content', package, b''),
('gpg_signature', '%s.asc' % package, b'')])
assert resp.status_int == 200
uploaded_pkgs = [f.basename.lower() for f in root.listdir()]
@ -433,7 +433,7 @@ def test_remove_pkg_missingNaveVersion(name, version, root, testapp):
params = {':action': 'remove_pkg', 'name': name, 'version': version}
params = dict((k, v) for k,v in params.items() if v is not None)
resp = testapp.post("/", expect_errors=1, params=params)
assert resp.status == '400 Bad Request'
assert msg %(name, version) in hp.unescape(resp.text)

View File

@ -169,3 +169,21 @@ def test_dot_password_without_auth_list(main, monkeypatch):
main(["-P", ".", "-a", "."])
assert main.app.module.config.authenticated == []
def test_password_with_auth_list(main, monkeypatch):
monkeypatch.setitem(sys.modules, 'passlib', mock.MagicMock())
monkeypatch.setitem(sys.modules, 'passlib.apache', mock.MagicMock())
main(["-P", "pswd-file", "-a", "update, download"])
assert main.app.module.config.authenticated == ['update', 'download']
def test_password_with_auth_list_with_no_spaces(main, monkeypatch):
monkeypatch.setitem(sys.modules, 'passlib', mock.MagicMock())
monkeypatch.setitem(sys.modules, 'passlib.apache', mock.MagicMock())
main(["-P", "pswd-file", "-a", "update,download"])
assert main.app.module.config.authenticated == ['update', 'download']
def test_matrix_auth_list(main, monkeypatch):
monkeypatch.setitem(sys.modules, 'passlib', mock.MagicMock())
monkeypatch.setitem(sys.modules, 'passlib.apache', mock.MagicMock())
main(["-P", "pswd-file", "-a", "{'a': ['update', 'list'], 'b': ['download']}"])
assert main.app.module.config.authenticated == {'a': ['update', 'list'], 'b': ['download']}

View File

@ -43,7 +43,7 @@ Srv = namedtuple('Srv', ('proc', 'port', 'package'))
def _run_server(packdir, port, authed, other_cli=''):
pswd_opt_choices = {
True: "-Ptests/htpasswd.a.a -a update,download",
True: "-Ptests/htpasswd.a.a -a update,download,list",
False: "-P. -a."
}
pswd_opts = pswd_opt_choices[authed]