From 34f5e964e66c030af8de6e1b52636d8c5d4c5add Mon Sep 17 00:00:00 2001 From: Rachel Date: Thu, 29 Aug 2019 14:03:26 -0400 Subject: [PATCH 01/96] Psuedo code added for issue #6255. --- warehouse/macaroons/caveats.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/warehouse/macaroons/caveats.py b/warehouse/macaroons/caveats.py index fa024f78319b..fa150a2d54a7 100644 --- a/warehouse/macaroons/caveats.py +++ b/warehouse/macaroons/caveats.py @@ -16,6 +16,8 @@ from warehouse.packaging.models import Project +from datetime import datetime + class InvalidMacaroon(Exception): ... @@ -67,6 +69,16 @@ def verify(self, predicate): projects = permissions.get("projects") if projects is None: raise InvalidMacaroon("invalid projects in predicate") + + ''' + time = macaroon time created + if time > curr time - user selected amount of time: + raise InvalidMacaroon("time has expired.") + + project_version = macaroon project version + if project_version == a project version that already exists: + raise InvalidMacaroon("project already exists") + ''' return self.verify_projects(projects) From 545dea0dbfd468ccb6f7a1190165622c18a7a2d7 Mon Sep 17 00:00:00 2001 From: Rachel Date: Fri, 30 Aug 2019 13:44:01 -0400 Subject: [PATCH 02/96] Added caveats for project version and macaroon expiration time. --- warehouse/macaroons/caveats.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/warehouse/macaroons/caveats.py b/warehouse/macaroons/caveats.py index fa150a2d54a7..4046a375c098 100644 --- a/warehouse/macaroons/caveats.py +++ b/warehouse/macaroons/caveats.py @@ -18,6 +18,8 @@ from datetime import datetime +from datetime import timedelta + class InvalidMacaroon(Exception): ... @@ -33,7 +35,6 @@ def verify(self, predicate): def __call__(self, predicate): return self.verify(predicate) - class V1Caveat(Caveat): def verify_projects(self, projects): # First, ensure that we're actually operating in @@ -44,10 +45,14 @@ def verify_projects(self, projects): ) project = self.verifier.context - if project.normalized_name in projects: - return True - - raise InvalidMacaroon("project-scoped token matches no projects") + if project.normalized_name not in projects: + raise InvalidMacaroon("project-scoped token matches no projects") + + #project version -- need to test + if project.version in projects: + raise InvalidMacaroon("project version already exists") + + return True def verify(self, predicate): try: @@ -69,16 +74,6 @@ def verify(self, predicate): projects = permissions.get("projects") if projects is None: raise InvalidMacaroon("invalid projects in predicate") - - ''' - time = macaroon time created - if time > curr time - user selected amount of time: - raise InvalidMacaroon("time has expired.") - - project_version = macaroon project version - if project_version == a project version that already exists: - raise InvalidMacaroon("project already exists") - ''' return self.verify_projects(projects) @@ -98,3 +93,7 @@ def verify(self, key): return self.verifier.verify(self.macaroon, key) except pymacaroons.exceptions.MacaroonInvalidSignatureException: raise InvalidMacaroon("invalid macaroon signature") + + time = self.macaroon.created + if time + timedelta(minutes = 30) < datetime.now(): + raise InvalidMacaroon("time has expired") From cd4e8a258706a4db195e57f6b73cea2a6ed089b6 Mon Sep 17 00:00:00 2001 From: Rachel Date: Thu, 5 Sep 2019 14:45:09 -0400 Subject: [PATCH 03/96] Added tests for expiration time. --- tests/unit/macaroons/test_caveats.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/unit/macaroons/test_caveats.py b/tests/unit/macaroons/test_caveats.py index 50eedbb4c8c0..108e679c8980 100644 --- a/tests/unit/macaroons/test_caveats.py +++ b/tests/unit/macaroons/test_caveats.py @@ -19,7 +19,7 @@ from warehouse.macaroons.caveats import Caveat, InvalidMacaroon, V1Caveat, Verifier -from ...common.db.packaging import ProjectFactory +from ...common.db.packaging import ProjectFactory, ReleaseFactory class TestCaveat: @@ -93,7 +93,18 @@ def test_verify_project(self, db_request): predicate = {"version": 1, "permissions": {"projects": ["foobar"]}} assert caveat(json.dumps(predicate)) is True + + #added + def test_verify_project_version(self, db_request): + project = ProjectFactory.create(name="foobar") + release = ReleaseFactory.create(project=project, version="1.0") + verifier = pretend.stub(context=project) + caveat = V1Caveat(verifier) + predicate = {"version": "1.0", "permissions": {"projects": ["foobar"]}} + with pytest.raises(InvalidMacaroon): + caveat(json.dumps(predicate)) + class TestVerifier: def test_creation(self): From 03ea2ce8b5a9a11375ed2456b12176b641d35f42 Mon Sep 17 00:00:00 2001 From: Rachel Date: Thu, 5 Sep 2019 14:45:54 -0400 Subject: [PATCH 04/96] Updated caveats for expiration time and release. --- warehouse/macaroons/caveats.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/warehouse/macaroons/caveats.py b/warehouse/macaroons/caveats.py index 4046a375c098..08d8b3761dca 100644 --- a/warehouse/macaroons/caveats.py +++ b/warehouse/macaroons/caveats.py @@ -14,7 +14,7 @@ import pymacaroons -from warehouse.packaging.models import Project +from warehouse.packaging.models import Project, Release from datetime import datetime @@ -47,9 +47,9 @@ def verify_projects(self, projects): project = self.verifier.context if project.normalized_name not in projects: raise InvalidMacaroon("project-scoped token matches no projects") - - #project version -- need to test - if project.version in projects: + + #added + if project.latest_version in project.all_versions: raise InvalidMacaroon("project version already exists") return True @@ -94,6 +94,7 @@ def verify(self, key): except pymacaroons.exceptions.MacaroonInvalidSignatureException: raise InvalidMacaroon("invalid macaroon signature") - time = self.macaroon.created - if time + timedelta(minutes = 30) < datetime.now(): + #added + expiration = self.macaroon.expiration + if expiration > datetime.now(): raise InvalidMacaroon("time has expired") From 1447ed7868179de133761f0644f7272c2d5d6152 Mon Sep 17 00:00:00 2001 From: Rachel Date: Thu, 5 Sep 2019 14:49:13 -0400 Subject: [PATCH 05/96] Added macaroon fields for expiration time and release. --- warehouse/manage/forms.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/warehouse/manage/forms.py b/warehouse/manage/forms.py index 64dcf92ee79e..9d78703c28a8 100644 --- a/warehouse/manage/forms.py +++ b/warehouse/manage/forms.py @@ -26,6 +26,9 @@ WebAuthnCredentialMixin, ) +from datetime import datetime +from datetime import timedelta + class RoleNameMixin: @@ -185,14 +188,16 @@ def validate_label(self, field): raise wtforms.validators.ValidationError(f"Label '{label}' already in use") +#modified class CreateMacaroonForm(forms.Form): - __params__ = ["description", "token_scope"] + __params__ = ["description", "token_scope", "release", "expiration",] - def __init__(self, *args, user_id, macaroon_service, project_names, **kwargs): + def __init__(self, *args, user_id, macaroon_service, project_names, all_projects, **kwargs): super().__init__(*args, **kwargs) self.user_id = user_id self.macaroon_service = macaroon_service self.project_names = project_names + self.all_projects = all_projects description = wtforms.StringField( validators=[ @@ -207,6 +212,18 @@ def __init__(self, *args, user_id, macaroon_service, project_names, **kwargs): validators=[wtforms.validators.DataRequired(message="Specify the token scope")] ) + release = wtforms.StringField( + validators=[wtforms.validators.DataRequired(message="Specify the token scope")] + ) + + #expiration = days + ':' + hours + ':' + minutes + #cannot be less than 1 min cannot be longer than 1 year + expiration = wtforms.DateTimeField( + validators=[ + wtforms.validators.DataRequired(message="Specify an expiration time"), + ] + ) + def validate_description(self, field): description = field.data @@ -245,6 +262,18 @@ def validate_token_scope(self, field): self.validated_scope = {"projects": [scope_value]} + def validate_release(self, field): + release = field.data + #valid release + #release exists + + def validate_expiration(self, field): + expiration = field.data + expiration = datetime.strptime(expiration, "%Y-%m-%dT%H:%M") + + if expiration > datetime.now() + timedelta(days=365): + raise wtforms.ValidationError("Expiration cannot be greater than one year") + class DeleteMacaroonForm(forms.Form): __params__ = ["macaroon_id"] From 0d2733d3925cf2bc72c4f7bb00c158eea148b4c9 Mon Sep 17 00:00:00 2001 From: Rachel Date: Thu, 5 Sep 2019 14:58:52 -0400 Subject: [PATCH 06/96] Added expiration field to macaroon. --- warehouse/manage/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/warehouse/manage/views.py b/warehouse/manage/views.py index 720c76a5dd36..1c89d1e95558 100644 --- a/warehouse/manage/views.py +++ b/warehouse/manage/views.py @@ -621,6 +621,11 @@ def __init__(self, request): def project_names(self): return sorted(project.normalized_name for project in self.request.user.projects) + # @property + # def project_releases(self, project): + # releases = (p for p in self.request.user.projects if p.normalized_name == project) + # return sorted(version for version in releases.all_versions) + @property def default_response(self): return { @@ -629,6 +634,7 @@ def default_response(self): user_id=self.request.user.id, macaroon_service=self.macaroon_service, project_names=self.project_names, + all_projects=self.request.user.projects ), "delete_macaroon_form": DeleteMacaroonForm( macaroon_service=self.macaroon_service @@ -652,6 +658,7 @@ def create_macaroon(self): user_id=self.request.user.id, macaroon_service=self.macaroon_service, project_names=self.project_names, + all_projects=self.request.user.projects ) response = {**self.default_response} @@ -661,6 +668,7 @@ def create_macaroon(self): location=self.request.domain, user_id=self.request.user.id, description=form.description.data, + expiration=form.expiration.data, caveats=macaroon_caveats, ) self.user_service.record_event( From a89a25e3bd17bd8d8c77e4330a620e7834c7ff4d Mon Sep 17 00:00:00 2001 From: Rachel Date: Thu, 5 Sep 2019 14:59:44 -0400 Subject: [PATCH 07/96] Added form fields for expiration and release. --- warehouse/templates/manage/token.html | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/warehouse/templates/manage/token.html b/warehouse/templates/manage/token.html index 1a3c98f997e8..65c97769013c 100644 --- a/warehouse/templates/manage/token.html +++ b/warehouse/templates/manage/token.html @@ -114,6 +114,44 @@

Add another token

Proceed with caution!

An API token scoped to your entire account will have upload permissions for all of your projects.

+ + {% if option.selected = project_value %} +
+ + +
+ {{ field_errors(create_macaroon_form.release) }} +
+
+ {% endif %} + +
+ + +
+ {{ field_errors(create_macaroon_form.expiration) }} +
+
+
From 8826d3943c30b8312ce52110b1e0edd08a225b7a Mon Sep 17 00:00:00 2001 From: Rachel Date: Fri, 6 Sep 2019 16:09:10 -0400 Subject: [PATCH 08/96] Moved release caveat. --- warehouse/macaroons/caveats.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/warehouse/macaroons/caveats.py b/warehouse/macaroons/caveats.py index 08d8b3761dca..e0bbb04193cc 100644 --- a/warehouse/macaroons/caveats.py +++ b/warehouse/macaroons/caveats.py @@ -47,10 +47,6 @@ def verify_projects(self, projects): project = self.verifier.context if project.normalized_name not in projects: raise InvalidMacaroon("project-scoped token matches no projects") - - #added - if project.latest_version in project.all_versions: - raise InvalidMacaroon("project version already exists") return True @@ -98,3 +94,7 @@ def verify(self, key): expiration = self.macaroon.expiration if expiration > datetime.now(): raise InvalidMacaroon("time has expired") + + release = self.macaroon.release + if release in self.macaroon.project.all_versions: + raise InvalidMacaroon("invalid release") From d3cb1955c7aa3b35dcbd2650ecc64e0512e9bbfb Mon Sep 17 00:00:00 2001 From: Rachel Date: Fri, 6 Sep 2019 16:09:47 -0400 Subject: [PATCH 09/96] Added potential new Macaroon fields. --- warehouse/macaroons/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/warehouse/macaroons/models.py b/warehouse/macaroons/models.py index 33373b7aa9e0..1c8490c6d63a 100644 --- a/warehouse/macaroons/models.py +++ b/warehouse/macaroons/models.py @@ -47,6 +47,9 @@ class Macaroon(db.Model): # Store some information about the Macaroon to give users some mechanism # to differentiate between them. description = Column(String(100), nullable=False) + # scope = Column(String(100), nullable=False) + # release = Column(String(100), nullable=False) + # expiration = Column(DateTime, nullable=False) created = Column(DateTime, nullable=False, server_default=sql.func.now()) last_used = Column(DateTime, nullable=True) From 71f7c1b0aa24c06ff8f853f028efafd16cdfadc7 Mon Sep 17 00:00:00 2001 From: Rachel Date: Fri, 6 Sep 2019 16:11:28 -0400 Subject: [PATCH 10/96] Added scope, release, and expiration fields to Macaroon object. --- warehouse/macaroons/services.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/warehouse/macaroons/services.py b/warehouse/macaroons/services.py index 0fff949ade55..b97b8fd3b6c2 100644 --- a/warehouse/macaroons/services.py +++ b/warehouse/macaroons/services.py @@ -115,7 +115,7 @@ def verify(self, raw_macaroon, context, principals, permission): raise InvalidMacaroon("invalid macaroon") - def create_macaroon(self, location, user_id, description, caveats): + def create_macaroon(self, location, user_id, description, scope, release, expiration, caveats): """ Returns a tuple of a new raw (serialized) macaroon and its DB model. The description provided is not embedded into the macaroon, only stored @@ -123,7 +123,8 @@ 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) + dm = Macaroon(user=user, description=description, scope=scope, release=release, + expiration=expiration, caveats=caveats) self.db.add(dm) self.db.flush() @@ -167,4 +168,4 @@ def get_macaroon_by_description(self, user_id, description): def database_macaroon_factory(context, request): - return DatabaseMacaroonService(request.db) + return DatabaseMacaroonService(request.db) \ No newline at end of file From 2e62cfca55a0b417c14198a87e3620bbe318e8e0 Mon Sep 17 00:00:00 2001 From: Rachel Date: Fri, 6 Sep 2019 16:12:05 -0400 Subject: [PATCH 11/96] Added scope, release, and expiration fields to create_macaroon. --- warehouse/manage/views.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/warehouse/manage/views.py b/warehouse/manage/views.py index 1c89d1e95558..36732d738a69 100644 --- a/warehouse/manage/views.py +++ b/warehouse/manage/views.py @@ -621,20 +621,23 @@ def __init__(self, request): def project_names(self): return sorted(project.normalized_name for project in self.request.user.projects) - # @property - # def project_releases(self, project): - # releases = (p for p in self.request.user.projects if p.normalized_name == project) - # return sorted(version for version in releases.all_versions) + @property + def project_releases(self): + releases = {} + for project in self.request.user.projects: + releases.update({project.normalized_name : project.all_versions}) + return releases @property def default_response(self): return { "project_names": self.project_names, + "releases": self.project_releases, "create_macaroon_form": CreateMacaroonForm( user_id=self.request.user.id, macaroon_service=self.macaroon_service, project_names=self.project_names, - all_projects=self.request.user.projects + releases=self.project_releases ), "delete_macaroon_form": DeleteMacaroonForm( macaroon_service=self.macaroon_service @@ -658,7 +661,7 @@ def create_macaroon(self): user_id=self.request.user.id, macaroon_service=self.macaroon_service, project_names=self.project_names, - all_projects=self.request.user.projects + releases=self.project_releases, ) response = {**self.default_response} @@ -668,6 +671,8 @@ def create_macaroon(self): location=self.request.domain, user_id=self.request.user.id, description=form.description.data, + scope=form.token_scope, + release=form.release, expiration=form.expiration.data, caveats=macaroon_caveats, ) From 29dcb8cd190693c83d8c4ca6c27713bce367a9f3 Mon Sep 17 00:00:00 2001 From: Rachel Date: Fri, 6 Sep 2019 16:12:54 -0400 Subject: [PATCH 12/96] Changed release to be a text input. --- warehouse/templates/manage/token.html | 28 +++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/warehouse/templates/manage/token.html b/warehouse/templates/manage/token.html index 65c97769013c..4ebdd1e28a27 100644 --- a/warehouse/templates/manage/token.html +++ b/warehouse/templates/manage/token.html @@ -91,6 +91,7 @@

Add another token

Permissions

Upload packages

+