We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Change username & API token prefix, to make Travis auth easier
Just wanted to share a potential pain point for folks using the new PyPI API tokens for authentication via a travis.yaml. The @token username needs to be wrapped in quotes and the `:` after `pypi` needs to be escaped to work, otherwise you hit a 403 error (thanks for the help tracking this down @ewdurbin).

If you're using the environment variables through Travis' UI, the following works:
```
deploy:
  provider: pypi
  user: "@token"  # quotes required for travis
  # server: https://test.pypi.org/legacy/  # uncomment to do a test deploy
  password:
    secure: $PASSWORD  # stored in travis env var, with `:` after `pypi` escaped (pypi\:)
  on:
    branch: master
  skip_existing: true
```

If you're encrypting the token via the command line, you can just wrap your token in quotes:
```travis encrypt "<your-pypi_token>" --add deploy.password [--com]```

--- 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/services.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 datetime
14 import json
15 import uuid
16 
17 import pymacaroons
18 
19 from sqlalchemy.orm import joinedload
20 from sqlalchemy.orm.exc import NoResultFound
21 from zope.interface import implementer
22 
23 from warehouse.accounts.models import User
24 from warehouse.macaroons.caveats import InvalidMacaroon, Verifier
25 from warehouse.macaroons.interfaces import IMacaroonService
26 from warehouse.macaroons.models import Macaroon
27 
28 
29 @implementer(IMacaroonService)
30 class DatabaseMacaroonService:
31     def __init__(self, db_session):
32         self.db = db_session
33 
34     def _extract_raw_macaroon(self, raw_macaroon):
35         """
36         Returns the base64-encoded macaroon component of a PyPI macaroon,
37         dropping the prefix.
38 
39         Returns None if the macaroon is None, has no prefix, or has the
40         wrong prefix.
41         """
42         if raw_macaroon is None:
43             return None
44 
45         try:
46             prefix, raw_macaroon = raw_macaroon.split(":", 1)
47         except ValueError:
48             return None
49 
50         if prefix != "pypi":
51             return None
52 
53         return raw_macaroon
54 
55     def find_macaroon(self, macaroon_id):
56         """
57         Returns a macaroon model from the DB by its identifier.
58         Returns None if no macaroon has the given ID.
59         """
60         try:
61             dm = (
62                 self.db.query(Macaroon)
63                 .options(joinedload("user"))
64                 .filter(Macaroon.id == uuid.UUID(macaroon_id))
65                 .one()
66             )
67         except NoResultFound:
68             return None
69 
70         return dm
71 
72     def find_userid(self, raw_macaroon):
73         """
74         Returns the id of the user associated with the given raw (serialized)
75         macaroon.
76         """
77         raw_macaroon = self._extract_raw_macaroon(raw_macaroon)
78         if raw_macaroon is None:
79             return None
80 
81         m = pymacaroons.Macaroon.deserialize(raw_macaroon)
82         dm = self.find_macaroon(m.identifier.decode())
83 
84         if dm is None:
85             return None
86 
87         return dm.user.id
88 
89     def verify(self, raw_macaroon, context, principals, permission):
90         """
91         Returns True if the given raw (serialized) macaroon is
92         valid for the context, principals, and requested permission.
93 
94         Raises InvalidMacaroon if the macaroon is not valid.
95         """
96         raw_macaroon = self._extract_raw_macaroon(raw_macaroon)
97         if raw_macaroon is None:
98             raise InvalidMacaroon("malformed or nonexistent macaroon")
99 
100         m = pymacaroons.Macaroon.deserialize(raw_macaroon)
101         dm = self.find_macaroon(m.identifier.decode())
102 
103         if dm is None:
104             raise InvalidMacaroon("deleted or nonexistent macaroon")
105 
106         verifier = Verifier(m, context, principals, permission)
107         if verifier.verify(dm.key):
108             dm.last_used = datetime.datetime.now()
109             return True
110 
111         raise InvalidMacaroon("invalid macaroon")
112 
113     def create_macaroon(self, location, user_id, description, caveats):
114         """
115         Returns a tuple of a new raw (serialized) macaroon and its DB model.
116         The description provided is not embedded into the macaroon, only stored
117         in the DB model.
118         """
119         user = self.db.query(User).filter(User.id == user_id).one()
120 
121         dm = Macaroon(user=user, description=description, caveats=caveats)
122         self.db.add(dm)
123         self.db.flush()
124 
125         m = pymacaroons.Macaroon(
126             location=location,
127             identifier=str(dm.id),
128             key=dm.key,
129             version=pymacaroons.MACAROON_V2,
130         )
131         m.add_first_party_caveat(json.dumps(caveats))
132         serialized_macaroon = f"pypi:{m.serialize()}"
133         return serialized_macaroon, dm
134 
135     def delete_macaroon(self, macaroon_id):
136         """
137         Deletes a macaroon from the DB by its identifier.
138         """
139         dm = self.find_macaroon(macaroon_id)
140         self.db.delete(dm)
141         self.db.flush()
142 
143     def get_macaroon_by_description(self, user_id, description):
144         """
145         Returns a macaroon model from the DB with the given description,
146         if one exists for the given user.
147 
148         Returns None if the user doesn't have a macaroon with this description.
149         """
150         try:
151             dm = (
152                 self.db.query(Macaroon)
153                 .options(joinedload("user"))
154                 .filter(Macaroon.description == description)
155                 .filter(Macaroon.user_id == user_id)
156                 .one()
157             )
158         except NoResultFound:
159             return None
160 
161         return dm
162 
163 
164 def database_macaroon_factory(context, request):
165     return DatabaseMacaroonService(request.db)
166
```

Path: `warehouse/macaroons/auth_policy.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 base64
14 
15 from pyramid.authentication import CallbackAuthenticationPolicy
16 from pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy
17 from pyramid.security import Denied
18 from pyramid.threadlocal import get_current_request
19 from zope.interface import implementer
20 
21 from warehouse.cache.http import add_vary_callback
22 from warehouse.macaroons.interfaces import IMacaroonService
23 from warehouse.macaroons.services import InvalidMacaroon
24 
25 
26 def _extract_basic_macaroon(auth):
27     """
28     A helper function for extracting a macaroon from a
29     HTTP Basic Authentication-style header.
30 
31     Returns None if the header doesn't contain a structurally
32     valid macaroon, or the candidate (not yet verified) macaroon
33     in a serialized form.
34     """
35     try:
36         authorization = base64.b64decode(auth).decode()
37         auth_method, _, auth = authorization.partition(":")
38     except ValueError:
39         return None
40 
41     if auth_method != "@token":
42         return None
43 
44     return auth
45 
46 
47 def _extract_http_macaroon(request):
48     """
49     A helper function for the extraction of HTTP Macaroon from a given request.
50     Returns either a None if no macaroon could be found, or the string
51     that represents our serialized macaroon.
52     """
53     authorization = request.headers.get("Authorization")
54     if not authorization:
55         return None
56 
57     try:
58         auth_method, auth = authorization.split(" ", 1)
59     except ValueError:
60         return None
61 
62     if auth_method.lower() == "basic":
63         return _extract_basic_macaroon(auth)
64     elif auth_method.lower() == "token":
65         return auth
66 
67     return None
68 
69 
70 @implementer(IAuthenticationPolicy)
71 class MacaroonAuthenticationPolicy(CallbackAuthenticationPolicy):
72     def __init__(self, callback=None):
73         self.callback = callback
74 
75     def unauthenticated_userid(self, request):
76         # If we're calling into this API on a request, then we want to register
77         # a callback which will ensure that the response varies based on the
78         # Authorization header.
79         request.add_response_callback(add_vary_callback("Authorization"))
80 
81         # We need to extract our Macaroon from the request.
82         macaroon = _extract_http_macaroon(request)
83         if macaroon is None:
84             return None
85 
86         # Check to see if our Macaroon exists in the database, and if so
87         # fetch the user that is associated with it.
88         macaroon_service = request.find_service(IMacaroonService, context=None)
89         userid = macaroon_service.find_userid(macaroon)
90         if userid is not None:
91             return str(userid)
92 
93     def remember(self, request, userid, **kw):
94         # This is a NO-OP because our Macaroon header policy doesn't allow
95         # the ability for authentication to "remember" the user id. This
96         # assumes it has been configured in clients somewhere out of band.
97         return []
98 
99     def forget(self, request):
100         # This is a NO-OP because our Macaroon header policy doesn't allow
101         # the ability for authentication to "forget" the user id. This
102         # assumes it has been configured in clients somewhere out of band.
103         return []
104 
105 
106 @implementer(IAuthorizationPolicy)
107 class MacaroonAuthorizationPolicy:
108     def __init__(self, policy):
109         self.policy = policy
110 
111     def permits(self, context, principals, permission):
112         # The Pyramid API doesn't let us access the request here, so we have to pull it
113         # out of the thread local instead.
114         # TODO: Work with Pyramid devs to figure out if there is a better way to support
115         #       the worklow we are using here or not.
116         request = get_current_request()
117 
118         # Our request could possibly be a None, if there isn't an active request, in
119         # that case we're going to always deny, because without a request, we can't
120         # determine if this request is authorized or not.
121         if request is None:
122             return Denied("There was no active request.")
123 
124         # Re-extract our Macaroon from the request, it sucks to have to do this work
125         # twice, but I believe it is inevitable unless we pass the Macaroon back as
126         # a principal-- which doesn't seem to be the right fit for it.
127         macaroon = _extract_http_macaroon(request)
128 
129         # This logic will only happen on requests that are being authenticated with
130         # Macaroons. Any other request will just fall back to the standard Authorization
131         # policy.
132         if macaroon is not None:
133             macaroon_service = request.find_service(IMacaroonService, context=None)
134 
135             try:
136                 macaroon_service.verify(macaroon, context, principals, permission)
137             except InvalidMacaroon as exc:
138                 return Denied(f"The supplied token was invalid: {str(exc)!r}")
139 
140         # If our Macaroon is verified, then we'll pass this request to our underlying
141         # Authorization policy, so it can handle its own authorization logic on
142         # the prinicpal.
143         return self.policy.permits(context, principals, permission)
144 
145     def principals_allowed_by_permission(self, context, permission):
146         # We just dispatch this, because Macaroons don't restrict what principals are
147         # allowed by a particular permission, they just restrict specific requests
148         # to not have that permission.
149         return self.policy.principals_allowed_by_permission(context, permission)
150
```
--- 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()

```

diff --git a/warehouse/macaroons/auth_policy.py b/warehouse/macaroons/auth_policy.py
--- a/warehouse/macaroons/auth_policy.py
+++ b/warehouse/macaroons/auth_policy.py
@@ -38,7 +38,8 @@
     except ValueError:
         return None
 
-    if auth_method != "@token":
+    # TODO: Remove @token as an acceptable token username (GH-6345)
+    if auth_method != "@token" and auth_method != "__token__":
         return None
 
     return auth
diff --git a/warehouse/macaroons/services.py b/warehouse/macaroons/services.py
--- a/warehouse/macaroons/services.py
+++ b/warehouse/macaroons/services.py
@@ -31,7 +31,7 @@
     def __init__(self, db_session):
         self.db = db_session
 
-    def _extract_raw_macaroon(self, raw_macaroon):
+    def _extract_raw_macaroon(self, prefixed_macaroon):
         """
         Returns the base64-encoded macaroon component of a PyPI macaroon,
         dropping the prefix.
@@ -39,13 +39,13 @@
         Returns None if the macaroon is None, has no prefix, or has the
         wrong prefix.
         """
-        if raw_macaroon is None:
+        if prefixed_macaroon is None:
             return None
 
-        try:
-            prefix, raw_macaroon = raw_macaroon.split(":", 1)
-        except ValueError:
-            return None
+        prefix, split, raw_macaroon = prefixed_macaroon.partition("-")
+        # TODO: Remove ':' as an acceptable delimiter for tokens (GH-6345)
+        if prefix != "pypi" or not split:
+            prefix, _, raw_macaroon = prefixed_macaroon.partition(":")
 
         if prefix != "pypi":
             return None
@@ -129,7 +129,7 @@
             version=pymacaroons.MACAROON_V2,
         )
         m.add_first_party_caveat(json.dumps(caveats))
-        serialized_macaroon = f"pypi:{m.serialize()}"
+        serialized_macaroon = f"pypi-{m.serialize()}"
         return serialized_macaroon, dm
 
     def delete_macaroon(self, macaroon_id):

{"golden_diff": "diff --git a/warehouse/macaroons/auth_policy.py b/warehouse/macaroons/auth_policy.py\n--- a/warehouse/macaroons/auth_policy.py\n+++ b/warehouse/macaroons/auth_policy.py\n@@ -38,7 +38,8 @@\n     except ValueError:\n         return None\n \n-    if auth_method != \"@token\":\n+    # TODO: Remove @token as an acceptable token username (GH-6345)\n+    if auth_method != \"@token\" and auth_method != \"__token__\":\n         return None\n \n     return auth\ndiff --git a/warehouse/macaroons/services.py b/warehouse/macaroons/services.py\n--- a/warehouse/macaroons/services.py\n+++ b/warehouse/macaroons/services.py\n@@ -31,7 +31,7 @@\n     def __init__(self, db_session):\n         self.db = db_session\n \n-    def _extract_raw_macaroon(self, raw_macaroon):\n+    def _extract_raw_macaroon(self, prefixed_macaroon):\n         \"\"\"\n         Returns the base64-encoded macaroon component of a PyPI macaroon,\n         dropping the prefix.\n@@ -39,13 +39,13 @@\n         Returns None if the macaroon is None, has no prefix, or has the\n         wrong prefix.\n         \"\"\"\n-        if raw_macaroon is None:\n+        if prefixed_macaroon is None:\n             return None\n \n-        try:\n-            prefix, raw_macaroon = raw_macaroon.split(\":\", 1)\n-        except ValueError:\n-            return None\n+        prefix, split, raw_macaroon = prefixed_macaroon.partition(\"-\")\n+        # TODO: Remove ':' as an acceptable delimiter for tokens (GH-6345)\n+        if prefix != \"pypi\" or not split:\n+            prefix, _, raw_macaroon = prefixed_macaroon.partition(\":\")\n \n         if prefix != \"pypi\":\n             return None\n@@ -129,7 +129,7 @@\n             version=pymacaroons.MACAROON_V2,\n         )\n         m.add_first_party_caveat(json.dumps(caveats))\n-        serialized_macaroon = f\"pypi:{m.serialize()}\"\n+        serialized_macaroon = f\"pypi-{m.serialize()}\"\n         return serialized_macaroon, dm\n \n     def delete_macaroon(self, macaroon_id):\n", "issue": "Change username & API token prefix, to make Travis auth easier\nJust wanted to share a potential pain point for folks using the new PyPI API tokens for authentication via a travis.yaml. The @token username needs to be wrapped in quotes and the `:` after `pypi` needs to be escaped to work, otherwise you hit a 403 error (thanks for the help tracking this down @ewdurbin).\r\n\r\nIf you're using the environment variables through Travis' UI, the following works:\r\n```\r\ndeploy:\r\n  provider: pypi\r\n  user: \"@token\"  # quotes required for travis\r\n  # server: https://test.pypi.org/legacy/  # uncomment to do a test deploy\r\n  password:\r\n    secure: $PASSWORD  # stored in travis env var, with `:` after `pypi` escaped (pypi\\:)\r\n  on:\r\n    branch: master\r\n  skip_existing: true\r\n```\r\n\r\nIf you're encrypting the token via the command line, you can just wrap your token in quotes:\r\n```travis encrypt \"<your-pypi_token>\" --add deploy.password [--com]```\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 datetime\nimport json\nimport uuid\n\nimport pymacaroons\n\nfrom sqlalchemy.orm import joinedload\nfrom sqlalchemy.orm.exc import NoResultFound\nfrom zope.interface import implementer\n\nfrom warehouse.accounts.models import User\nfrom warehouse.macaroons.caveats import InvalidMacaroon, Verifier\nfrom warehouse.macaroons.interfaces import IMacaroonService\nfrom warehouse.macaroons.models import Macaroon\n\n\n@implementer(IMacaroonService)\nclass DatabaseMacaroonService:\n    def __init__(self, db_session):\n        self.db = db_session\n\n    def _extract_raw_macaroon(self, raw_macaroon):\n        \"\"\"\n        Returns the base64-encoded macaroon component of a PyPI macaroon,\n        dropping the prefix.\n\n        Returns None if the macaroon is None, has no prefix, or has the\n        wrong prefix.\n        \"\"\"\n        if raw_macaroon is None:\n            return None\n\n        try:\n            prefix, raw_macaroon = raw_macaroon.split(\":\", 1)\n        except ValueError:\n            return None\n\n        if prefix != \"pypi\":\n            return None\n\n        return raw_macaroon\n\n    def find_macaroon(self, macaroon_id):\n        \"\"\"\n        Returns a macaroon model from the DB by its identifier.\n        Returns None if no macaroon has the given ID.\n        \"\"\"\n        try:\n            dm = (\n                self.db.query(Macaroon)\n                .options(joinedload(\"user\"))\n                .filter(Macaroon.id == uuid.UUID(macaroon_id))\n                .one()\n            )\n        except NoResultFound:\n            return None\n\n        return dm\n\n    def find_userid(self, raw_macaroon):\n        \"\"\"\n        Returns the id of the user associated with the given raw (serialized)\n        macaroon.\n        \"\"\"\n        raw_macaroon = self._extract_raw_macaroon(raw_macaroon)\n        if raw_macaroon is None:\n            return None\n\n        m = pymacaroons.Macaroon.deserialize(raw_macaroon)\n        dm = self.find_macaroon(m.identifier.decode())\n\n        if dm is None:\n            return None\n\n        return dm.user.id\n\n    def verify(self, raw_macaroon, context, principals, permission):\n        \"\"\"\n        Returns True if the given raw (serialized) macaroon is\n        valid for the context, principals, and requested permission.\n\n        Raises InvalidMacaroon if the macaroon is not valid.\n        \"\"\"\n        raw_macaroon = self._extract_raw_macaroon(raw_macaroon)\n        if raw_macaroon is None:\n            raise InvalidMacaroon(\"malformed or nonexistent macaroon\")\n\n        m = pymacaroons.Macaroon.deserialize(raw_macaroon)\n        dm = self.find_macaroon(m.identifier.decode())\n\n        if dm is None:\n            raise InvalidMacaroon(\"deleted or nonexistent macaroon\")\n\n        verifier = Verifier(m, context, principals, permission)\n        if verifier.verify(dm.key):\n            dm.last_used = datetime.datetime.now()\n            return True\n\n        raise InvalidMacaroon(\"invalid macaroon\")\n\n    def create_macaroon(self, location, user_id, description, caveats):\n        \"\"\"\n        Returns a tuple of a new raw (serialized) macaroon and its DB model.\n        The description provided is not embedded into the macaroon, only stored\n        in the DB model.\n        \"\"\"\n        user = self.db.query(User).filter(User.id == user_id).one()\n\n        dm = Macaroon(user=user, description=description, caveats=caveats)\n        self.db.add(dm)\n        self.db.flush()\n\n        m = pymacaroons.Macaroon(\n            location=location,\n            identifier=str(dm.id),\n            key=dm.key,\n            version=pymacaroons.MACAROON_V2,\n        )\n        m.add_first_party_caveat(json.dumps(caveats))\n        serialized_macaroon = f\"pypi:{m.serialize()}\"\n        return serialized_macaroon, dm\n\n    def delete_macaroon(self, macaroon_id):\n        \"\"\"\n        Deletes a macaroon from the DB by its identifier.\n        \"\"\"\n        dm = self.find_macaroon(macaroon_id)\n        self.db.delete(dm)\n        self.db.flush()\n\n    def get_macaroon_by_description(self, user_id, description):\n        \"\"\"\n        Returns a macaroon model from the DB with the given description,\n        if one exists for the given user.\n\n        Returns None if the user doesn't have a macaroon with this description.\n        \"\"\"\n        try:\n            dm = (\n                self.db.query(Macaroon)\n                .options(joinedload(\"user\"))\n                .filter(Macaroon.description == description)\n                .filter(Macaroon.user_id == user_id)\n                .one()\n            )\n        except NoResultFound:\n            return None\n\n        return dm\n\n\ndef database_macaroon_factory(context, request):\n    return DatabaseMacaroonService(request.db)\n", "path": "warehouse/macaroons/services.py"}, {"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 base64\n\nfrom pyramid.authentication import CallbackAuthenticationPolicy\nfrom pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy\nfrom pyramid.security import Denied\nfrom pyramid.threadlocal import get_current_request\nfrom zope.interface import implementer\n\nfrom warehouse.cache.http import add_vary_callback\nfrom warehouse.macaroons.interfaces import IMacaroonService\nfrom warehouse.macaroons.services import InvalidMacaroon\n\n\ndef _extract_basic_macaroon(auth):\n    \"\"\"\n    A helper function for extracting a macaroon from a\n    HTTP Basic Authentication-style header.\n\n    Returns None if the header doesn't contain a structurally\n    valid macaroon, or the candidate (not yet verified) macaroon\n    in a serialized form.\n    \"\"\"\n    try:\n        authorization = base64.b64decode(auth).decode()\n        auth_method, _, auth = authorization.partition(\":\")\n    except ValueError:\n        return None\n\n    if auth_method != \"@token\":\n        return None\n\n    return auth\n\n\ndef _extract_http_macaroon(request):\n    \"\"\"\n    A helper function for the extraction of HTTP Macaroon from a given request.\n    Returns either a None if no macaroon could be found, or the string\n    that represents our serialized macaroon.\n    \"\"\"\n    authorization = request.headers.get(\"Authorization\")\n    if not authorization:\n        return None\n\n    try:\n        auth_method, auth = authorization.split(\" \", 1)\n    except ValueError:\n        return None\n\n    if auth_method.lower() == \"basic\":\n        return _extract_basic_macaroon(auth)\n    elif auth_method.lower() == \"token\":\n        return auth\n\n    return None\n\n\n@implementer(IAuthenticationPolicy)\nclass MacaroonAuthenticationPolicy(CallbackAuthenticationPolicy):\n    def __init__(self, callback=None):\n        self.callback = callback\n\n    def unauthenticated_userid(self, request):\n        # If we're calling into this API on a request, then we want to register\n        # a callback which will ensure that the response varies based on the\n        # Authorization header.\n        request.add_response_callback(add_vary_callback(\"Authorization\"))\n\n        # We need to extract our Macaroon from the request.\n        macaroon = _extract_http_macaroon(request)\n        if macaroon is None:\n            return None\n\n        # Check to see if our Macaroon exists in the database, and if so\n        # fetch the user that is associated with it.\n        macaroon_service = request.find_service(IMacaroonService, context=None)\n        userid = macaroon_service.find_userid(macaroon)\n        if userid is not None:\n            return str(userid)\n\n    def remember(self, request, userid, **kw):\n        # This is a NO-OP because our Macaroon header policy doesn't allow\n        # the ability for authentication to \"remember\" the user id. This\n        # assumes it has been configured in clients somewhere out of band.\n        return []\n\n    def forget(self, request):\n        # This is a NO-OP because our Macaroon header policy doesn't allow\n        # the ability for authentication to \"forget\" the user id. This\n        # assumes it has been configured in clients somewhere out of band.\n        return []\n\n\n@implementer(IAuthorizationPolicy)\nclass MacaroonAuthorizationPolicy:\n    def __init__(self, policy):\n        self.policy = policy\n\n    def permits(self, context, principals, permission):\n        # The Pyramid API doesn't let us access the request here, so we have to pull it\n        # out of the thread local instead.\n        # TODO: Work with Pyramid devs to figure out if there is a better way to support\n        #       the worklow we are using here or not.\n        request = get_current_request()\n\n        # Our request could possibly be a None, if there isn't an active request, in\n        # that case we're going to always deny, because without a request, we can't\n        # determine if this request is authorized or not.\n        if request is None:\n            return Denied(\"There was no active request.\")\n\n        # Re-extract our Macaroon from the request, it sucks to have to do this work\n        # twice, but I believe it is inevitable unless we pass the Macaroon back as\n        # a principal-- which doesn't seem to be the right fit for it.\n        macaroon = _extract_http_macaroon(request)\n\n        # This logic will only happen on requests that are being authenticated with\n        # Macaroons. Any other request will just fall back to the standard Authorization\n        # policy.\n        if macaroon is not None:\n            macaroon_service = request.find_service(IMacaroonService, context=None)\n\n            try:\n                macaroon_service.verify(macaroon, context, principals, permission)\n            except InvalidMacaroon as exc:\n                return Denied(f\"The supplied token was invalid: {str(exc)!r}\")\n\n        # If our Macaroon is verified, then we'll pass this request to our underlying\n        # Authorization policy, so it can handle its own authorization logic on\n        # the prinicpal.\n        return self.policy.permits(context, principals, permission)\n\n    def principals_allowed_by_permission(self, context, permission):\n        # We just dispatch this, because Macaroons don't restrict what principals are\n        # allowed by a particular permission, they just restrict specific requests\n        # to not have that permission.\n        return self.policy.principals_allowed_by_permission(context, permission)\n", "path": "warehouse/macaroons/auth_policy.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 datetime\nimport json\nimport uuid\n\nimport pymacaroons\n\nfrom sqlalchemy.orm import joinedload\nfrom sqlalchemy.orm.exc import NoResultFound\nfrom zope.interface import implementer\n\nfrom warehouse.accounts.models import User\nfrom warehouse.macaroons.caveats import InvalidMacaroon, Verifier\nfrom warehouse.macaroons.interfaces import IMacaroonService\nfrom warehouse.macaroons.models import Macaroon\n\n\n@implementer(IMacaroonService)\nclass DatabaseMacaroonService:\n    def __init__(self, db_session):\n        self.db = db_session\n\n    def _extract_raw_macaroon(self, prefixed_macaroon):\n        \"\"\"\n        Returns the base64-encoded macaroon component of a PyPI macaroon,\n        dropping the prefix.\n\n        Returns None if the macaroon is None, has no prefix, or has the\n        wrong prefix.\n        \"\"\"\n        if prefixed_macaroon is None:\n            return None\n\n        prefix, split, raw_macaroon = prefixed_macaroon.partition(\"-\")\n        # TODO: Remove ':' as an acceptable delimiter for tokens (GH-6345)\n        if prefix != \"pypi\" or not split:\n            prefix, _, raw_macaroon = prefixed_macaroon.partition(\":\")\n\n        if prefix != \"pypi\":\n            return None\n\n        return raw_macaroon\n\n    def find_macaroon(self, macaroon_id):\n        \"\"\"\n        Returns a macaroon model from the DB by its identifier.\n        Returns None if no macaroon has the given ID.\n        \"\"\"\n        try:\n            dm = (\n                self.db.query(Macaroon)\n                .options(joinedload(\"user\"))\n                .filter(Macaroon.id == uuid.UUID(macaroon_id))\n                .one()\n            )\n        except NoResultFound:\n            return None\n\n        return dm\n\n    def find_userid(self, raw_macaroon):\n        \"\"\"\n        Returns the id of the user associated with the given raw (serialized)\n        macaroon.\n        \"\"\"\n        raw_macaroon = self._extract_raw_macaroon(raw_macaroon)\n        if raw_macaroon is None:\n            return None\n\n        m = pymacaroons.Macaroon.deserialize(raw_macaroon)\n        dm = self.find_macaroon(m.identifier.decode())\n\n        if dm is None:\n            return None\n\n        return dm.user.id\n\n    def verify(self, raw_macaroon, context, principals, permission):\n        \"\"\"\n        Returns True if the given raw (serialized) macaroon is\n        valid for the context, principals, and requested permission.\n\n        Raises InvalidMacaroon if the macaroon is not valid.\n        \"\"\"\n        raw_macaroon = self._extract_raw_macaroon(raw_macaroon)\n        if raw_macaroon is None:\n            raise InvalidMacaroon(\"malformed or nonexistent macaroon\")\n\n        m = pymacaroons.Macaroon.deserialize(raw_macaroon)\n        dm = self.find_macaroon(m.identifier.decode())\n\n        if dm is None:\n            raise InvalidMacaroon(\"deleted or nonexistent macaroon\")\n\n        verifier = Verifier(m, context, principals, permission)\n        if verifier.verify(dm.key):\n            dm.last_used = datetime.datetime.now()\n            return True\n\n        raise InvalidMacaroon(\"invalid macaroon\")\n\n    def create_macaroon(self, location, user_id, description, caveats):\n        \"\"\"\n        Returns a tuple of a new raw (serialized) macaroon and its DB model.\n        The description provided is not embedded into the macaroon, only stored\n        in the DB model.\n        \"\"\"\n        user = self.db.query(User).filter(User.id == user_id).one()\n\n        dm = Macaroon(user=user, description=description, caveats=caveats)\n        self.db.add(dm)\n        self.db.flush()\n\n        m = pymacaroons.Macaroon(\n            location=location,\n            identifier=str(dm.id),\n            key=dm.key,\n            version=pymacaroons.MACAROON_V2,\n        )\n        m.add_first_party_caveat(json.dumps(caveats))\n        serialized_macaroon = f\"pypi-{m.serialize()}\"\n        return serialized_macaroon, dm\n\n    def delete_macaroon(self, macaroon_id):\n        \"\"\"\n        Deletes a macaroon from the DB by its identifier.\n        \"\"\"\n        dm = self.find_macaroon(macaroon_id)\n        self.db.delete(dm)\n        self.db.flush()\n\n    def get_macaroon_by_description(self, user_id, description):\n        \"\"\"\n        Returns a macaroon model from the DB with the given description,\n        if one exists for the given user.\n\n        Returns None if the user doesn't have a macaroon with this description.\n        \"\"\"\n        try:\n            dm = (\n                self.db.query(Macaroon)\n                .options(joinedload(\"user\"))\n                .filter(Macaroon.description == description)\n                .filter(Macaroon.user_id == user_id)\n                .one()\n            )\n        except NoResultFound:\n            return None\n\n        return dm\n\n\ndef database_macaroon_factory(context, request):\n    return DatabaseMacaroonService(request.db)\n", "path": "warehouse/macaroons/services.py"}, {"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 base64\n\nfrom pyramid.authentication import CallbackAuthenticationPolicy\nfrom pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy\nfrom pyramid.security import Denied\nfrom pyramid.threadlocal import get_current_request\nfrom zope.interface import implementer\n\nfrom warehouse.cache.http import add_vary_callback\nfrom warehouse.macaroons.interfaces import IMacaroonService\nfrom warehouse.macaroons.services import InvalidMacaroon\n\n\ndef _extract_basic_macaroon(auth):\n    \"\"\"\n    A helper function for extracting a macaroon from a\n    HTTP Basic Authentication-style header.\n\n    Returns None if the header doesn't contain a structurally\n    valid macaroon, or the candidate (not yet verified) macaroon\n    in a serialized form.\n    \"\"\"\n    try:\n        authorization = base64.b64decode(auth).decode()\n        auth_method, _, auth = authorization.partition(\":\")\n    except ValueError:\n        return None\n\n    # TODO: Remove @token as an acceptable token username (GH-6345)\n    if auth_method != \"@token\" and auth_method != \"__token__\":\n        return None\n\n    return auth\n\n\ndef _extract_http_macaroon(request):\n    \"\"\"\n    A helper function for the extraction of HTTP Macaroon from a given request.\n    Returns either a None if no macaroon could be found, or the string\n    that represents our serialized macaroon.\n    \"\"\"\n    authorization = request.headers.get(\"Authorization\")\n    if not authorization:\n        return None\n\n    try:\n        auth_method, auth = authorization.split(\" \", 1)\n    except ValueError:\n        return None\n\n    if auth_method.lower() == \"basic\":\n        return _extract_basic_macaroon(auth)\n    elif auth_method.lower() == \"token\":\n        return auth\n\n    return None\n\n\n@implementer(IAuthenticationPolicy)\nclass MacaroonAuthenticationPolicy(CallbackAuthenticationPolicy):\n    def __init__(self, callback=None):\n        self.callback = callback\n\n    def unauthenticated_userid(self, request):\n        # If we're calling into this API on a request, then we want to register\n        # a callback which will ensure that the response varies based on the\n        # Authorization header.\n        request.add_response_callback(add_vary_callback(\"Authorization\"))\n\n        # We need to extract our Macaroon from the request.\n        macaroon = _extract_http_macaroon(request)\n        if macaroon is None:\n            return None\n\n        # Check to see if our Macaroon exists in the database, and if so\n        # fetch the user that is associated with it.\n        macaroon_service = request.find_service(IMacaroonService, context=None)\n        userid = macaroon_service.find_userid(macaroon)\n        if userid is not None:\n            return str(userid)\n\n    def remember(self, request, userid, **kw):\n        # This is a NO-OP because our Macaroon header policy doesn't allow\n        # the ability for authentication to \"remember\" the user id. This\n        # assumes it has been configured in clients somewhere out of band.\n        return []\n\n    def forget(self, request):\n        # This is a NO-OP because our Macaroon header policy doesn't allow\n        # the ability for authentication to \"forget\" the user id. This\n        # assumes it has been configured in clients somewhere out of band.\n        return []\n\n\n@implementer(IAuthorizationPolicy)\nclass MacaroonAuthorizationPolicy:\n    def __init__(self, policy):\n        self.policy = policy\n\n    def permits(self, context, principals, permission):\n        # The Pyramid API doesn't let us access the request here, so we have to pull it\n        # out of the thread local instead.\n        # TODO: Work with Pyramid devs to figure out if there is a better way to support\n        #       the worklow we are using here or not.\n        request = get_current_request()\n\n        # Our request could possibly be a None, if there isn't an active request, in\n        # that case we're going to always deny, because without a request, we can't\n        # determine if this request is authorized or not.\n        if request is None:\n            return Denied(\"There was no active request.\")\n\n        # Re-extract our Macaroon from the request, it sucks to have to do this work\n        # twice, but I believe it is inevitable unless we pass the Macaroon back as\n        # a principal-- which doesn't seem to be the right fit for it.\n        macaroon = _extract_http_macaroon(request)\n\n        # This logic will only happen on requests that are being authenticated with\n        # Macaroons. Any other request will just fall back to the standard Authorization\n        # policy.\n        if macaroon is not None:\n            macaroon_service = request.find_service(IMacaroonService, context=None)\n\n            try:\n                macaroon_service.verify(macaroon, context, principals, permission)\n            except InvalidMacaroon as exc:\n                return Denied(f\"The supplied token was invalid: {str(exc)!r}\")\n\n        # If our Macaroon is verified, then we'll pass this request to our underlying\n        # Authorization policy, so it can handle its own authorization logic on\n        # the prinicpal.\n        return self.policy.permits(context, principals, permission)\n\n    def principals_allowed_by_permission(self, context, permission):\n        # We just dispatch this, because Macaroons don't restrict what principals are\n        # allowed by a particular permission, they just restrict specific requests\n        # to not have that permission.\n        return self.policy.principals_allowed_by_permission(context, permission)\n", "path": "warehouse/macaroons/auth_policy.py"}]}