diff --git a/tests/unit/oidc/models/test_activestate.py b/tests/unit/oidc/models/test_activestate.py index a1d178735566..a0bd7e128911 100644 --- a/tests/unit/oidc/models/test_activestate.py +++ b/tests/unit/oidc/models/test_activestate.py @@ -93,6 +93,21 @@ def test_stored_claims(self): assert publisher.stored_claims() == {} + def test_admin_details(self): + publisher = ActiveStatePublisher( + organization="fakeorg", + activestate_project_name="fakeproject", + actor="fakeactor", + actor_id="fakeactorid", + ) + + assert publisher.admin_details == [ + ("Organization", "fakeorg"), + ("Project", "fakeproject"), + ("Actor", "fakeactor"), + ("Actor ID", "fakeactorid"), + ] + def test_stringifies_as_project_url(self): org_name = "fakeorg" project_name = "fakeproject" diff --git a/tests/unit/oidc/models/test_core.py b/tests/unit/oidc/models/test_core.py index 9c2c4dfc03cb..671cb7072d18 100644 --- a/tests/unit/oidc/models/test_core.py +++ b/tests/unit/oidc/models/test_core.py @@ -47,6 +47,10 @@ def test_attestation_identity(self): publisher = _core.OIDCPublisher(projects=[]) assert not publisher.attestation_identity + def test_admin_details_default(self): + publisher = _core.OIDCPublisher(projects=[]) + assert publisher.admin_details == [] + @pytest.mark.parametrize( ("url", "publisher_url", "expected"), [ diff --git a/tests/unit/oidc/models/test_github.py b/tests/unit/oidc/models/test_github.py index 10a569f180fb..7e187b6c9746 100644 --- a/tests/unit/oidc/models/test_github.py +++ b/tests/unit/oidc/models/test_github.py @@ -238,6 +238,37 @@ def test_github_publisher_computed_properties(self): "ref": "someref", } + def test_github_publisher_admin_details_with_environment(self): + publisher = github.GitHubPublisher( + repository_name="fakerepo", + repository_owner="fakeowner", + repository_owner_id="fakeid", + workflow_filename="fakeworkflow.yml", + environment="fakeenv", + ) + + assert publisher.admin_details == [ + ("Repository", "fakeowner/fakerepo"), + ("Workflow", "fakeworkflow.yml"), + ("Owner ID", "fakeid"), + ("Environment", "fakeenv"), + ] + + def test_github_publisher_admin_details_without_environment(self): + publisher = github.GitHubPublisher( + repository_name="fakerepo", + repository_owner="fakeowner", + repository_owner_id="fakeid", + workflow_filename="fakeworkflow.yml", + environment="", + ) + + assert publisher.admin_details == [ + ("Repository", "fakeowner/fakerepo"), + ("Workflow", "fakeworkflow.yml"), + ("Owner ID", "fakeid"), + ] + def test_github_publisher_unaccounted_claims(self, monkeypatch): scope = pretend.stub() sentry_sdk = pretend.stub( diff --git a/tests/unit/oidc/models/test_gitlab.py b/tests/unit/oidc/models/test_gitlab.py index 8e23e14521b2..07ce44574133 100644 --- a/tests/unit/oidc/models/test_gitlab.py +++ b/tests/unit/oidc/models/test_gitlab.py @@ -249,6 +249,33 @@ def test_gitlab_publisher_computed_properties(self): "ref_path": "someref", } + def test_gitlab_publisher_admin_details_with_environment(self): + publisher = gitlab.GitLabPublisher( + project="fakerepo", + namespace="fakeowner", + workflow_filepath="subfolder/fakeworkflow.yml", + environment="fakeenv", + ) + + assert publisher.admin_details == [ + ("Project", "fakeowner/fakerepo"), + ("Workflow", "subfolder/fakeworkflow.yml"), + ("Environment", "fakeenv"), + ] + + def test_gitlab_publisher_admin_details_without_environment(self): + publisher = gitlab.GitLabPublisher( + project="fakerepo", + namespace="fakeowner", + workflow_filepath="subfolder/fakeworkflow.yml", + environment="", + ) + + assert publisher.admin_details == [ + ("Project", "fakeowner/fakerepo"), + ("Workflow", "subfolder/fakeworkflow.yml"), + ] + def test_gitlab_publisher_unaccounted_claims(self, monkeypatch): scope = pretend.stub() sentry_sdk = pretend.stub( diff --git a/tests/unit/oidc/models/test_google.py b/tests/unit/oidc/models/test_google.py index 5bb77d516f84..2ec702204872 100644 --- a/tests/unit/oidc/models/test_google.py +++ b/tests/unit/oidc/models/test_google.py @@ -34,6 +34,27 @@ def test_stringifies_as_email(self): assert str(publisher) == publisher.email + def test_google_publisher_admin_details_with_sub(self): + publisher = google.GooglePublisher( + email="fake@example.com", + sub="fakesubject", + ) + + assert publisher.admin_details == [ + ("Email", "fake@example.com"), + ("Subject", "fakesubject"), + ] + + def test_google_publisher_admin_details_without_sub(self): + publisher = google.GooglePublisher( + email="fake@example.com", + sub=None, + ) + + assert publisher.admin_details == [ + ("Email", "fake@example.com"), + ] + def test_google_publisher_all_known_claims(self): assert google.GooglePublisher.all_known_claims() == { # verifiable claims diff --git a/warehouse/admin/templates/admin/projects/detail.html b/warehouse/admin/templates/admin/projects/detail.html index 070e004503b6..28a812daea1f 100644 --- a/warehouse/admin/templates/admin/projects/detail.html +++ b/warehouse/admin/templates/admin/projects/detail.html @@ -129,7 +129,7 @@

- Upload limit + Upload limit {# Calculate effective limit #} {% set effective_upload_limit = MAX_FILESIZE %} @@ -142,16 +142,16 @@

{% set effective_upload_limit = project.organization.upload_limit %} {% set limit_source = "organization" %} {% endif %} - -
+ +
Effective limit: {{ effective_upload_limit|filesizeformat(binary=True) }} (from {{ limit_source }})
- - + +
- - - + + + @@ -193,7 +193,7 @@

{% else %} {% set upload_limit_value = '' %} {% endif %} - + MiB {% if project.upload_limit and limit_source != "project" %} @@ -204,7 +204,7 @@

- +
System default:{{ MAX_FILESIZE|filesizeformat(binary=True) }}Not configurableSystem default:{{ MAX_FILESIZE|filesizeformat(binary=True) }}Not configurable
Project limit:
Total size limitTotal size limit {# Calculate effective limit #} {% set effective_total_size_limit = MAX_PROJECT_SIZE %} @@ -217,16 +217,16 @@

{% set effective_total_size_limit = project.organization.total_size_limit %} {% set size_limit_source = "organization" %} {% endif %} - -
+ +
Effective limit: {{ effective_total_size_limit|filesizeformat(binary=True) }} (from {{ size_limit_source }})
- - + +
- - - + + + @@ -268,7 +268,7 @@

{% else %} {% set total_size_limit_value = '' %} {% endif %} - + GiB {% if project.total_size_limit and size_limit_source != "project" %} @@ -480,21 +480,35 @@

Releases

System default:{{ MAX_PROJECT_SIZE|filesizeformat(binary=True) }}Not configurableSystem default:{{ MAX_PROJECT_SIZE|filesizeformat(binary=True) }}Not configurable
Project limit:
- + + - {% for pub in oidc_publishers %} - - {% if pub.publisher_url() %} - - {% else %} - - {% endif %} - + + + {% endfor %} diff --git a/warehouse/oidc/models/_core.py b/warehouse/oidc/models/_core.py index 6908492ac46d..6ae8ab43bc7e 100644 --- a/warehouse/oidc/models/_core.py +++ b/warehouse/oidc/models/_core.py @@ -346,6 +346,14 @@ def exists(self, session) -> bool: # pragma: no cover # Only concrete subclasses are constructed. raise NotImplementedError + @property + def admin_details(self) -> list[tuple[str, str]]: + """ + Returns a list of (label, value) tuples for display in admin interface. + Each publisher should override this to provide its configuration details. + """ + return [] + class OIDCPublisher(OIDCPublisherMixin, db.Model): __tablename__ = "oidc_publishers" diff --git a/warehouse/oidc/models/activestate.py b/warehouse/oidc/models/activestate.py index 91cb10822ed5..650568517bc4 100644 --- a/warehouse/oidc/models/activestate.py +++ b/warehouse/oidc/models/activestate.py @@ -124,6 +124,16 @@ def exists(self, session) -> bool: ) ).scalar() + @property + def admin_details(self) -> list[tuple[str, str]]: + """Returns ActiveState publisher configuration details for admin display.""" + return [ + ("Organization", self.organization), + ("Project", self.activestate_project_name), + ("Actor", self.actor), + ("Actor ID", self.actor_id), + ] + @classmethod def lookup_by_claims(cls, session, signed_claims: SignedClaims) -> Self: query: Query = Query(cls).filter_by( diff --git a/warehouse/oidc/models/github.py b/warehouse/oidc/models/github.py index 017666f4c048..0f984e32918b 100644 --- a/warehouse/oidc/models/github.py +++ b/warehouse/oidc/models/github.py @@ -292,6 +292,18 @@ def exists(self, session) -> bool: ) ).scalar() + @property + def admin_details(self) -> list[tuple[str, str]]: + """Returns GitHub publisher configuration details for admin display.""" + details = [ + ("Repository", self.repository), + ("Workflow", self.workflow_filename), + ("Owner ID", self.repository_owner_id), + ] + if self.environment: + details.append(("Environment", self.environment)) + return details + class GitHubPublisher(GitHubPublisherMixin, OIDCPublisher): __tablename__ = "github_oidc_publishers" diff --git a/warehouse/oidc/models/gitlab.py b/warehouse/oidc/models/gitlab.py index 078f89e8ff07..446d0138026c 100644 --- a/warehouse/oidc/models/gitlab.py +++ b/warehouse/oidc/models/gitlab.py @@ -282,6 +282,17 @@ def exists(self, session) -> bool: ) ).scalar() + @property + def admin_details(self) -> list[tuple[str, str]]: + """Returns GitLab publisher configuration details for admin display.""" + details = [ + ("Project", self.project_path), + ("Workflow", self.workflow_filepath), + ] + if self.environment: + details.append(("Environment", self.environment)) + return details + class GitLabPublisher(GitLabPublisherMixin, OIDCPublisher): __tablename__ = "gitlab_oidc_publishers" diff --git a/warehouse/oidc/models/google.py b/warehouse/oidc/models/google.py index 13251e76a142..f5b55b44e92b 100644 --- a/warehouse/oidc/models/google.py +++ b/warehouse/oidc/models/google.py @@ -113,6 +113,16 @@ def exists(self, session) -> bool: ) ).scalar() + @property + def admin_details(self) -> list[tuple[str, str]]: + """Returns Google publisher configuration details for admin display.""" + details = [ + ("Email", self.email), + ] + if self.sub: + details.append(("Subject", self.sub)) + return details + class GooglePublisher(GooglePublisherMixin, OIDCPublisher): __tablename__ = "google_oidc_publishers"
Publisher namePublisherConfiguration URLrepr
{{ pub.publisher_name }}{{ pub.publisher_url() }}N/A{{ pub }}{{ pub.publisher_name }} + {% if pub.admin_details %} +
+ {% for label, value in pub.admin_details %} +
{{ label }}:
+
{{ value }}
+
+ {% endfor %} +
+ {% else %} + {{ pub }} + {% endif %} +
+ {% if pub.publisher_url() %} + {{ pub.publisher_url() }} + {% else %} + N/A + {% endif %} +