diff --git a/tests/unit/macaroons/test_caveats.py b/tests/unit/macaroons/test_caveats.py index 052a6d9ede2a..5f0a24fbb2e5 100644 --- a/tests/unit/macaroons/test_caveats.py +++ b/tests/unit/macaroons/test_caveats.py @@ -11,13 +11,20 @@ # limitations under the License. import json +import time import pretend import pytest from pymacaroons.exceptions import MacaroonInvalidSignatureException -from warehouse.macaroons.caveats import Caveat, InvalidMacaroonError, V1Caveat, Verifier +from warehouse.macaroons.caveats import ( + Caveat, + ExpiryCaveat, + InvalidMacaroonError, + V1Caveat, + Verifier, +) from ...common.db.packaging import ProjectFactory @@ -95,6 +102,56 @@ def test_verify_project(self, db_request): assert caveat(json.dumps(predicate)) is True +class TestExpiryCaveat: + @pytest.mark.parametrize( + "predicate", + [ + # invalid JSON + "invalid json", + # missing nbf and exp + '{"missing": "values"}', + # nbf and exp present, but null + '{"nbf": null, "exp": null}', + # nbf and exp present, but empty + '{"nbf": "", "exp": ""}', + # valid JSON, but wrong type + "[]", + ], + ) + def test_verify_invalid_predicates(self, predicate): + verifier = pretend.stub() + caveat = ExpiryCaveat(verifier) + + assert not caveat(predicate) + + def test_verify_not_before(self): + verifier = pretend.stub() + caveat = ExpiryCaveat(verifier) + + not_before = int(time.time()) + 60 + expiry = not_before + 60 + predicate = json.dumps({"exp": expiry, "nbf": not_before}) + assert not caveat(predicate) + + def test_verify_already_expired(self): + verifier = pretend.stub() + caveat = ExpiryCaveat(verifier) + + not_before = int(time.time()) - 10 + expiry = not_before - 5 + predicate = json.dumps({"exp": expiry, "nbf": not_before}) + assert not caveat(predicate) + + def test_verify_ok(self): + verifier = pretend.stub() + caveat = ExpiryCaveat(verifier) + + not_before = int(time.time()) - 10 + expiry = int(time.time()) + 60 + predicate = json.dumps({"exp": expiry, "nbf": not_before}) + assert caveat(predicate) + + class TestVerifier: def test_creation(self): macaroon = pretend.stub() diff --git a/warehouse/macaroons/caveats.py b/warehouse/macaroons/caveats.py index accb743b8a3d..4edc765b01ba 100644 --- a/warehouse/macaroons/caveats.py +++ b/warehouse/macaroons/caveats.py @@ -11,6 +11,7 @@ # limitations under the License. import json +import time import pymacaroons @@ -73,6 +74,25 @@ def verify(self, predicate): return self.verify_projects(projects) +class ExpiryCaveat(Caveat): + def verify(self, predicate): + try: + data = json.loads(predicate) + expiry = data["exp"] + not_before = data["nbf"] + except (KeyError, ValueError, TypeError): + return False + + if not expiry or not not_before: + return False + + now = int(time.time()) + if now < not_before or now >= expiry: + return False + + return True + + class Verifier: def __init__(self, macaroon, context, principals, permission): self.macaroon = macaroon @@ -83,6 +103,7 @@ def __init__(self, macaroon, context, principals, permission): def verify(self, key): self.verifier.satisfy_general(V1Caveat(self)) + self.verifier.satisfy_general(ExpiryCaveat(self)) try: return self.verifier.verify(self.macaroon, key)