diff --git a/tests/unit/packaging/test_models.py b/tests/unit/packaging/test_models.py index de24269da8a3..470100fdf487 100644 --- a/tests/unit/packaging/test_models.py +++ b/tests/unit/packaging/test_models.py @@ -32,6 +32,7 @@ ) from ...common.db.packaging import ( DependencyFactory as DBDependencyFactory, + FileEventFactory as DBFileEventFactory, FileFactory as DBFileFactory, ProjectFactory as DBProjectFactory, ReleaseFactory as DBReleaseFactory, @@ -515,6 +516,60 @@ def test_github_open_issue_info_url(self, db_session, home_page, expected): release = DBReleaseFactory.create(home_page=home_page) assert release.github_open_issue_info_url == expected + def test_trusted_published_none(self, db_session): + release = DBReleaseFactory.create() + + assert not release.trusted_published + + def test_trusted_published_all(self, db_session): + release = DBReleaseFactory.create() + release_file = DBFileFactory.create( + release=release, + filename=f"{release.project.name}-{release.version}.tar.gz", + python_version="source", + ) + DBFileEventFactory.create( + source=release_file, + tag="fake:event", + additional={}, + ) + + # Without the `publisher_url` key, not considered trusted published + assert not release.trusted_published + + DBFileEventFactory.create( + source=release_file, + tag="fake:event", + additional={"publisher_url": "https://fake/url"}, + ) + + assert release.trusted_published + + def test_trusted_published_mixed(self, db_session): + release = DBReleaseFactory.create() + rfile_1 = DBFileFactory.create( + release=release, + filename=f"{release.project.name}-{release.version}.tar.gz", + python_version="source", + ) + rfile_2 = DBFileFactory.create( + release=release, + filename=f"{release.project.name}-{release.version}.whl", + python_version="bdist_wheel", + ) + DBFileEventFactory.create( + source=rfile_1, + tag="fake:event", + additional={}, + ) + DBFileEventFactory.create( + source=rfile_2, + tag="fake:event", + additional={"publisher_url": "https://fake/url"}, + ) + + assert not release.trusted_published + class TestFile: def test_requires_python(self, db_session): @@ -579,3 +634,28 @@ def test_query_paths(self, db_session): ) assert results == (expected, expected + ".metadata") + + def test_published_via_trusted_publisher(self, db_session): + project = DBProjectFactory.create() + release = DBReleaseFactory.create(project=project) + rfile = DBFileFactory.create( + release=release, + filename=f"{project.name}-{release.version}.tar.gz", + python_version="source", + ) + DBFileEventFactory.create( + source=rfile, + tag="fake:event", + additional={}, + ) + + # Without the `publisher_url` key, not considered trusted published + assert not rfile.uploaded_via_trusted_publisher + + DBFileEventFactory.create( + source=rfile, + tag="fake:event", + additional={"publisher_url": "https://fake/url"}, + ) + + assert rfile.uploaded_via_trusted_publisher diff --git a/warehouse/admin/templates/admin/projects/release_detail.html b/warehouse/admin/templates/admin/projects/release_detail.html index 93f172a99930..33235efde9d5 100644 --- a/warehouse/admin/templates/admin/projects/release_detail.html +++ b/warehouse/admin/templates/admin/projects/release_detail.html @@ -51,6 +51,18 @@

Details

Created via {{ release.uploaded_via }} + + Trusted Published? + + {% if release.trusted_published %} + + {% else %} + + + + + {% endif %} + Yanked {{ release.yanked }} @@ -70,6 +82,7 @@

Files

Package Type Python Version Uploaded via + Trusted Publisher? @@ -79,6 +92,13 @@

Files

{{ file.packagetype }} {{ file.python_version }} {{ file.uploaded_via }} + + {% if file.uploaded_via_trusted_publisher %} + + {% else %} + + {% endif %} + {% endfor %} diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index d0211bbc8f3d..8c97f4a08717 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -603,6 +603,17 @@ def has_meta(self): ] ) + @property + def trusted_published(self) -> bool: + """ + A Release can be considered published via a trusted publisher if + **all** the Files in the release are published via a trusted publisher. + """ + files = self.files.all() # type: ignore[attr-defined] + if not files: + return False + return all(file.uploaded_via_trusted_publisher for file in files) + class PackageType(str, enum.Enum): bdist_dmg = "bdist_dmg" @@ -670,6 +681,16 @@ def __table_args__(cls): # noqa comment="If True, the object has been archived to our archival bucket.", ) + @property + def uploaded_via_trusted_publisher(self) -> bool: + """Return True if the file was uploaded via a trusted publisher.""" + return ( + self.events.where( + self.Event.additional.has_key("publisher_url") # type: ignore[attr-defined] # noqa E501 + ).count() + > 0 + ) + @hybrid_property def metadata_path(self): return self.path + ".metadata"