From c55b64043c32b1959cc9ed503beaf447a000bdb0 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 13 Mar 2024 20:18:50 -0400 Subject: [PATCH 1/9] Revert "Revert "Return `Macaroon` alongside `User` in `MacaroonSecurityPolicy.identity` (#15581)" (#15588)" This reverts commit 5eba9cbd904383afd218ca1c619852294d9d02b2. --- tests/unit/accounts/test_core.py | 4 +- tests/unit/forklift/test_legacy.py | 4 +- tests/unit/macaroons/test_caveats.py | 14 ++++--- tests/unit/macaroons/test_security_policy.py | 7 ++-- tests/unit/macaroons/test_utils.py | 24 +++++++++++ tests/unit/oidc/test_utils.py | 2 +- warehouse/accounts/__init__.py | 8 ++-- warehouse/accounts/utils.py | 44 ++++++++++++++++++++ warehouse/macaroons/security_policy.py | 7 ++-- warehouse/oidc/utils.py | 2 +- 10 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 tests/unit/macaroons/test_utils.py create mode 100644 warehouse/accounts/utils.py diff --git a/tests/unit/accounts/test_core.py b/tests/unit/accounts/test_core.py index 94d34ecb1267..383b06a766cb 100644 --- a/tests/unit/accounts/test_core.py +++ b/tests/unit/accounts/test_core.py @@ -31,7 +31,7 @@ from warehouse.accounts.tasks import compute_user_metrics from warehouse.oidc.interfaces import SignedClaims from warehouse.oidc.models import OIDCPublisher -from warehouse.oidc.utils import OIDCContext +from warehouse.oidc.utils import PublisherTokenContext from warehouse.rate_limiting import IRateLimiter, RateLimit from ...common.db.accounts import UserFactory @@ -62,7 +62,7 @@ def test_with_oidc_publisher(self, db_request): assert isinstance(publisher, OIDCPublisher) claims = SignedClaims({"foo": "bar"}) - request = pretend.stub(identity=OIDCContext(publisher, claims)) + request = pretend.stub(identity=PublisherTokenContext(publisher, claims)) assert accounts._oidc_publisher(request) is publisher assert accounts._oidc_claims(request) is claims diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index 150cfba93b97..d787c23fb284 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -36,7 +36,7 @@ from warehouse.forklift import legacy from warehouse.metrics import IMetricsService from warehouse.oidc.interfaces import SignedClaims -from warehouse.oidc.utils import OIDCContext +from warehouse.oidc.utils import PublisherTokenContext from warehouse.packaging.interfaces import IFileStorage, IProjectService from warehouse.packaging.models import ( Dependency, @@ -3354,7 +3354,7 @@ def test_upload_succeeds_creates_release( else: publisher = GitHubPublisherFactory.create(projects=[project]) claims = {"sha": "somesha"} - identity = OIDCContext(publisher, SignedClaims(claims)) + identity = PublisherTokenContext(publisher, SignedClaims(claims)) db_request.oidc_publisher = identity.publisher db_request.oidc_claims = identity.claims diff --git a/tests/unit/macaroons/test_caveats.py b/tests/unit/macaroons/test_caveats.py index 6a718ae5baa2..3783c851067d 100644 --- a/tests/unit/macaroons/test_caveats.py +++ b/tests/unit/macaroons/test_caveats.py @@ -36,7 +36,7 @@ verify, ) from warehouse.macaroons.caveats._core import _CaveatRegistry -from warehouse.oidc.utils import OIDCContext +from warehouse.oidc.utils import PublisherTokenContext from ...common.db.accounts import UserFactory from ...common.db.oidc import GitHubPublisherFactory @@ -295,7 +295,7 @@ def test_verify_no_identity(self): ) def test_verify_invalid_publisher_id(self, db_request): - identity = OIDCContext(GitHubPublisherFactory.create(), None) + identity = PublisherTokenContext(GitHubPublisherFactory.create(), None) request = pretend.stub(identity=identity) request.oidc_publisher = _oidc_publisher(request) @@ -307,7 +307,7 @@ def test_verify_invalid_publisher_id(self, db_request): ) def test_verify_invalid_context(self, db_request): - identity = OIDCContext(GitHubPublisherFactory.create(), None) + identity = PublisherTokenContext(GitHubPublisherFactory.create(), None) request = pretend.stub(identity=identity) request.oidc_publisher = _oidc_publisher(request) @@ -322,7 +322,9 @@ def test_verify_invalid_project(self, db_request): # This OIDC publisher is only registered to "foobar", so it should # not verify a caveat presented for "foobaz". - identity = OIDCContext(GitHubPublisherFactory.create(projects=[foobar]), None) + identity = PublisherTokenContext( + GitHubPublisherFactory.create(projects=[foobar]), None + ) request = pretend.stub(identity=identity) request.oidc_publisher = _oidc_publisher(request) caveat = OIDCPublisher(oidc_publisher_id=str(request.oidc_publisher.id)) @@ -336,7 +338,9 @@ def test_verify_ok(self, db_request): # This OIDC publisher is only registered to "foobar", so it should # not verify a caveat presented for "foobaz". - identity = OIDCContext(GitHubPublisherFactory.create(projects=[foobar]), None) + identity = PublisherTokenContext( + GitHubPublisherFactory.create(projects=[foobar]), None + ) request = pretend.stub(identity=identity) request.oidc_publisher = _oidc_publisher(request) caveat = OIDCPublisher(oidc_publisher_id=str(request.oidc_publisher.id)) diff --git a/tests/unit/macaroons/test_security_policy.py b/tests/unit/macaroons/test_security_policy.py index 84397ac2bf47..4ce4a2e691b8 100644 --- a/tests/unit/macaroons/test_security_policy.py +++ b/tests/unit/macaroons/test_security_policy.py @@ -20,12 +20,13 @@ from zope.interface.verify import verifyClass from warehouse.accounts.interfaces import IUserService +from warehouse.accounts.utils import UserTokenContext from warehouse.authnz import Permissions from warehouse.macaroons import security_policy from warehouse.macaroons.interfaces import IMacaroonService from warehouse.macaroons.services import InvalidMacaroonError from warehouse.oidc.interfaces import SignedClaims -from warehouse.oidc.utils import OIDCContext +from warehouse.oidc.utils import PublisherTokenContext @pytest.mark.parametrize( @@ -214,7 +215,7 @@ def test_identity_user(self, monkeypatch): ), ) - assert policy.identity(request) is user + assert policy.identity(request) == UserTokenContext(user, macaroon) assert extract_http_macaroon.calls == [pretend.call(request)] assert request.find_service.calls == [ pretend.call(IMacaroonService, context=None), @@ -258,7 +259,7 @@ def test_identity_oidc_publisher(self, monkeypatch): identity = policy.identity(request) assert identity assert identity.publisher is oidc_publisher - assert identity == OIDCContext( + assert identity == PublisherTokenContext( oidc_publisher, SignedClaims(oidc_additional["oidc"]) ) diff --git a/tests/unit/macaroons/test_utils.py b/tests/unit/macaroons/test_utils.py new file mode 100644 index 000000000000..bbeab50c0667 --- /dev/null +++ b/tests/unit/macaroons/test_utils.py @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pretend + +from tests.common.db.accounts import UserFactory +from warehouse.accounts.utils import UserTokenContext +from warehouse.utils.security_policy import principals_for + + +def test_usertoken_context_principals(db_request): + user = UserFactory.create() + assert principals_for( + UserTokenContext(user=user, macaroon=pretend.stub()) + ) == principals_for(user) diff --git a/tests/unit/oidc/test_utils.py b/tests/unit/oidc/test_utils.py index 3b51d18bd756..4522eb9b29c4 100644 --- a/tests/unit/oidc/test_utils.py +++ b/tests/unit/oidc/test_utils.py @@ -238,7 +238,7 @@ def test_find_publisher_by_issuer_activestate( def test_oidc_context_principals(): assert principals_for( - utils.OIDCContext(publisher=pretend.stub(id=17), claims=None) + utils.PublisherTokenContext(publisher=pretend.stub(id=17), claims=None) ) == [ Authenticated, "oidc:17", diff --git a/warehouse/accounts/__init__.py b/warehouse/accounts/__init__.py index c88cec823f2c..40e5570e6bc5 100644 --- a/warehouse/accounts/__init__.py +++ b/warehouse/accounts/__init__.py @@ -34,7 +34,7 @@ from warehouse.accounts.tasks import compute_user_metrics from warehouse.admin.flags import AdminFlagValue from warehouse.macaroons.security_policy import MacaroonSecurityPolicy -from warehouse.oidc.utils import OIDCContext +from warehouse.oidc.utils import PublisherTokenContext from warehouse.organizations.services import IOrganizationService from warehouse.rate_limiting import IRateLimiter, RateLimit from warehouse.utils.security_policy import MultiSecurityPolicy @@ -63,14 +63,16 @@ def _user(request): def _oidc_publisher(request): return ( request.identity.publisher - if isinstance(request.identity, OIDCContext) + if isinstance(request.identity, PublisherTokenContext) else None ) def _oidc_claims(request): return ( - request.identity.claims if isinstance(request.identity, OIDCContext) else None + request.identity.claims + if isinstance(request.identity, PublisherTokenContext) + else None ) diff --git a/warehouse/accounts/utils.py b/warehouse/accounts/utils.py new file mode 100644 index 000000000000..c05e87f01956 --- /dev/null +++ b/warehouse/accounts/utils.py @@ -0,0 +1,44 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from dataclasses import dataclass + +from warehouse.accounts.models import User +from warehouse.macaroons.models import Macaroon + + +@dataclass +class UserTokenContext: + """ + This class supports `MacaroonSecurityPolicy` in + `warehouse.macaroons.security_policy`. + + It is a wrapper containing both the user associated with an API token + authenticated request and its corresponding Macaroon. We use it to smuggle + the Macaroon associated to the API token through to a session. `request.identity` + in an API token authenticated request should return this type. + """ + + user: User + """ + The associated user. + """ + + macaroon: Macaroon + """ + The Macaroon associated to the API token used to authenticate. + """ + + def __principals__(self) -> list[str]: + return self.user.__principals__() diff --git a/warehouse/macaroons/security_policy.py b/warehouse/macaroons/security_policy.py index 5190d13cb3c2..0748c1025cc6 100644 --- a/warehouse/macaroons/security_policy.py +++ b/warehouse/macaroons/security_policy.py @@ -17,13 +17,14 @@ from zope.interface import implementer from warehouse.accounts.interfaces import IUserService +from warehouse.accounts.utils import UserTokenContext from warehouse.authnz import Permissions from warehouse.cache.http import add_vary_callback from warehouse.errors import WarehouseDenied from warehouse.macaroons import InvalidMacaroonError from warehouse.macaroons.interfaces import IMacaroonService from warehouse.metrics.interfaces import IMetricsService -from warehouse.oidc.utils import OIDCContext +from warehouse.oidc.utils import PublisherTokenContext from warehouse.utils.security_policy import AuthenticationMethod, principals_for @@ -119,9 +120,9 @@ def identity(self, request): is_disabled, _ = login_service.is_disabled(dm.user.id) if is_disabled: return None - return dm.user + return UserTokenContext(dm.user, dm) - return OIDCContext(dm.oidc_publisher, oidc_claims) + return PublisherTokenContext(dm.oidc_publisher, oidc_claims) def remember(self, request, userid, **kw): # This is a NO-OP because our Macaroon header policy doesn't allow diff --git a/warehouse/oidc/utils.py b/warehouse/oidc/utils.py index 155275748077..3b38f861301e 100644 --- a/warehouse/oidc/utils.py +++ b/warehouse/oidc/utils.py @@ -94,7 +94,7 @@ def find_publisher_by_issuer( @dataclass -class OIDCContext: +class PublisherTokenContext: """ This class supports `MacaroonSecurityPolicy` in `warehouse.macaroons.security_policy`. From 38b57bf36a4aaed36fb83d9ecd0e4882a017758d Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 13 Mar 2024 20:34:36 -0400 Subject: [PATCH 2/9] warehouse: add UserTokenContext checks --- warehouse/accounts/__init__.py | 11 ++++++++--- warehouse/accounts/security_policy.py | 4 +++- warehouse/macaroons/caveats/__init__.py | 4 ++-- warehouse/utils/security_policy.py | 3 +++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/warehouse/accounts/__init__.py b/warehouse/accounts/__init__.py index 40e5570e6bc5..85837cfaa4c4 100644 --- a/warehouse/accounts/__init__.py +++ b/warehouse/accounts/__init__.py @@ -32,6 +32,7 @@ database_login_factory, ) from warehouse.accounts.tasks import compute_user_metrics +from warehouse.accounts.utils import UserTokenContext from warehouse.admin.flags import AdminFlagValue from warehouse.macaroons.security_policy import MacaroonSecurityPolicy from warehouse.oidc.utils import PublisherTokenContext @@ -54,11 +55,15 @@ def _user(request): if request.identity is None: return None - if not isinstance(request.identity, User): + if isinstance(request.identity, UserTokenContext): + # A UserTokenContext signals a user-created API token; + # take the underlying user. + return request.identity.user + elif isinstance(request.identity, User): + return request.identity + else: return None - return request.identity - def _oidc_publisher(request): return ( diff --git a/warehouse/accounts/security_policy.py b/warehouse/accounts/security_policy.py index ab5e15932638..efcb99ec3fa8 100644 --- a/warehouse/accounts/security_policy.py +++ b/warehouse/accounts/security_policy.py @@ -171,7 +171,9 @@ def permits(self, request, context, permission): def _permits_for_user_policy(acl, request, context, permission): # It should only be possible for request.identity to be a User object - # at this point, and we only a User in these policies. + # at this point, and we only allow a User in these policies. + # Note that UserTokenContext is not allowed here, since a UserTokenContext + # can only appear in an API-token-authenticated request, not a session. assert isinstance(request.identity, User) # Dispatch to our ACL diff --git a/warehouse/macaroons/caveats/__init__.py b/warehouse/macaroons/caveats/__init__.py index 1b1bcdc06308..d04fa4baa12e 100644 --- a/warehouse/macaroons/caveats/__init__.py +++ b/warehouse/macaroons/caveats/__init__.py @@ -11,7 +11,6 @@ # limitations under the License. import time - from typing import Any from pydantic import StrictInt, StrictStr @@ -22,6 +21,7 @@ from pyramid.security import Allowed from warehouse.accounts.models import User +from warehouse.accounts.utils import UserTokenContext from warehouse.errors import WarehouseDenied from warehouse.macaroons.caveats._core import ( Caveat, @@ -104,7 +104,7 @@ class RequestUser(Caveat): user_id: StrictStr def verify(self, request: Request, context: Any, permission: str) -> Result: - if not isinstance(request.identity, User): + if not isinstance(request.identity, UserTokenContext): return Failure("token with user restriction without a user") if str(request.identity.id) != self.user_id: diff --git a/warehouse/utils/security_policy.py b/warehouse/utils/security_policy.py index 25a81860a152..40db36d7aa51 100644 --- a/warehouse/utils/security_policy.py +++ b/warehouse/utils/security_policy.py @@ -18,6 +18,7 @@ from zope.interface import implementer from warehouse.accounts.models import User +from warehouse.accounts.utils import UserTokenContext # NOTE: Is there a better place for this to live? It may not even need to exist @@ -84,6 +85,8 @@ def authenticated_userid(self, request): # more correct pattern before fixing this. if isinstance(ident, User): return str(ident.id) + elif isinstance(ident, UserTokenContext): + return str(ident.user.id) return None def forget(self, request, **kw): From 57c7677b3cb73b147d960984b7e999d5130ea6eb Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 13 Mar 2024 20:42:40 -0400 Subject: [PATCH 3/9] warehouse: reformat, circular import --- warehouse/accounts/utils.py | 11 +++++++++-- warehouse/macaroons/caveats/__init__.py | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/warehouse/accounts/utils.py b/warehouse/accounts/utils.py index c05e87f01956..9b141616859d 100644 --- a/warehouse/accounts/utils.py +++ b/warehouse/accounts/utils.py @@ -13,9 +13,12 @@ from __future__ import annotations from dataclasses import dataclass +from typing import TYPE_CHECKING +from uuid import UUID -from warehouse.accounts.models import User -from warehouse.macaroons.models import Macaroon +if TYPE_CHECKING: + from warehouse.accounts.models import User + from warehouse.macaroons.models import Macaroon @dataclass @@ -42,3 +45,7 @@ class UserTokenContext: def __principals__(self) -> list[str]: return self.user.__principals__() + + @property + def id(self) -> UUID: + return self.user.id diff --git a/warehouse/macaroons/caveats/__init__.py b/warehouse/macaroons/caveats/__init__.py index d04fa4baa12e..49cadfb01692 100644 --- a/warehouse/macaroons/caveats/__init__.py +++ b/warehouse/macaroons/caveats/__init__.py @@ -11,6 +11,7 @@ # limitations under the License. import time + from typing import Any from pydantic import StrictInt, StrictStr @@ -20,7 +21,6 @@ from pyramid.request import Request from pyramid.security import Allowed -from warehouse.accounts.models import User from warehouse.accounts.utils import UserTokenContext from warehouse.errors import WarehouseDenied from warehouse.macaroons.caveats._core import ( From 9637d7c93d1c984be5044110fbb5bbdb9d1cd260 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 13 Mar 2024 21:08:51 -0400 Subject: [PATCH 4/9] tests: coverage --- tests/unit/accounts/test_core.py | 9 ++++++++- tests/unit/macaroons/test_caveats.py | 7 +++++-- tests/unit/utils/test_security_policy.py | 21 ++++++++++++++++++--- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/tests/unit/accounts/test_core.py b/tests/unit/accounts/test_core.py index 383b06a766cb..d2c2d466888e 100644 --- a/tests/unit/accounts/test_core.py +++ b/tests/unit/accounts/test_core.py @@ -12,7 +12,6 @@ import pretend import pytest - from celery.schedules import crontab from warehouse import accounts @@ -29,6 +28,7 @@ database_login_factory, ) from warehouse.accounts.tasks import compute_user_metrics +from warehouse.accounts.utils import UserTokenContext from warehouse.oidc.interfaces import SignedClaims from warehouse.oidc.models import OIDCPublisher from warehouse.oidc.utils import PublisherTokenContext @@ -45,6 +45,13 @@ def test_with_user(self, db_request): assert accounts._user(request) is user + def test_with_user_token_context(self, db_request): + user = UserFactory.create() + user_ctx = UserTokenContext(user, pretend.stub()) + request = pretend.stub(identity=user_ctx) + + assert accounts._user(request) is user + def test_without_user_identity(self): nonuser = pretend.stub() request = pretend.stub(identity=nonuser) diff --git a/tests/unit/macaroons/test_caveats.py b/tests/unit/macaroons/test_caveats.py index 3783c851067d..de44d7f43fdd 100644 --- a/tests/unit/macaroons/test_caveats.py +++ b/tests/unit/macaroons/test_caveats.py @@ -20,6 +20,7 @@ from pymacaroons import Macaroon from warehouse.accounts import _oidc_publisher +from warehouse.accounts.utils import UserTokenContext from warehouse.macaroons import caveats from warehouse.macaroons.caveats import ( Caveat, @@ -260,10 +261,11 @@ def test_verify_invalid_identity(self): def test_verify_invalid_user_id(self, db_request): user = UserFactory.create() + user_token_context = UserTokenContext(user, pretend.stub()) caveat = RequestUser(user_id="invalid") result = caveat.verify( - pretend.stub(identity=user), pretend.stub(), pretend.stub() + pretend.stub(identity=user_token_context), pretend.stub(), pretend.stub() ) assert result == Failure( @@ -272,10 +274,11 @@ def test_verify_invalid_user_id(self, db_request): def test_verify_ok(self, db_request): user = UserFactory.create() + user_token_context = UserTokenContext(user, pretend.stub()) caveat = RequestUser(user_id=str(user.id)) result = caveat.verify( - pretend.stub(identity=user), pretend.stub(), pretend.stub() + pretend.stub(identity=user_token_context), pretend.stub(), pretend.stub() ) assert result == Success() diff --git a/tests/unit/utils/test_security_policy.py b/tests/unit/utils/test_security_policy.py index 0c1c1b0683af..69324b8b8f84 100644 --- a/tests/unit/utils/test_security_policy.py +++ b/tests/unit/utils/test_security_policy.py @@ -15,6 +15,8 @@ from pyramid.interfaces import ISecurityPolicy from zope.interface.verify import verifyClass +from tests.common.db.accounts import UserFactory +from warehouse.accounts.utils import UserTokenContext from warehouse.utils import security_policy @@ -86,11 +88,24 @@ def test_authenticated_userid_nonuser_identity(self, db_request): assert policy.authenticated_userid(request) is None - def test_authenticated_userid(self, monkeypatch): - monkeypatch.setattr(security_policy, "User", pretend.stub) + def test_authenticated_userid_user_token_context(self, db_request): + user = UserFactory.create() + user_ctx = UserTokenContext(user, pretend.stub()) + + request = pretend.stub(add_finished_callback=lambda *a, **kw: None) + subpolicies = [pretend.stub(identity=lambda r: user_ctx)] + policy = security_policy.MultiSecurityPolicy(subpolicies) + + assert ( + policy.authenticated_userid(request) + == str(user.id) + == str(user_ctx.user.id) + ) + + def test_authenticated_userid(self, db_request): + user = UserFactory.create() request = pretend.stub(add_finished_callback=lambda *a, **kw: None) - user = pretend.stub(id="a fake user") subpolicies = [pretend.stub(identity=lambda r: user)] policy = security_policy.MultiSecurityPolicy(subpolicies) From 9c79055fa76cf9b2fb1cb5cfa61184a8838146a7 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 13 Mar 2024 21:09:24 -0400 Subject: [PATCH 5/9] tests: reformat --- tests/unit/accounts/test_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/accounts/test_core.py b/tests/unit/accounts/test_core.py index d2c2d466888e..24465b78c992 100644 --- a/tests/unit/accounts/test_core.py +++ b/tests/unit/accounts/test_core.py @@ -12,6 +12,7 @@ import pretend import pytest + from celery.schedules import crontab from warehouse import accounts From c4f3af36bfddf4453cb97b265de945d92774e6f7 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 13 Mar 2024 21:20:38 -0400 Subject: [PATCH 6/9] warehouse: remove UserTokenContext.id Eliminate the line that needed it. --- warehouse/accounts/utils.py | 4 ---- warehouse/macaroons/caveats/__init__.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/warehouse/accounts/utils.py b/warehouse/accounts/utils.py index 9b141616859d..e62df21e41f8 100644 --- a/warehouse/accounts/utils.py +++ b/warehouse/accounts/utils.py @@ -45,7 +45,3 @@ class UserTokenContext: def __principals__(self) -> list[str]: return self.user.__principals__() - - @property - def id(self) -> UUID: - return self.user.id diff --git a/warehouse/macaroons/caveats/__init__.py b/warehouse/macaroons/caveats/__init__.py index 49cadfb01692..9bb112f57488 100644 --- a/warehouse/macaroons/caveats/__init__.py +++ b/warehouse/macaroons/caveats/__init__.py @@ -107,7 +107,7 @@ def verify(self, request: Request, context: Any, permission: str) -> Result: if not isinstance(request.identity, UserTokenContext): return Failure("token with user restriction without a user") - if str(request.identity.id) != self.user_id: + if str(request.identity.user.id) != self.user_id: return Failure("current user does not match user restriction in token") return Success() From 71ff0daf85352135a83f8d9205c9cd635523e33f Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 13 Mar 2024 21:36:17 -0400 Subject: [PATCH 7/9] accounts/utils: lintage --- warehouse/accounts/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/warehouse/accounts/utils.py b/warehouse/accounts/utils.py index e62df21e41f8..d120945861e2 100644 --- a/warehouse/accounts/utils.py +++ b/warehouse/accounts/utils.py @@ -14,7 +14,6 @@ from dataclasses import dataclass from typing import TYPE_CHECKING -from uuid import UUID if TYPE_CHECKING: from warehouse.accounts.models import User From 82efc7f3e55c418280e87685bb386f47078e34a4 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 15 Mar 2024 17:37:35 -0400 Subject: [PATCH 8/9] test_legacy: backstop behavior Signed-off-by: William Woodruff --- tests/unit/forklift/test_legacy.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index d787c23fb284..9379254bf39f 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -16,13 +16,11 @@ import tarfile import tempfile import zipfile - from cgi import FieldStorage from unittest import mock import pretend import pytest - from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPTooManyRequests from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload @@ -31,6 +29,7 @@ from wtforms.form import Form from wtforms.validators import ValidationError +from warehouse.accounts.utils import UserTokenContext from warehouse.admin.flags import AdminFlag, AdminFlagValue from warehouse.classifiers.models import Classifier from warehouse.forklift import legacy @@ -1379,6 +1378,7 @@ def test_upload_escapes_nul_characters(self, pyramid_config, db_request): assert "\x00" not in db_request.POST["summary"] + @pytest.mark.parametrize("token_context", [True, False]) @pytest.mark.parametrize( ("digests",), [ @@ -1407,6 +1407,7 @@ def test_successful_upload( pyramid_config, db_request, digests, + token_context, metrics, ): monkeypatch.setattr(tempfile, "tempdir", str(tmpdir)) @@ -1421,8 +1422,13 @@ def test_successful_upload( filename = f"{project.name}-{release.version}.tar.gz" - pyramid_config.testing_securitypolicy(identity=user) db_request.user = user + if token_context: + user_context = UserTokenContext(user, pretend.stub()) + pyramid_config.testing_securitypolicy(identity=user_context) + else: + pyramid_config.testing_securitypolicy(identity=user) + db_request.user_agent = "warehouse-tests/6.6.6" content = FieldStorage() From 509353e5dc13bd09c009bca1cfd1f9a67ce79991 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 15 Mar 2024 19:17:14 -0400 Subject: [PATCH 9/9] tests/unit: lintage --- tests/unit/forklift/test_legacy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index 9379254bf39f..ae81fb81aa34 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -16,11 +16,13 @@ import tarfile import tempfile import zipfile + from cgi import FieldStorage from unittest import mock import pretend import pytest + from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPTooManyRequests from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload