From e60885d91ba6747f1fea52209307718101bee388 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 11 Apr 2022 12:49:20 -0400 Subject: [PATCH 1/7] warehouse, tests: pick DB changes from #11122 --- tests/unit/integration/github/test_utils.py | 2 +- tests/unit/macaroons/test_services.py | 16 +++---- tests/unit/manage/test_views.py | 46 +++++++++++-------- warehouse/integrations/github/utils.py | 2 +- warehouse/macaroons/models.py | 7 ++- warehouse/macaroons/services.py | 9 +++- warehouse/manage/views.py | 10 ++-- ...62e097c26_rename_caveats_to_permissions.py | 31 +++++++++++++ warehouse/templates/manage/account.html | 24 +++++++--- warehouse/templates/manage/token.html | 6 +-- 10 files changed, 103 insertions(+), 50 deletions(-) create mode 100644 warehouse/migrations/versions/84262e097c26_rename_caveats_to_permissions.py diff --git a/tests/unit/integration/github/test_utils.py b/tests/unit/integration/github/test_utils.py index b041a75ea01b..24ef54955dbf 100644 --- a/tests/unit/integration/github/test_utils.py +++ b/tests/unit/integration/github/test_utils.py @@ -554,7 +554,7 @@ def metrics_increment(key): user_id = uuid.UUID(bytes=b"0" * 16) user = pretend.stub(id=user_id) database_macaroon = pretend.stub( - user=user, id=12, caveats={"permissions": "user"}, description="foo" + user=user, id=12, permissions="user", description="foo" ) find = pretend.call_recorder(lambda *a, **kw: database_macaroon) diff --git a/tests/unit/macaroons/test_services.py b/tests/unit/macaroons/test_services.py index 1a921d48ff14..8f0487eb27b1 100644 --- a/tests/unit/macaroons/test_services.py +++ b/tests/unit/macaroons/test_services.py @@ -61,7 +61,7 @@ def test_find_macaroon_invalid_macaroon(self, macaroon_service): def test_find_macaroon(self, user_service, macaroon_service): user = UserFactory.create() _, macaroon = macaroon_service.create_macaroon( - "fake location", user.id, "fake description", {"fake": "caveats"} + "fake location", user.id, "fake description", [{"permissions": "user"}] ) dm = macaroon_service.find_macaroon(str(macaroon.id)) @@ -72,7 +72,7 @@ def test_find_macaroon(self, user_service, macaroon_service): def test_find_from_raw(self, user_service, macaroon_service): user = UserFactory.create() serialized, macaroon = macaroon_service.create_macaroon( - "fake location", user.id, "fake description", {"fake": "caveats"} + "fake location", user.id, "fake description", [{"permissions": "user"}] ) dm = macaroon_service.find_from_raw(serialized) @@ -116,14 +116,14 @@ def test_find_userid_malformed_macaroon(self, macaroon_service): def test_find_userid_valid_macaroon_trailinglinebreak(self, macaroon_service): user = UserFactory.create() raw_macaroon, _ = macaroon_service.create_macaroon( - "fake location", user.id, "fake description", {"fake": "caveats"} + "fake location", user.id, "fake description", [{"permissions": "user"}] ) assert macaroon_service.find_userid(f"{raw_macaroon}\n") is None def test_find_userid(self, macaroon_service): user = UserFactory.create() raw_macaroon, _ = macaroon_service.create_macaroon( - "fake location", user.id, "fake description", {"fake": "caveats"} + "fake location", user.id, "fake description", [{"permissions": "user"}] ) user_id = macaroon_service.find_userid(raw_macaroon) @@ -159,7 +159,7 @@ def test_verify_no_macaroon(self, macaroon_service): def test_verify_invalid_macaroon(self, monkeypatch, user_service, macaroon_service): user = UserFactory.create() raw_macaroon, _ = macaroon_service.create_macaroon( - "fake location", user.id, "fake description", {"fake": "caveats"} + "fake location", user.id, "fake description", [{"permissions": "user"}] ) verifier_obj = pretend.stub(verify=pretend.call_recorder(lambda k: False)) @@ -219,7 +219,7 @@ def test_verify_malformed_macaroon(self, macaroon_service): def test_verify_valid_macaroon(self, monkeypatch, macaroon_service): user = UserFactory.create() raw_macaroon, _ = macaroon_service.create_macaroon( - "fake location", user.id, "fake description", {"fake": "caveats"} + "fake location", user.id, "fake description", [{"permissions": "user"}] ) verifier_obj = pretend.stub(verify=pretend.call_recorder(lambda k: True)) @@ -238,7 +238,7 @@ def test_verify_valid_macaroon(self, monkeypatch, macaroon_service): def test_delete_macaroon(self, user_service, macaroon_service): user = UserFactory.create() _, macaroon = macaroon_service.create_macaroon( - "fake location", user.id, "fake description", {"fake": "caveats"} + "fake location", user.id, "fake description", [{"permissions": "user"}] ) macaroon_id = str(macaroon.id) @@ -256,7 +256,7 @@ def test_get_macaroon_by_description_no_macaroon(self, macaroon_service): def test_get_macaroon_by_description(self, macaroon_service): user = UserFactory.create() _, macaroon = macaroon_service.create_macaroon( - "fake location", user.id, "fake description", {"fake": "caveats"} + "fake location", user.id, "fake description", [{"permissions": "user"}] ) dm = macaroon_service.find_macaroon(str(macaroon.id)) diff --git a/tests/unit/manage/test_views.py b/tests/unit/manage/test_views.py index 4b9483c25a01..f25976769481 100644 --- a/tests/unit/manage/test_views.py +++ b/tests/unit/manage/test_views.py @@ -1957,10 +1957,12 @@ def test_create_macaroon(self, monkeypatch): location=request.domain, user_id=request.user.id, description=create_macaroon_obj.description.data, - caveats={ - "permissions": create_macaroon_obj.validated_scope, - "version": 1, - }, + caveats=[ + { + "permissions": create_macaroon_obj.validated_scope, + "version": 1, + } + ], ) ] assert result == { @@ -1975,10 +1977,12 @@ def test_create_macaroon(self, monkeypatch): tag="account:api_token:added", additional={ "description": create_macaroon_obj.description.data, - "caveats": { - "permissions": create_macaroon_obj.validated_scope, - "version": 1, - }, + "caveats": [ + { + "permissions": create_macaroon_obj.validated_scope, + "version": 1, + } + ], }, ) ] @@ -2044,10 +2048,12 @@ def test_create_macaroon_records_events_for_each_project(self, monkeypatch): location=request.domain, user_id=request.user.id, description=create_macaroon_obj.description.data, - caveats={ - "permissions": create_macaroon_obj.validated_scope, - "version": 1, - }, + caveats=[ + { + "permissions": create_macaroon_obj.validated_scope, + "version": 1, + } + ], ) ] assert result == { @@ -2062,10 +2068,12 @@ def test_create_macaroon_records_events_for_each_project(self, monkeypatch): tag="account:api_token:added", additional={ "description": create_macaroon_obj.description.data, - "caveats": { - "permissions": create_macaroon_obj.validated_scope, - "version": 1, - }, + "caveats": [ + { + "permissions": create_macaroon_obj.validated_scope, + "version": 1, + } + ], }, ) ] @@ -2154,9 +2162,7 @@ def test_delete_macaroon_dangerous_redirect(self, monkeypatch): assert macaroon_service.delete_macaroon.calls == [] def test_delete_macaroon(self, monkeypatch): - macaroon = pretend.stub( - description="fake macaroon", caveats={"version": 1, "permissions": "user"} - ) + macaroon = pretend.stub(description="fake macaroon", permissions="user") macaroon_service = pretend.stub( delete_macaroon=pretend.call_recorder(lambda id: pretend.stub()), find_macaroon=pretend.call_recorder(lambda id: macaroon), @@ -2213,7 +2219,7 @@ def test_delete_macaroon(self, monkeypatch): def test_delete_macaroon_records_events_for_each_project(self, monkeypatch): macaroon = pretend.stub( description="fake macaroon", - caveats={"version": 1, "permissions": {"projects": ["foo", "bar"]}}, + permissions={"projects": ["foo", "bar"]}, ) macaroon_service = pretend.stub( delete_macaroon=pretend.call_recorder(lambda id: pretend.stub()), diff --git a/warehouse/integrations/github/utils.py b/warehouse/integrations/github/utils.py index f29bc2a05375..fa6322cfaa45 100644 --- a/warehouse/integrations/github/utils.py +++ b/warehouse/integrations/github/utils.py @@ -251,7 +251,7 @@ def _analyze_disclosure(request, disclosure_record, origin): additional={ "macaroon_id": str(database_macaroon.id), "public_url": disclosure.public_url, - "permissions": database_macaroon.caveats.get("permissions", "user"), + "permissions": database_macaroon.permissions, "description": database_macaroon.description, }, ) diff --git a/warehouse/macaroons/models.py b/warehouse/macaroons/models.py index d599b1a8fd57..572558cce6e2 100644 --- a/warehouse/macaroons/models.py +++ b/warehouse/macaroons/models.py @@ -52,10 +52,9 @@ class Macaroon(db.Model): created = Column(DateTime, nullable=False, server_default=sql.func.now()) last_used = Column(DateTime, nullable=True) - # We'll store the caveats that were added to the Macaroon during generation - # to allow users to see in their management UI what the total possible - # scope of their macaroon is. - caveats = Column(JSONB, nullable=False, server_default=sql.text("'{}'")) + # Human-readable "permissions" for this macaroon, corresponding to the + # body of the permissions ("V1") caveat. + permissions = Column(JSONB, nullable=False, server_default=sql.text("'{}'")) # It might be better to move this default into the database, that way we # make it less likely that something does it incorrectly (since the diff --git a/warehouse/macaroons/services.py b/warehouse/macaroons/services.py index c394b74f8349..a1a691df3049 100644 --- a/warehouse/macaroons/services.py +++ b/warehouse/macaroons/services.py @@ -135,7 +135,11 @@ def create_macaroon(self, location, user_id, description, caveats): """ user = self.db.query(User).filter(User.id == user_id).one() - dm = Macaroon(user=user, description=description, caveats=caveats) + # NOTE: This is a bit of a hack: we keep a separate copy of the + # permissions caveat in the DB, so that we can display scope information + # in the UI. + permissions = next(c for c in caveats if "permissions" in c) # pragma: no cover + dm = Macaroon(user=user, description=description, permissions=permissions) self.db.add(dm) self.db.flush() @@ -145,7 +149,8 @@ def create_macaroon(self, location, user_id, description, caveats): key=dm.key, version=pymacaroons.MACAROON_V2, ) - m.add_first_party_caveat(json.dumps(caveats)) + for caveat in caveats: + m.add_first_party_caveat(json.dumps(caveat)) serialized_macaroon = f"pypi-{m.serialize()}" return serialized_macaroon, dm diff --git a/warehouse/manage/views.py b/warehouse/manage/views.py index 0adc541fb6f0..b61c9f03e898 100644 --- a/warehouse/manage/views.py +++ b/warehouse/manage/views.py @@ -879,7 +879,10 @@ def create_macaroon(self): response = {**self.default_response} if form.validate(): - macaroon_caveats = {"permissions": form.validated_scope, "version": 1} + # NOTE: The caveat order here is important: the UI assumes + # that the permissions caveat comes first in any recorded + # events. + macaroon_caveats = [{"permissions": form.validated_scope, "version": 1}] serialized_macaroon, macaroon = self.macaroon_service.create_macaroon( location=self.request.domain, user_id=self.request.user.id, @@ -941,12 +944,11 @@ def delete_macaroon(self): tag="account:api_token:removed", additional={"macaroon_id": form.macaroon_id.data}, ) - if "projects" in macaroon.caveats["permissions"]: + if "projects" in macaroon.permissions: projects = [ project for project in self.request.user.projects - if project.normalized_name - in macaroon.caveats["permissions"]["projects"] + if project.normalized_name in macaroon.permissions["projects"] ] for project in projects: project.record_event( diff --git a/warehouse/migrations/versions/84262e097c26_rename_caveats_to_permissions.py b/warehouse/migrations/versions/84262e097c26_rename_caveats_to_permissions.py new file mode 100644 index 000000000000..54392e95c9b2 --- /dev/null +++ b/warehouse/migrations/versions/84262e097c26_rename_caveats_to_permissions.py @@ -0,0 +1,31 @@ +# 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. +""" +Remove caveats to permissions + +Revision ID: 84262e097c26 +Revises: f345394c444f +Create Date: 2022-04-05 18:35:57.325801 +""" + +from alembic import op + +revision = "84262e097c26" +down_revision = "f345394c444f" + + +def upgrade(): + op.alter_column("macaroons", "caveats", new_column_name="permissions") + + +def downgrade(): + op.alter_column("macaroons", "permissions", new_column_name="caveats") diff --git a/warehouse/templates/manage/account.html b/warehouse/templates/manage/account.html index d4be82cae493..8dc63616433a 100644 --- a/warehouse/templates/manage/account.html +++ b/warehouse/templates/manage/account.html @@ -141,10 +141,10 @@ {% trans %}Scope{% endtrans %} - {% if macaroon.caveats.get("permissions") == 'user' %} + {% if macaroon.permissions.permissions == 'user' %} {% trans %}All projects{% endtrans %} {% else %} - {% for project in macaroon.caveats.get("permissions")['projects'] %} + {% for project in macaroon.permissions.get("permissions")['projects'] %} {{ project }} {% endfor %} {% endif %} @@ -461,6 +461,18 @@

{% trans %}Security history{% endtrans %}

{% set recent_events = user.recent_events %} {% if recent_events|length > 0 %} + {% macro caveat_detail(caveat) -%} + {% if "permissions" in caveat %} + {% if caveat.permissions == "user" %} + {% trans %}Token scope: entire account{% endtrans %} + {% else %} + {% trans project_name=caveat.permissions.projects[0] %}Token scope: Project {{ project_name }}{% endtrans %} + {% endif %} + {% elif "exp" in caveat %} + {% trans trimmed exp=humanize(caveat.exp) %}Expires: {{ exp }}{% endtrans %} + {% endif %} + {%- endmacro %} + {% macro event_summary(event) -%} {% if event.tag == "account:create" %} {% trans %}Account created{% endtrans %} @@ -597,11 +609,9 @@

{% trans %}Security history{% endtrans %}

{% trans %}API token added{% endtrans %}
{% trans %}Token name:{% endtrans %} {{ event.additional.description }}
- {% if event.additional.caveats.permissions == "user" %} - {% trans %}Token scope: entire account{% endtrans %} - {% else %} - {% trans project_name=event.additional.caveats.permissions.projects[0] %}Token scope: Project {{ project_name }}{% endtrans %} - {% endif %} + {% for caveat in event.additional.caveats %} + {{ caveat_detail(caveat) }} + {% endfor %}
{% elif event.tag == "account:api_token:removed" %} diff --git a/warehouse/templates/manage/token.html b/warehouse/templates/manage/token.html index a3434d17ad38..eb69e8774678 100644 --- a/warehouse/templates/manage/token.html +++ b/warehouse/templates/manage/token.html @@ -38,10 +38,10 @@

{{ title }}

{% trans macaroon_description=macaroon.description %}Token for "{{ macaroon_description }}"{% endtrans %}

{% trans %}Permissions:{% endtrans %} {% trans %}Upload packages{% endtrans %}
- {% if macaroon.caveats.permissions == "user" %} + {% if macaroon.permissions.permissions == "user" %} {% trans %}Scope:{% endtrans %} {% trans %}Entire account (all projects){% endtrans %} {% else %} - {% trans %}Scope:{% endtrans %} {% trans project=macaroon.caveats.permissions.projects[0] %}Project "{{ project }}"{% endtrans %} + {% trans %}Scope:{% endtrans %} {% trans project=macaroon.permissions.permissions.projects[0] %}Project "{{ project }}"{% endtrans %} {% endif %}

@@ -78,7 +78,7 @@

{% trans %}Using this token{% endtrans %}

  • {% trans prefix='pypi-' %}Set your password to the token value, including the {{ prefix }} prefix{% endtrans %}
  • - {% if macaroon.caveats.permissions == "user" %} + {% if macaroon.permissions.permissions == "user" %}

    {% trans trimmed href='https://pypi.org/project/twine/', filename='$HOME/.pypirc' %} From a8a1df6a9f560510194579047d5f7ec2c843702e Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 11 Apr 2022 12:56:49 -0400 Subject: [PATCH 2/7] warehouse: `make translations` --- warehouse/locale/messages.pot | 183 +++++++++++++++++----------------- 1 file changed, 94 insertions(+), 89 deletions(-) diff --git a/warehouse/locale/messages.pot b/warehouse/locale/messages.pot index 23279f4b7077..8c42afc0193b 100644 --- a/warehouse/locale/messages.pot +++ b/warehouse/locale/messages.pot @@ -237,39 +237,39 @@ msgstr "" msgid "Generating new recovery codes will invalidate your existing codes." msgstr "" -#: warehouse/manage/views.py:1191 +#: warehouse/manage/views.py:1193 msgid "" "There have been too many attempted OpenID Connect registrations. Try " "again later." msgstr "" -#: warehouse/manage/views.py:1872 +#: warehouse/manage/views.py:1874 msgid "User '${username}' already has ${role_name} role for project" msgstr "" -#: warehouse/manage/views.py:1883 +#: warehouse/manage/views.py:1885 msgid "" "User '${username}' does not have a verified primary email address and " "cannot be added as a ${role_name} for project" msgstr "" -#: warehouse/manage/views.py:1896 +#: warehouse/manage/views.py:1898 msgid "User '${username}' already has an active invite. Please try again later." msgstr "" -#: warehouse/manage/views.py:1954 +#: warehouse/manage/views.py:1956 msgid "Invitation sent to '${username}'" msgstr "" -#: warehouse/manage/views.py:2001 +#: warehouse/manage/views.py:2003 msgid "Could not find role invitation." msgstr "" -#: warehouse/manage/views.py:2012 +#: warehouse/manage/views.py:2014 msgid "Invitation already expired." msgstr "" -#: warehouse/manage/views.py:2036 +#: warehouse/manage/views.py:2038 msgid "Invitation revoked from '${username}'." msgstr "" @@ -585,7 +585,7 @@ msgstr "" #: warehouse/templates/base.html:172 #: warehouse/templates/includes/flash-messages.html:30 #: warehouse/templates/includes/session-notifications.html:20 -#: warehouse/templates/manage/account.html:696 +#: warehouse/templates/manage/account.html:706 #: warehouse/templates/manage/documentation.html:27 #: warehouse/templates/manage/manage_base.html:278 #: warehouse/templates/manage/manage_base.html:330 @@ -2180,221 +2180,226 @@ msgid "" "tokens to your account." msgstr "" -#: warehouse/templates/manage/account.html:466 -msgid "Account created" +#: warehouse/templates/manage/account.html:467 +#: warehouse/templates/manage/account.html:627 +msgid "Token scope: entire account" msgstr "" #: warehouse/templates/manage/account.html:469 +#: warehouse/templates/manage/account.html:629 +#, python-format +msgid "Token scope: Project %(project_name)s" +msgstr "" + +#: warehouse/templates/manage/account.html:472 +#, python-format +msgid "Expires: %(exp)s" +msgstr "" + +#: warehouse/templates/manage/account.html:478 +msgid "Account created" +msgstr "" + +#: warehouse/templates/manage/account.html:481 msgid "Logged in" msgstr "" -#: warehouse/templates/manage/account.html:471 +#: warehouse/templates/manage/account.html:483 msgid "Two factor method:" msgstr "" -#: warehouse/templates/manage/account.html:473 +#: warehouse/templates/manage/account.html:485 #: warehouse/templates/manage/release.html:58 msgid "None" msgstr "" -#: warehouse/templates/manage/account.html:475 +#: warehouse/templates/manage/account.html:487 #: warehouse/templates/manage/manage_base.html:75 msgid "Security device (WebAuthn)" msgstr "" -#: warehouse/templates/manage/account.html:477 +#: warehouse/templates/manage/account.html:489 #: warehouse/templates/manage/manage_base.html:62 msgid "" "Authentication application (TOTP)" msgstr "" -#: warehouse/templates/manage/account.html:479 +#: warehouse/templates/manage/account.html:491 msgid "Recovery code" msgstr "" -#: warehouse/templates/manage/account.html:484 +#: warehouse/templates/manage/account.html:496 msgid "Login failed" msgstr "" -#: warehouse/templates/manage/account.html:487 +#: warehouse/templates/manage/account.html:499 msgid "- Basic Auth (Upload endpoint)" msgstr "" -#: warehouse/templates/manage/account.html:492 -#: warehouse/templates/manage/account.html:511 +#: warehouse/templates/manage/account.html:504 +#: warehouse/templates/manage/account.html:523 #: warehouse/templates/manage/history.html:84 msgid "Reason:" msgstr "" -#: warehouse/templates/manage/account.html:494 -#: warehouse/templates/manage/account.html:513 +#: warehouse/templates/manage/account.html:506 +#: warehouse/templates/manage/account.html:525 msgid "Incorrect Password" msgstr "" -#: warehouse/templates/manage/account.html:496 +#: warehouse/templates/manage/account.html:508 msgid "Invalid two factor (TOTP)" msgstr "" -#: warehouse/templates/manage/account.html:498 +#: warehouse/templates/manage/account.html:510 msgid "Invalid two factor (WebAuthn)" msgstr "" -#: warehouse/templates/manage/account.html:500 -#: warehouse/templates/manage/account.html:502 +#: warehouse/templates/manage/account.html:512 +#: warehouse/templates/manage/account.html:514 msgid "Invalid two factor (Recovery code)" msgstr "" -#: warehouse/templates/manage/account.html:509 +#: warehouse/templates/manage/account.html:521 msgid "Session reauthentication failed" msgstr "" -#: warehouse/templates/manage/account.html:520 +#: warehouse/templates/manage/account.html:532 msgid "Email added to account" msgstr "" -#: warehouse/templates/manage/account.html:523 +#: warehouse/templates/manage/account.html:535 msgid "Email removed from account" msgstr "" -#: warehouse/templates/manage/account.html:526 +#: warehouse/templates/manage/account.html:538 msgid "Email verified" msgstr "" -#: warehouse/templates/manage/account.html:529 +#: warehouse/templates/manage/account.html:541 msgid "Email reverified" msgstr "" -#: warehouse/templates/manage/account.html:533 +#: warehouse/templates/manage/account.html:545 msgid "Primary email changed" msgstr "" -#: warehouse/templates/manage/account.html:535 +#: warehouse/templates/manage/account.html:547 msgid "Old primary email:" msgstr "" -#: warehouse/templates/manage/account.html:536 +#: warehouse/templates/manage/account.html:548 msgid "New primary email:" msgstr "" -#: warehouse/templates/manage/account.html:539 +#: warehouse/templates/manage/account.html:551 msgid "Primary email set" msgstr "" -#: warehouse/templates/manage/account.html:545 +#: warehouse/templates/manage/account.html:557 msgid "Email sent" msgstr "" -#: warehouse/templates/manage/account.html:547 +#: warehouse/templates/manage/account.html:559 msgid "From:" msgstr "" -#: warehouse/templates/manage/account.html:548 +#: warehouse/templates/manage/account.html:560 msgid "To:" msgstr "" -#: warehouse/templates/manage/account.html:549 +#: warehouse/templates/manage/account.html:561 msgid "Subject:" msgstr "" -#: warehouse/templates/manage/account.html:553 +#: warehouse/templates/manage/account.html:565 msgid "Password reset requested" msgstr "" -#: warehouse/templates/manage/account.html:555 +#: warehouse/templates/manage/account.html:567 msgid "Password reset attempted" msgstr "" -#: warehouse/templates/manage/account.html:557 +#: warehouse/templates/manage/account.html:569 msgid "Password successfully reset" msgstr "" -#: warehouse/templates/manage/account.html:559 +#: warehouse/templates/manage/account.html:571 msgid "Password successfully changed" msgstr "" -#: warehouse/templates/manage/account.html:562 +#: warehouse/templates/manage/account.html:574 msgid "Two factor authentication added" msgstr "" -#: warehouse/templates/manage/account.html:565 -#: warehouse/templates/manage/account.html:575 +#: warehouse/templates/manage/account.html:577 +#: warehouse/templates/manage/account.html:587 msgid "" "Method: Security device (WebAuthn)" msgstr "" -#: warehouse/templates/manage/account.html:566 -#: warehouse/templates/manage/account.html:576 +#: warehouse/templates/manage/account.html:578 +#: warehouse/templates/manage/account.html:588 msgid "Device name:" msgstr "" -#: warehouse/templates/manage/account.html:568 -#: warehouse/templates/manage/account.html:578 +#: warehouse/templates/manage/account.html:580 +#: warehouse/templates/manage/account.html:590 msgid "" "Method: Authentication application (TOTP)" msgstr "" -#: warehouse/templates/manage/account.html:572 +#: warehouse/templates/manage/account.html:584 msgid "Two factor authentication removed" msgstr "" -#: warehouse/templates/manage/account.html:583 +#: warehouse/templates/manage/account.html:595 msgid "Recovery codes generated" msgstr "" -#: warehouse/templates/manage/account.html:587 +#: warehouse/templates/manage/account.html:599 msgid "Recovery codes regenerated" msgstr "" -#: warehouse/templates/manage/account.html:591 +#: warehouse/templates/manage/account.html:603 msgid "Recovery code used for login" msgstr "" -#: warehouse/templates/manage/account.html:597 +#: warehouse/templates/manage/account.html:609 #: warehouse/templates/manage/history.html:65 msgid "API token added" msgstr "" -#: warehouse/templates/manage/account.html:599 -#: warehouse/templates/manage/account.html:614 +#: warehouse/templates/manage/account.html:611 +#: warehouse/templates/manage/account.html:624 #: warehouse/templates/manage/history.html:69 #: warehouse/templates/manage/history.html:76 msgid "Token name:" msgstr "" -#: warehouse/templates/manage/account.html:601 -#: warehouse/templates/manage/account.html:617 -msgid "Token scope: entire account" -msgstr "" - -#: warehouse/templates/manage/account.html:603 -#: warehouse/templates/manage/account.html:619 -#, python-format -msgid "Token scope: Project %(project_name)s" -msgstr "" - -#: warehouse/templates/manage/account.html:608 +#: warehouse/templates/manage/account.html:618 #: warehouse/templates/manage/history.html:72 msgid "API token removed" msgstr "" -#: warehouse/templates/manage/account.html:609 -#: warehouse/templates/manage/account.html:615 +#: warehouse/templates/manage/account.html:619 +#: warehouse/templates/manage/account.html:625 msgid "Unique identifier:" msgstr "" -#: warehouse/templates/manage/account.html:612 +#: warehouse/templates/manage/account.html:622 msgid "API token automatically removed for security reasons" msgstr "" -#: warehouse/templates/manage/account.html:621 +#: warehouse/templates/manage/account.html:631 #, python-format msgid "Reason: Token found at public url" msgstr "" -#: warehouse/templates/manage/account.html:630 +#: warehouse/templates/manage/account.html:640 #, python-format msgid "" "Events appear here as security-related actions occur on your account. If " @@ -2402,42 +2407,42 @@ msgid "" "your account as soon as possible." msgstr "" -#: warehouse/templates/manage/account.html:635 +#: warehouse/templates/manage/account.html:645 msgid "Recent account activity" msgstr "" -#: warehouse/templates/manage/account.html:637 +#: warehouse/templates/manage/account.html:647 #: warehouse/templates/manage/history.html:107 msgid "Event" msgstr "" -#: warehouse/templates/manage/account.html:638 -#: warehouse/templates/manage/account.html:646 +#: warehouse/templates/manage/account.html:648 +#: warehouse/templates/manage/account.html:656 #: warehouse/templates/manage/history.html:108 #: warehouse/templates/manage/history.html:117 msgid "Date / time" msgstr "" -#: warehouse/templates/manage/account.html:639 -#: warehouse/templates/manage/account.html:650 +#: warehouse/templates/manage/account.html:649 +#: warehouse/templates/manage/account.html:660 #: warehouse/templates/manage/history.html:109 #: warehouse/templates/manage/history.html:121 msgid "IP address" msgstr "" -#: warehouse/templates/manage/account.html:658 +#: warehouse/templates/manage/account.html:668 msgid "Events will appear here as security-related actions occur on your account." msgstr "" -#: warehouse/templates/manage/account.html:665 +#: warehouse/templates/manage/account.html:675 msgid "Delete account" msgstr "" -#: warehouse/templates/manage/account.html:668 +#: warehouse/templates/manage/account.html:678 msgid "Cannot delete account" msgstr "" -#: warehouse/templates/manage/account.html:670 +#: warehouse/templates/manage/account.html:680 #, python-format msgid "" "\n" @@ -2452,7 +2457,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: warehouse/templates/manage/account.html:675 +#: warehouse/templates/manage/account.html:685 msgid "" "\n" " You must transfer ownership or delete this project before you " @@ -2466,23 +2471,23 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: warehouse/templates/manage/account.html:685 +#: warehouse/templates/manage/account.html:695 #, python-format msgid "" "transfer ownership or delete project" msgstr "" -#: warehouse/templates/manage/account.html:694 +#: warehouse/templates/manage/account.html:704 #: warehouse/templates/manage/token.html:169 msgid "Proceed with caution!" msgstr "" -#: warehouse/templates/manage/account.html:697 +#: warehouse/templates/manage/account.html:707 msgid "You will not be able to recover your account after you delete it" msgstr "" -#: warehouse/templates/manage/account.html:699 +#: warehouse/templates/manage/account.html:709 msgid "Delete your PyPI account" msgstr "" From 3cb51cad0e80b84c5b7ba09288d880c267b751e7 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 11 Apr 2022 13:13:18 -0400 Subject: [PATCH 3/7] manage/views: remove outdated note --- warehouse/manage/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/warehouse/manage/views.py b/warehouse/manage/views.py index b61c9f03e898..5ad509cc352e 100644 --- a/warehouse/manage/views.py +++ b/warehouse/manage/views.py @@ -879,9 +879,6 @@ def create_macaroon(self): response = {**self.default_response} if form.validate(): - # NOTE: The caveat order here is important: the UI assumes - # that the permissions caveat comes first in any recorded - # events. macaroon_caveats = [{"permissions": form.validated_scope, "version": 1}] serialized_macaroon, macaroon = self.macaroon_service.create_macaroon( location=self.request.domain, From 3e4c601ffd5fca9176243d418afe00dd8ab5a2aa Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 11 Apr 2022 13:29:40 -0400 Subject: [PATCH 4/7] warehouse, tests: `Macaroon.permissions -> Macaroon.permissions_caveat` Emphasizes that this is the entire caveat, and not just the permissions body. --- tests/unit/integration/github/test_utils.py | 5 ++++- tests/unit/manage/test_views.py | 4 ++-- warehouse/integrations/github/utils.py | 4 +++- warehouse/macaroons/models.py | 2 +- warehouse/macaroons/services.py | 4 +++- warehouse/manage/views.py | 5 +++-- .../versions/84262e097c26_rename_caveats_to_permissions.py | 4 ++-- warehouse/templates/manage/account.html | 4 ++-- warehouse/templates/manage/token.html | 6 +++--- 9 files changed, 23 insertions(+), 15 deletions(-) diff --git a/tests/unit/integration/github/test_utils.py b/tests/unit/integration/github/test_utils.py index 24ef54955dbf..7f9d87f797b1 100644 --- a/tests/unit/integration/github/test_utils.py +++ b/tests/unit/integration/github/test_utils.py @@ -554,7 +554,10 @@ def metrics_increment(key): user_id = uuid.UUID(bytes=b"0" * 16) user = pretend.stub(id=user_id) database_macaroon = pretend.stub( - user=user, id=12, permissions="user", description="foo" + user=user, + id=12, + permissions_caveat={"permissions": "user", "version": 1}, + description="foo", ) find = pretend.call_recorder(lambda *a, **kw: database_macaroon) diff --git a/tests/unit/manage/test_views.py b/tests/unit/manage/test_views.py index f25976769481..92b91be50d03 100644 --- a/tests/unit/manage/test_views.py +++ b/tests/unit/manage/test_views.py @@ -2162,7 +2162,7 @@ def test_delete_macaroon_dangerous_redirect(self, monkeypatch): assert macaroon_service.delete_macaroon.calls == [] def test_delete_macaroon(self, monkeypatch): - macaroon = pretend.stub(description="fake macaroon", permissions="user") + macaroon = pretend.stub(description="fake macaroon", permissions_caveat="user") macaroon_service = pretend.stub( delete_macaroon=pretend.call_recorder(lambda id: pretend.stub()), find_macaroon=pretend.call_recorder(lambda id: macaroon), @@ -2219,7 +2219,7 @@ def test_delete_macaroon(self, monkeypatch): def test_delete_macaroon_records_events_for_each_project(self, monkeypatch): macaroon = pretend.stub( description="fake macaroon", - permissions={"projects": ["foo", "bar"]}, + permissions_caveat={"projects": ["foo", "bar"]}, ) macaroon_service = pretend.stub( delete_macaroon=pretend.call_recorder(lambda id: pretend.stub()), diff --git a/warehouse/integrations/github/utils.py b/warehouse/integrations/github/utils.py index fa6322cfaa45..ef77f2621440 100644 --- a/warehouse/integrations/github/utils.py +++ b/warehouse/integrations/github/utils.py @@ -251,7 +251,9 @@ def _analyze_disclosure(request, disclosure_record, origin): additional={ "macaroon_id": str(database_macaroon.id), "public_url": disclosure.public_url, - "permissions": database_macaroon.permissions, + "permissions": database_macaroon.permissions_caveat.get( + "permissions", "user" + ), "description": database_macaroon.description, }, ) diff --git a/warehouse/macaroons/models.py b/warehouse/macaroons/models.py index 572558cce6e2..b5bd5dbb3df2 100644 --- a/warehouse/macaroons/models.py +++ b/warehouse/macaroons/models.py @@ -54,7 +54,7 @@ class Macaroon(db.Model): # Human-readable "permissions" for this macaroon, corresponding to the # body of the permissions ("V1") caveat. - permissions = Column(JSONB, nullable=False, server_default=sql.text("'{}'")) + permissions_caveat = Column(JSONB, nullable=False, server_default=sql.text("'{}'")) # It might be better to move this default into the database, that way we # make it less likely that something does it incorrectly (since the diff --git a/warehouse/macaroons/services.py b/warehouse/macaroons/services.py index a1a691df3049..bb27125f90b4 100644 --- a/warehouse/macaroons/services.py +++ b/warehouse/macaroons/services.py @@ -139,7 +139,9 @@ def create_macaroon(self, location, user_id, description, caveats): # permissions caveat in the DB, so that we can display scope information # in the UI. permissions = next(c for c in caveats if "permissions" in c) # pragma: no cover - dm = Macaroon(user=user, description=description, permissions=permissions) + dm = Macaroon( + user=user, description=description, permissions_caveat=permissions + ) self.db.add(dm) self.db.flush() diff --git a/warehouse/manage/views.py b/warehouse/manage/views.py index 5ad509cc352e..458ade5b97b2 100644 --- a/warehouse/manage/views.py +++ b/warehouse/manage/views.py @@ -941,11 +941,12 @@ def delete_macaroon(self): tag="account:api_token:removed", additional={"macaroon_id": form.macaroon_id.data}, ) - if "projects" in macaroon.permissions: + if "projects" in macaroon.permissions_caveat: projects = [ project for project in self.request.user.projects - if project.normalized_name in macaroon.permissions["projects"] + if project.normalized_name + in macaroon.permissions_caveat["projects"] ] for project in projects: project.record_event( diff --git a/warehouse/migrations/versions/84262e097c26_rename_caveats_to_permissions.py b/warehouse/migrations/versions/84262e097c26_rename_caveats_to_permissions.py index 54392e95c9b2..9bee3f5df6fa 100644 --- a/warehouse/migrations/versions/84262e097c26_rename_caveats_to_permissions.py +++ b/warehouse/migrations/versions/84262e097c26_rename_caveats_to_permissions.py @@ -24,8 +24,8 @@ def upgrade(): - op.alter_column("macaroons", "caveats", new_column_name="permissions") + op.alter_column("macaroons", "caveats", new_column_name="permissions_caveat") def downgrade(): - op.alter_column("macaroons", "permissions", new_column_name="caveats") + op.alter_column("macaroons", "permissions_caveat", new_column_name="caveats") diff --git a/warehouse/templates/manage/account.html b/warehouse/templates/manage/account.html index 8dc63616433a..25f71c765cea 100644 --- a/warehouse/templates/manage/account.html +++ b/warehouse/templates/manage/account.html @@ -141,10 +141,10 @@ {% trans %}Scope{% endtrans %} - {% if macaroon.permissions.permissions == 'user' %} + {% if macaroon.permissions_caveat.permissions == 'user' %} {% trans %}All projects{% endtrans %} {% else %} - {% for project in macaroon.permissions.get("permissions")['projects'] %} + {% for project in macaroon.permissions_caveat.get("permissions")['projects'] %} {{ project }} {% endfor %} {% endif %} diff --git a/warehouse/templates/manage/token.html b/warehouse/templates/manage/token.html index eb69e8774678..4d5040550947 100644 --- a/warehouse/templates/manage/token.html +++ b/warehouse/templates/manage/token.html @@ -38,10 +38,10 @@

    {{ title }}

    {% trans macaroon_description=macaroon.description %}Token for "{{ macaroon_description }}"{% endtrans %}

    {% trans %}Permissions:{% endtrans %} {% trans %}Upload packages{% endtrans %}
    - {% if macaroon.permissions.permissions == "user" %} + {% if macaroon.permissions_caveat.permissions == "user" %} {% trans %}Scope:{% endtrans %} {% trans %}Entire account (all projects){% endtrans %} {% else %} - {% trans %}Scope:{% endtrans %} {% trans project=macaroon.permissions.permissions.projects[0] %}Project "{{ project }}"{% endtrans %} + {% trans %}Scope:{% endtrans %} {% trans project=macaroon.permissions_caveat.permissions.projects[0] %}Project "{{ project }}"{% endtrans %} {% endif %}

    @@ -78,7 +78,7 @@

    {% trans %}Using this token{% endtrans %}

  • {% trans prefix='pypi-' %}Set your password to the token value, including the {{ prefix }} prefix{% endtrans %}
  • - {% if macaroon.permissions.permissions == "user" %} + {% if macaroon.permissions_caveat.permissions == "user" %}

    {% trans trimmed href='https://pypi.org/project/twine/', filename='$HOME/.pypirc' %} From 254569cf88619e4f528e2ad2b892fd0877832c0b Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 11 Apr 2022 13:41:05 -0400 Subject: [PATCH 5/7] warehouse: `make translations` --- warehouse/locale/messages.pot | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/warehouse/locale/messages.pot b/warehouse/locale/messages.pot index 8c42afc0193b..31d7733428cb 100644 --- a/warehouse/locale/messages.pot +++ b/warehouse/locale/messages.pot @@ -237,39 +237,39 @@ msgstr "" msgid "Generating new recovery codes will invalidate your existing codes." msgstr "" -#: warehouse/manage/views.py:1193 +#: warehouse/manage/views.py:1191 msgid "" "There have been too many attempted OpenID Connect registrations. Try " "again later." msgstr "" -#: warehouse/manage/views.py:1874 +#: warehouse/manage/views.py:1872 msgid "User '${username}' already has ${role_name} role for project" msgstr "" -#: warehouse/manage/views.py:1885 +#: warehouse/manage/views.py:1883 msgid "" "User '${username}' does not have a verified primary email address and " "cannot be added as a ${role_name} for project" msgstr "" -#: warehouse/manage/views.py:1898 +#: warehouse/manage/views.py:1896 msgid "User '${username}' already has an active invite. Please try again later." msgstr "" -#: warehouse/manage/views.py:1956 +#: warehouse/manage/views.py:1954 msgid "Invitation sent to '${username}'" msgstr "" -#: warehouse/manage/views.py:2003 +#: warehouse/manage/views.py:2001 msgid "Could not find role invitation." msgstr "" -#: warehouse/manage/views.py:2014 +#: warehouse/manage/views.py:2012 msgid "Invitation already expired." msgstr "" -#: warehouse/manage/views.py:2038 +#: warehouse/manage/views.py:2036 msgid "Invitation revoked from '${username}'." msgstr "" From d35585db647c58896df317a6ff545ae41b87074b Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 11 Apr 2022 16:03:09 -0400 Subject: [PATCH 6/7] warehouse/templates: handle stale event caveats Prior to these changes, the `caveats` field in API token events was a dictionary, not a list. --- warehouse/templates/manage/account.html | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/warehouse/templates/manage/account.html b/warehouse/templates/manage/account.html index 25f71c765cea..2b6c7856d7bd 100644 --- a/warehouse/templates/manage/account.html +++ b/warehouse/templates/manage/account.html @@ -609,9 +609,19 @@

    {% trans %}Security history{% endtrans %}

    {% trans %}API token added{% endtrans %}
    {% trans %}Token name:{% endtrans %} {{ event.additional.description }}
    - {% for caveat in event.additional.caveats %} - {{ caveat_detail(caveat) }} - {% endfor %} + {# + NOTE: Old events contain a single caveat dictionary, rather than a list of caveats. + + This check can be deleted roughly 90 days after merge, since events older than + 90 days are not presented to the user. + #} + {% if event.additional.caveats is mapping %} + {{ caveat_detail(event.additional.caveats) }} + {% else %} + {% for caveat in event.additional.caveats %} + {{ caveat_detail(caveat) }} + {% endfor %} + {% endif %}
    {% elif event.tag == "account:api_token:removed" %} From d3d0514eb82c1a7c155da3d70b6031034c0c9750 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 11 Apr 2022 16:33:35 -0400 Subject: [PATCH 7/7] warehouse: `make translations` --- warehouse/locale/messages.pot | 50 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/warehouse/locale/messages.pot b/warehouse/locale/messages.pot index 31d7733428cb..45cbb56c6e03 100644 --- a/warehouse/locale/messages.pot +++ b/warehouse/locale/messages.pot @@ -585,7 +585,7 @@ msgstr "" #: warehouse/templates/base.html:172 #: warehouse/templates/includes/flash-messages.html:30 #: warehouse/templates/includes/session-notifications.html:20 -#: warehouse/templates/manage/account.html:706 +#: warehouse/templates/manage/account.html:716 #: warehouse/templates/manage/documentation.html:27 #: warehouse/templates/manage/manage_base.html:278 #: warehouse/templates/manage/manage_base.html:330 @@ -2181,12 +2181,12 @@ msgid "" msgstr "" #: warehouse/templates/manage/account.html:467 -#: warehouse/templates/manage/account.html:627 +#: warehouse/templates/manage/account.html:637 msgid "Token scope: entire account" msgstr "" #: warehouse/templates/manage/account.html:469 -#: warehouse/templates/manage/account.html:629 +#: warehouse/templates/manage/account.html:639 #, python-format msgid "Token scope: Project %(project_name)s" msgstr "" @@ -2374,32 +2374,32 @@ msgid "API token added" msgstr "" #: warehouse/templates/manage/account.html:611 -#: warehouse/templates/manage/account.html:624 +#: warehouse/templates/manage/account.html:634 #: warehouse/templates/manage/history.html:69 #: warehouse/templates/manage/history.html:76 msgid "Token name:" msgstr "" -#: warehouse/templates/manage/account.html:618 +#: warehouse/templates/manage/account.html:628 #: warehouse/templates/manage/history.html:72 msgid "API token removed" msgstr "" -#: warehouse/templates/manage/account.html:619 -#: warehouse/templates/manage/account.html:625 +#: warehouse/templates/manage/account.html:629 +#: warehouse/templates/manage/account.html:635 msgid "Unique identifier:" msgstr "" -#: warehouse/templates/manage/account.html:622 +#: warehouse/templates/manage/account.html:632 msgid "API token automatically removed for security reasons" msgstr "" -#: warehouse/templates/manage/account.html:631 +#: warehouse/templates/manage/account.html:641 #, python-format msgid "Reason: Token found at public url" msgstr "" -#: warehouse/templates/manage/account.html:640 +#: warehouse/templates/manage/account.html:650 #, python-format msgid "" "Events appear here as security-related actions occur on your account. If " @@ -2407,42 +2407,42 @@ msgid "" "your account as soon as possible." msgstr "" -#: warehouse/templates/manage/account.html:645 +#: warehouse/templates/manage/account.html:655 msgid "Recent account activity" msgstr "" -#: warehouse/templates/manage/account.html:647 +#: warehouse/templates/manage/account.html:657 #: warehouse/templates/manage/history.html:107 msgid "Event" msgstr "" -#: warehouse/templates/manage/account.html:648 -#: warehouse/templates/manage/account.html:656 +#: warehouse/templates/manage/account.html:658 +#: warehouse/templates/manage/account.html:666 #: warehouse/templates/manage/history.html:108 #: warehouse/templates/manage/history.html:117 msgid "Date / time" msgstr "" -#: warehouse/templates/manage/account.html:649 -#: warehouse/templates/manage/account.html:660 +#: warehouse/templates/manage/account.html:659 +#: warehouse/templates/manage/account.html:670 #: warehouse/templates/manage/history.html:109 #: warehouse/templates/manage/history.html:121 msgid "IP address" msgstr "" -#: warehouse/templates/manage/account.html:668 +#: warehouse/templates/manage/account.html:678 msgid "Events will appear here as security-related actions occur on your account." msgstr "" -#: warehouse/templates/manage/account.html:675 +#: warehouse/templates/manage/account.html:685 msgid "Delete account" msgstr "" -#: warehouse/templates/manage/account.html:678 +#: warehouse/templates/manage/account.html:688 msgid "Cannot delete account" msgstr "" -#: warehouse/templates/manage/account.html:680 +#: warehouse/templates/manage/account.html:690 #, python-format msgid "" "\n" @@ -2457,7 +2457,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: warehouse/templates/manage/account.html:685 +#: warehouse/templates/manage/account.html:695 msgid "" "\n" " You must transfer ownership or delete this project before you " @@ -2471,23 +2471,23 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: warehouse/templates/manage/account.html:695 +#: warehouse/templates/manage/account.html:705 #, python-format msgid "" "transfer ownership or delete project" msgstr "" -#: warehouse/templates/manage/account.html:704 +#: warehouse/templates/manage/account.html:714 #: warehouse/templates/manage/token.html:169 msgid "Proceed with caution!" msgstr "" -#: warehouse/templates/manage/account.html:707 +#: warehouse/templates/manage/account.html:717 msgid "You will not be able to recover your account after you delete it" msgstr "" -#: warehouse/templates/manage/account.html:709 +#: warehouse/templates/manage/account.html:719 msgid "Delete your PyPI account" msgstr ""