Skip to content

warehouse, tests: v2 caveats #10888

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 108 additions & 11 deletions tests/unit/macaroons/test_caveats.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@

from pymacaroons.exceptions import MacaroonInvalidSignatureException

from warehouse.macaroons.caveats import Caveat, InvalidMacaroonError, V1Caveat, Verifier
from warehouse.macaroons.caveats import (
Caveat,
InvalidMacaroonError,
TopLevelCaveat,
V1Caveat,
V2Caveat,
Verifier,
)

from ...common.db.packaging import ProjectFactory

Expand All @@ -36,14 +43,20 @@ def test_creation(self):

class TestV1Caveat:
@pytest.mark.parametrize(
["predicate", "result"],
"predicate",
[
("invalid json", False),
('{"version": 2}', False),
('{"permissions": null, "version": 1}', False),
# Wrong version
{"version": 2},
# Right version, missing permissions
{"version": 1},
# Right version, permissions are empty
{"permissions": None, "version": 1},
{"permissions": {}, "version": 1},
# Right version, missing projects list
{"permissions": {"projects": None}, "version": 1},
],
)
def test_verify_invalid_predicates(self, predicate, result):
def test_verify_invalid_predicates(self, predicate):
verifier = pretend.stub()
caveat = V1Caveat(verifier)

Expand All @@ -53,7 +66,7 @@ def test_verify_invalid_predicates(self, predicate, result):
def test_verify_valid_predicate(self):
verifier = pretend.stub()
caveat = V1Caveat(verifier)
predicate = '{"permissions": "user", "version": 1}'
predicate = {"permissions": "user", "version": 1}

assert caveat(predicate) is True

Expand All @@ -63,7 +76,7 @@ def test_verify_project_invalid_context(self):

predicate = {"version": 1, "permissions": {"projects": ["notfoobar"]}}
with pytest.raises(InvalidMacaroonError):
caveat(json.dumps(predicate))
caveat(predicate)

def test_verify_project_invalid_project_name(self, db_request):
project = ProjectFactory.create(name="foobar")
Expand All @@ -72,7 +85,7 @@ def test_verify_project_invalid_project_name(self, db_request):

predicate = {"version": 1, "permissions": {"projects": ["notfoobar"]}}
with pytest.raises(InvalidMacaroonError):
caveat(json.dumps(predicate))
caveat(predicate)

def test_verify_project_no_projects_object(self, db_request):
project = ProjectFactory.create(name="foobar")
Expand All @@ -84,15 +97,99 @@ def test_verify_project_no_projects_object(self, db_request):
"permissions": {"somethingthatisntprojects": ["blah"]},
}
with pytest.raises(InvalidMacaroonError):
caveat(json.dumps(predicate))
caveat(predicate)

def test_verify_project(self, db_request):
project = ProjectFactory.create(name="foobar")
verifier = pretend.stub(context=project)
caveat = V1Caveat(verifier)

predicate = {"version": 1, "permissions": {"projects": ["foobar"]}}
assert caveat(json.dumps(predicate)) is True
assert caveat(predicate) is True


class TestV2Caveat:
@pytest.mark.parametrize(
"predicate",
[
# Wrong version
{"version": 1},
# Right version, no contents
{"version": 2},
],
)
def test_verify_invalid_predicates(self, predicate):
verifier = pretend.stub()
caveat = V2Caveat(verifier)

with pytest.raises(InvalidMacaroonError):
caveat(predicate)

def test_verify(self):
verifier = pretend.stub()
caveat = V2Caveat(verifier)

# TODO: Nothing to test yet.
predicate = {"version": 2}
with pytest.raises(InvalidMacaroonError):
caveat(predicate)


class TestTopLevelCaveat:
@pytest.mark.parametrize(
"predicate",
[
# Completely invalid (missing payloads, invalid types, invalid JSON)
{},
'""',
[],
None,
"invalid json",
# Empty version
{"version": None},
# Unsupported versions
{"version": 0},
{"version": 3},
],
)
def test_verify_bad_versions(self, predicate):
verifier = pretend.stub()
caveat = TopLevelCaveat(verifier)

assert caveat.verifier is verifier

with pytest.raises(InvalidMacaroonError):
caveat(json.dumps(predicate))

def test_verify_dispatch_v1(self, monkeypatch):
verifier = pretend.stub()
caveat = TopLevelCaveat(verifier)

v1_verify = pretend.call_recorder(lambda self, predicate: True)
v2_verify = pretend.call_recorder(lambda self, predicate: True)
monkeypatch.setattr(V1Caveat, "verify", v1_verify)
monkeypatch.setattr(V2Caveat, "verify", v2_verify)

predicate = {"version": 1}
caveat(json.dumps(predicate))

assert len(v1_verify.calls) == 1
assert len(v2_verify.calls) == 0

def test_verify_dispatch_v2(self, monkeypatch):
verifier = pretend.stub()
caveat = TopLevelCaveat(verifier)

v1_verify = pretend.call_recorder(lambda self, predicate: True)
v2_verify = pretend.call_recorder(lambda self, predicate: True)
monkeypatch.setattr(V1Caveat, "verify", v1_verify)
monkeypatch.setattr(V2Caveat, "verify", v2_verify)

predicate = {"version": 2}
caveat(json.dumps(predicate))

assert len(v1_verify.calls) == 0
assert len(v2_verify.calls) == 1


class TestVerifier:
Expand Down
41 changes: 32 additions & 9 deletions warehouse/macaroons/caveats.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,11 @@ def verify_projects(self, projects):
)

def verify(self, predicate):
try:
data = json.loads(predicate)
except ValueError:
raise InvalidMacaroonError("malformatted predicate")
# Already checked in TopLevelCaveat, but just in case.
if predicate["version"] != 1:
raise InvalidMacaroonError("invalid version for this macaroon")

if data.get("version") != 1:
raise InvalidMacaroonError("invalidate version in predicate")

permissions = data.get("permissions")
permissions = predicate.get("permissions")
if permissions is None:
raise InvalidMacaroonError("invalid permissions in predicate")

Expand All @@ -73,6 +69,33 @@ def verify(self, predicate):
return self.verify_projects(projects)


class V2Caveat(Caveat):
def verify(self, predicate):
# Already checked in TopLevelCaveat, but just in case.
if predicate["version"] != 2:
raise InvalidMacaroonError("invalid version for this macaroon")

raise InvalidMacaroonError("not supported yet")


class TopLevelCaveat(Caveat):
def verify(self, predicate):
try:
data = json.loads(predicate)
version = data["version"]
except (ValueError, KeyError, TypeError):
raise InvalidMacaroonError("malformed predicate")

if version == 1:
caveat_verifier = V1Caveat(self.verifier)
elif version == 2:
caveat_verifier = V2Caveat(self.verifier)
else:
raise InvalidMacaroonError("invalid version: must be 1 or 2")

return caveat_verifier.verify(data)


class Verifier:
def __init__(self, macaroon, context, principals, permission):
self.macaroon = macaroon
Expand All @@ -82,7 +105,7 @@ def __init__(self, macaroon, context, principals, permission):
self.verifier = pymacaroons.Verifier()

def verify(self, key):
self.verifier.satisfy_general(V1Caveat(self))
self.verifier.satisfy_general(TopLevelCaveat(self))

try:
return self.verifier.verify(self.macaroon, key)
Expand Down