prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Add caveats to macaroons for expiration (time) and version
  **What's the problem this feature will solve?**
  
  This will allow further attenuating the permissions granted by an API key
  
  **Describe the solution you'd like**
  
  Addition of two addition types of caveat, project version (for uploads) and time (expiry).
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `warehouse/macaroons/caveats.py`
  Content:
  ```
  1 # Licensed under the Apache License, Version 2.0 (the "License");
  2 # you may not use this file except in compliance with the License.
  3 # You may obtain a copy of the License at
  4 #
  5 # http://www.apache.org/licenses/LICENSE-2.0
  6 #
  7 # Unless required by applicable law or agreed to in writing, software
  8 # distributed under the License is distributed on an "AS IS" BASIS,
  9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 # See the License for the specific language governing permissions and
  11 # limitations under the License.
  12 
  13 import json
  14 
  15 import pymacaroons
  16 
  17 from warehouse.packaging.models import Project
  18 
  19 
  20 class InvalidMacaroonError(Exception):
  21     ...
  22 
  23 
  24 class Caveat:
  25     def __init__(self, verifier):
  26         self.verifier = verifier
  27 
  28     def verify(self, predicate):
  29         raise InvalidMacaroonError
  30 
  31     def __call__(self, predicate):
  32         return self.verify(predicate)
  33 
  34 
  35 class V1Caveat(Caveat):
  36     def verify_projects(self, projects):
  37         # First, ensure that we're actually operating in
  38         # the context of a package.
  39         if not isinstance(self.verifier.context, Project):
  40             raise InvalidMacaroonError(
  41                 "project-scoped token used outside of a project context"
  42             )
  43 
  44         project = self.verifier.context
  45         if project.normalized_name in projects:
  46             return True
  47 
  48         raise InvalidMacaroonError(
  49             f"project-scoped token is not valid for project '{project.name}'"
  50         )
  51 
  52     def verify(self, predicate):
  53         try:
  54             data = json.loads(predicate)
  55         except ValueError:
  56             raise InvalidMacaroonError("malformatted predicate")
  57 
  58         if data.get("version") != 1:
  59             raise InvalidMacaroonError("invalidate version in predicate")
  60 
  61         permissions = data.get("permissions")
  62         if permissions is None:
  63             raise InvalidMacaroonError("invalid permissions in predicate")
  64 
  65         if permissions == "user":
  66             # User-scoped tokens behave exactly like a user's normal credentials.
  67             return True
  68 
  69         projects = permissions.get("projects")
  70         if projects is None:
  71             raise InvalidMacaroonError("invalid projects in predicate")
  72 
  73         return self.verify_projects(projects)
  74 
  75 
  76 class Verifier:
  77     def __init__(self, macaroon, context, principals, permission):
  78         self.macaroon = macaroon
  79         self.context = context
  80         self.principals = principals
  81         self.permission = permission
  82         self.verifier = pymacaroons.Verifier()
  83 
  84     def verify(self, key):
  85         self.verifier.satisfy_general(V1Caveat(self))
  86 
  87         try:
  88             return self.verifier.verify(self.macaroon, key)
  89         except (
  90             pymacaroons.exceptions.MacaroonInvalidSignatureException,
  91             Exception,  # https://github.com/ecordell/pymacaroons/issues/50
  92         ):
  93             raise InvalidMacaroonError("invalid macaroon signature")
  94
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/warehouse/macaroons/caveats.py b/warehouse/macaroons/caveats.py
  --- a/warehouse/macaroons/caveats.py
  +++ b/warehouse/macaroons/caveats.py
  @@ -11,6 +11,7 @@
   # limitations under the License.
   
   import json
  +import time
   
   import pymacaroons
   
  @@ -24,43 +25,51 @@
   class Caveat:
       def __init__(self, verifier):
           self.verifier = verifier
  +        # TODO: Surface this failure reason to the user.
  +        # See: https://github.com/pypa/warehouse/issues/9018
  +        self.failure_reason = None
   
  -    def verify(self, predicate):
  -        raise InvalidMacaroonError
  +    def verify(self, predicate) -> bool:
  +        return False
   
       def __call__(self, predicate):
           return self.verify(predicate)
   
   
   class V1Caveat(Caveat):
  -    def verify_projects(self, projects):
  +    def verify_projects(self, projects) -> bool:
           # First, ensure that we're actually operating in
           # the context of a package.
           if not isinstance(self.verifier.context, Project):
  -            raise InvalidMacaroonError(
  +            self.failure_reason = (
                   "project-scoped token used outside of a project context"
               )
  +            return False
   
           project = self.verifier.context
           if project.normalized_name in projects:
               return True
   
  -        raise InvalidMacaroonError(
  +        self.failure_reason = (
               f"project-scoped token is not valid for project '{project.name}'"
           )
  +        return False
   
  -    def verify(self, predicate):
  +    def verify(self, predicate) -> bool:
           try:
               data = json.loads(predicate)
           except ValueError:
  -            raise InvalidMacaroonError("malformatted predicate")
  +            self.failure_reason = "malformatted predicate"
  +            return False
   
           if data.get("version") != 1:
  -            raise InvalidMacaroonError("invalidate version in predicate")
  +            self.failure_reason = "invalid version in predicate"
  +            return False
   
           permissions = data.get("permissions")
           if permissions is None:
  -            raise InvalidMacaroonError("invalid permissions in predicate")
  +            self.failure_reason = "invalid permissions in predicate"
  +            return False
   
           if permissions == "user":
               # User-scoped tokens behave exactly like a user's normal credentials.
  @@ -68,11 +77,34 @@
   
           projects = permissions.get("projects")
           if projects is None:
  -            raise InvalidMacaroonError("invalid projects in predicate")
  +            self.failure_reason = "invalid projects in predicate"
  +            return False
   
           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):
  +            self.failure_reason = "malformatted predicate"
  +            return False
  +
  +        if not expiry or not not_before:
  +            self.failure_reason = "missing fields"
  +            return False
  +
  +        now = int(time.time())
  +        if now < not_before or now >= expiry:
  +            self.failure_reason = "token is expired"
  +            return False
  +
  +        return True
  +
  +
   class Verifier:
       def __init__(self, macaroon, context, principals, permission):
           self.macaroon = macaroon
  @@ -83,6 +115,7 @@
   
       def verify(self, key):
           self.verifier.satisfy_general(V1Caveat(self))
  +        self.verifier.satisfy_general(ExpiryCaveat(self))
   
           try:
               return self.verifier.verify(self.macaroon, key)
  @@ -90,4 +123,4 @@
               pymacaroons.exceptions.MacaroonInvalidSignatureException,
               Exception,  # https://github.com/ecordell/pymacaroons/issues/50
           ):
  -            raise InvalidMacaroonError("invalid macaroon signature")
  +            return False
  
verification_info: {"golden_diff": "diff --git a/warehouse/macaroons/caveats.py b/warehouse/macaroons/caveats.py\n--- a/warehouse/macaroons/caveats.py\n+++ b/warehouse/macaroons/caveats.py\n@@ -11,6 +11,7 @@\n # limitations under the License.\n \n import json\n+import time\n \n import pymacaroons\n \n@@ -24,43 +25,51 @@\n class Caveat:\n     def __init__(self, verifier):\n         self.verifier = verifier\n+        # TODO: Surface this failure reason to the user.\n+        # See: https://github.com/pypa/warehouse/issues/9018\n+        self.failure_reason = None\n \n-    def verify(self, predicate):\n-        raise InvalidMacaroonError\n+    def verify(self, predicate) -> bool:\n+        return False\n \n     def __call__(self, predicate):\n         return self.verify(predicate)\n \n \n class V1Caveat(Caveat):\n-    def verify_projects(self, projects):\n+    def verify_projects(self, projects) -> bool:\n         # First, ensure that we're actually operating in\n         # the context of a package.\n         if not isinstance(self.verifier.context, Project):\n-            raise InvalidMacaroonError(\n+            self.failure_reason = (\n                 \"project-scoped token used outside of a project context\"\n             )\n+            return False\n \n         project = self.verifier.context\n         if project.normalized_name in projects:\n             return True\n \n-        raise InvalidMacaroonError(\n+        self.failure_reason = (\n             f\"project-scoped token is not valid for project '{project.name}'\"\n         )\n+        return False\n \n-    def verify(self, predicate):\n+    def verify(self, predicate) -> bool:\n         try:\n             data = json.loads(predicate)\n         except ValueError:\n-            raise InvalidMacaroonError(\"malformatted predicate\")\n+            self.failure_reason = \"malformatted predicate\"\n+            return False\n \n         if data.get(\"version\") != 1:\n-            raise InvalidMacaroonError(\"invalidate version in predicate\")\n+            self.failure_reason = \"invalid version in predicate\"\n+            return False\n \n         permissions = data.get(\"permissions\")\n         if permissions is None:\n-            raise InvalidMacaroonError(\"invalid permissions in predicate\")\n+            self.failure_reason = \"invalid permissions in predicate\"\n+            return False\n \n         if permissions == \"user\":\n             # User-scoped tokens behave exactly like a user's normal credentials.\n@@ -68,11 +77,34 @@\n \n         projects = permissions.get(\"projects\")\n         if projects is None:\n-            raise InvalidMacaroonError(\"invalid projects in predicate\")\n+            self.failure_reason = \"invalid projects in predicate\"\n+            return False\n \n         return self.verify_projects(projects)\n \n \n+class ExpiryCaveat(Caveat):\n+    def verify(self, predicate):\n+        try:\n+            data = json.loads(predicate)\n+            expiry = data[\"exp\"]\n+            not_before = data[\"nbf\"]\n+        except (KeyError, ValueError, TypeError):\n+            self.failure_reason = \"malformatted predicate\"\n+            return False\n+\n+        if not expiry or not not_before:\n+            self.failure_reason = \"missing fields\"\n+            return False\n+\n+        now = int(time.time())\n+        if now < not_before or now >= expiry:\n+            self.failure_reason = \"token is expired\"\n+            return False\n+\n+        return True\n+\n+\n class Verifier:\n     def __init__(self, macaroon, context, principals, permission):\n         self.macaroon = macaroon\n@@ -83,6 +115,7 @@\n \n     def verify(self, key):\n         self.verifier.satisfy_general(V1Caveat(self))\n+        self.verifier.satisfy_general(ExpiryCaveat(self))\n \n         try:\n             return self.verifier.verify(self.macaroon, key)\n@@ -90,4 +123,4 @@\n             pymacaroons.exceptions.MacaroonInvalidSignatureException,\n             Exception,  # https://github.com/ecordell/pymacaroons/issues/50\n         ):\n-            raise InvalidMacaroonError(\"invalid macaroon signature\")\n+            return False\n", "issue": "Add caveats to macaroons for expiration (time) and version\n**What's the problem this feature will solve?**\r\n\r\nThis will allow further attenuating the permissions granted by an API key\r\n\r\n**Describe the solution you'd like**\r\n\r\nAddition of two addition types of caveat, project version (for uploads) and time (expiry).\r\n\n", "before_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport pymacaroons\n\nfrom warehouse.packaging.models import Project\n\n\nclass InvalidMacaroonError(Exception):\n    ...\n\n\nclass Caveat:\n    def __init__(self, verifier):\n        self.verifier = verifier\n\n    def verify(self, predicate):\n        raise InvalidMacaroonError\n\n    def __call__(self, predicate):\n        return self.verify(predicate)\n\n\nclass V1Caveat(Caveat):\n    def verify_projects(self, projects):\n        # First, ensure that we're actually operating in\n        # the context of a package.\n        if not isinstance(self.verifier.context, Project):\n            raise InvalidMacaroonError(\n                \"project-scoped token used outside of a project context\"\n            )\n\n        project = self.verifier.context\n        if project.normalized_name in projects:\n            return True\n\n        raise InvalidMacaroonError(\n            f\"project-scoped token is not valid for project '{project.name}'\"\n        )\n\n    def verify(self, predicate):\n        try:\n            data = json.loads(predicate)\n        except ValueError:\n            raise InvalidMacaroonError(\"malformatted predicate\")\n\n        if data.get(\"version\") != 1:\n            raise InvalidMacaroonError(\"invalidate version in predicate\")\n\n        permissions = data.get(\"permissions\")\n        if permissions is None:\n            raise InvalidMacaroonError(\"invalid permissions in predicate\")\n\n        if permissions == \"user\":\n            # User-scoped tokens behave exactly like a user's normal credentials.\n            return True\n\n        projects = permissions.get(\"projects\")\n        if projects is None:\n            raise InvalidMacaroonError(\"invalid projects in predicate\")\n\n        return self.verify_projects(projects)\n\n\nclass Verifier:\n    def __init__(self, macaroon, context, principals, permission):\n        self.macaroon = macaroon\n        self.context = context\n        self.principals = principals\n        self.permission = permission\n        self.verifier = pymacaroons.Verifier()\n\n    def verify(self, key):\n        self.verifier.satisfy_general(V1Caveat(self))\n\n        try:\n            return self.verifier.verify(self.macaroon, key)\n        except (\n            pymacaroons.exceptions.MacaroonInvalidSignatureException,\n            Exception,  # https://github.com/ecordell/pymacaroons/issues/50\n        ):\n            raise InvalidMacaroonError(\"invalid macaroon signature\")\n", "path": "warehouse/macaroons/caveats.py"}], "after_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport time\n\nimport pymacaroons\n\nfrom warehouse.packaging.models import Project\n\n\nclass InvalidMacaroonError(Exception):\n    ...\n\n\nclass Caveat:\n    def __init__(self, verifier):\n        self.verifier = verifier\n        # TODO: Surface this failure reason to the user.\n        # See: https://github.com/pypa/warehouse/issues/9018\n        self.failure_reason = None\n\n    def verify(self, predicate) -> bool:\n        return False\n\n    def __call__(self, predicate):\n        return self.verify(predicate)\n\n\nclass V1Caveat(Caveat):\n    def verify_projects(self, projects) -> bool:\n        # First, ensure that we're actually operating in\n        # the context of a package.\n        if not isinstance(self.verifier.context, Project):\n            self.failure_reason = (\n                \"project-scoped token used outside of a project context\"\n            )\n            return False\n\n        project = self.verifier.context\n        if project.normalized_name in projects:\n            return True\n\n        self.failure_reason = (\n            f\"project-scoped token is not valid for project '{project.name}'\"\n        )\n        return False\n\n    def verify(self, predicate) -> bool:\n        try:\n            data = json.loads(predicate)\n        except ValueError:\n            self.failure_reason = \"malformatted predicate\"\n            return False\n\n        if data.get(\"version\") != 1:\n            self.failure_reason = \"invalid version in predicate\"\n            return False\n\n        permissions = data.get(\"permissions\")\n        if permissions is None:\n            self.failure_reason = \"invalid permissions in predicate\"\n            return False\n\n        if permissions == \"user\":\n            # User-scoped tokens behave exactly like a user's normal credentials.\n            return True\n\n        projects = permissions.get(\"projects\")\n        if projects is None:\n            self.failure_reason = \"invalid projects in predicate\"\n            return False\n\n        return self.verify_projects(projects)\n\n\nclass ExpiryCaveat(Caveat):\n    def verify(self, predicate):\n        try:\n            data = json.loads(predicate)\n            expiry = data[\"exp\"]\n            not_before = data[\"nbf\"]\n        except (KeyError, ValueError, TypeError):\n            self.failure_reason = \"malformatted predicate\"\n            return False\n\n        if not expiry or not not_before:\n            self.failure_reason = \"missing fields\"\n            return False\n\n        now = int(time.time())\n        if now < not_before or now >= expiry:\n            self.failure_reason = \"token is expired\"\n            return False\n\n        return True\n\n\nclass Verifier:\n    def __init__(self, macaroon, context, principals, permission):\n        self.macaroon = macaroon\n        self.context = context\n        self.principals = principals\n        self.permission = permission\n        self.verifier = pymacaroons.Verifier()\n\n    def verify(self, key):\n        self.verifier.satisfy_general(V1Caveat(self))\n        self.verifier.satisfy_general(ExpiryCaveat(self))\n\n        try:\n            return self.verifier.verify(self.macaroon, key)\n        except (\n            pymacaroons.exceptions.MacaroonInvalidSignatureException,\n            Exception,  # https://github.com/ecordell/pymacaroons/issues/50\n        ):\n            return False\n", "path": "warehouse/macaroons/caveats.py"}]}