Repository: pypi/warehouse

Issue:
URL to trusted publisher workflow is confusing
As discussed [here](https://twitter.com/HEPfeickert/status/1649181900521472001), the URL in the list of trusted publishers is a little confusing to users because it links to a workflow on a specific branch (`HEAD`) where in reality any matching workflow on any branch in the repository can publish.

Instead of a full URL to the workflow, we should display the name of the org/repo (with a link) and the filename of the workflow (without a link).


Discussion:
Relevant code:

https://github.com/pypi/warehouse/blob/99dfe5397a9e22a452139c9c69fd58ad76ced5c3/warehouse/templates/manage/manage_base.html#L510-L519

https://github.com/pypi/warehouse/blob/91c36dd98671ac39bd4a488cb407264bc645ea0a/warehouse/oidc/models.py#L325-L329

I'm not sure I understand the comment - how does a SHA get embedded, and how might it be used?

Could we convert to use the repr of the publisher? We already have this method:

https://github.com/pypi/warehouse/blob/91c36dd98671ac39bd4a488cb407264bc645ea0a/warehouse/oidc/models.py#L343-L344

Otherwise we'd have to construct some other string for this purpose, since we preserve the `publisher_url` in Events (unless we don't care about the `HEAD` aspect there either?)
> I'm not sure I understand the comment - how does a SHA get embedded, and how might it be used?

I'm not sure I understand the comment either: @woodruffw, any thoughts?

> Otherwise we'd have to construct some other string for this purpose, since we preserve the publisher_url in Events (unless we don't care about the HEAD aspect there either?)

We should probably update this to be the _actual_ URL to the workflow that _actually_ published the file (which we can construct from the OIDC claims) which will likely include the SHA, which might be what @woodruffw was talking about in that comment.

> I'm not sure I understand the comment either: @woodruffw, any thoughts?

Yeah, that's a misleading comment on my part: that was referring to somehow embedding the OIDC token's `ref` claim in various publisher management events, but that's currently not possible due to how we disconnect the state during token exchange.

> Otherwise we'd have to construct some other string for this purpose, since we preserve the publisher_url in Events (unless we don't care about the HEAD aspect there either?)

Correct, this is what the comment was referring to. IMO it'd be fine to remove the `publisher_url` from both, and replace both with individual fields for the components.

Resolving patch:
diff --git a/warehouse/manage/views/__init__.py b/warehouse/manage/views/__init__.py
--- a/warehouse/manage/views/__init__.py
+++ b/warehouse/manage/views/__init__.py
@@ -1320,7 +1320,7 @@ def add_github_oidc_publisher(self):
         )
 
         self.request.session.flash(
-            f"Added {publisher} to {self.project.name}",
+            f"Added {publisher} in {publisher.publisher_url} to {self.project.name}",
             queue="success",
         )
 
diff --git a/warehouse/oidc/models.py b/warehouse/oidc/models.py
--- a/warehouse/oidc/models.py
+++ b/warehouse/oidc/models.py
@@ -330,16 +330,14 @@ def _workflow_slug(self):
     def publisher_name(self):
         return "GitHub"
 
-    @property
-    def publisher_url(self):
-        # NOTE: Until we embed the SHA, this URL is not guaranteed to contain
-        # the exact contents of the workflow that their OIDC publisher corresponds to.
-        return f"https://github.com/{self.repository}/blob/HEAD/{self._workflow_slug}"
-
     @property
     def repository(self):
         return f"{self.repository_owner}/{self.repository_name}"
 
+    @property
+    def publisher_url(self):
+        return f"https://github.com/{self.repository}"
+
     @property
     def job_workflow_ref(self):
         return f"{self.repository}/{self._workflow_slug}"
@@ -349,7 +347,7 @@ def sub(self):
         return f"repo:{self.repository}"
 
     def __str__(self):
-        return f"{self.workflow_filename} @ {self.repository}"
+        return self.workflow_filename
 
 
 class GitHubPublisher(GitHubPublisherMixin, OIDCPublisher):
diff --git a/warehouse/oidc/views.py b/warehouse/oidc/views.py
--- a/warehouse/oidc/views.py
+++ b/warehouse/oidc/views.py
@@ -187,7 +187,7 @@ def _invalid(errors):
     serialized, dm = macaroon_service.create_macaroon(
         request.domain,
         (
-            f"OpenID token: {publisher.publisher_url} "
+            f"OpenID token: {str(publisher)} "
             f"({datetime.fromtimestamp(not_before).isoformat()})"
         ),
         [


Test patch:
diff --git a/tests/unit/manage/test_views.py b/tests/unit/manage/test_views.py
--- a/tests/unit/manage/test_views.py
+++ b/tests/unit/manage/test_views.py
@@ -6062,10 +6062,10 @@ def test_add_github_oidc_publisher_preexisting(self, monkeypatch):
             id="fakeid",
             publisher_name="GitHub",
             repository_name="fakerepo",
+            publisher_url="https://github.com/fakeowner/fakerepo",
             owner="fakeowner",
             owner_id="1234",
             workflow_filename="fakeworkflow.yml",
-            publisher_url="some-url",
             environment="some-environment",
         )
         # NOTE: Can't set __str__ using pretend.stub()
@@ -6139,14 +6139,17 @@ def test_add_github_oidc_publisher_preexisting(self, monkeypatch):
                     "publisher": "GitHub",
                     "id": "fakeid",
                     "specifier": "fakespecifier",
-                    "url": "some-url",
+                    "url": "https://github.com/fakeowner/fakerepo",
                     "submitted_by": "some-user",
                 },
             )
         ]
         assert request.session.flash.calls == [
             pretend.call(
-                "Added fakespecifier to fakeproject",
+                (
+                    "Added fakespecifier in https://github.com/fakeowner/fakerepo "
+                    "to fakeproject"
+                ),
                 queue="success",
             )
         ]
@@ -6231,18 +6234,18 @@ def test_add_github_oidc_publisher_created(self, monkeypatch):
                 additional={
                     "publisher": "GitHub",
                     "id": "fakeid",
-                    "specifier": "fakeworkflow.yml @ fakeowner/fakerepo",
-                    "url": (
-                        "https://github.com/fakeowner/fakerepo/blob/HEAD/"
-                        ".github/workflows/fakeworkflow.yml"
-                    ),
+                    "specifier": "fakeworkflow.yml",
+                    "url": "https://github.com/fakeowner/fakerepo",
                     "submitted_by": "some-user",
                 },
             )
         ]
         assert request.session.flash.calls == [
             pretend.call(
-                "Added fakeworkflow.yml @ fakeowner/fakerepo to fakeproject",
+                (
+                    "Added fakeworkflow.yml in https://github.com/fakeowner/fakerepo "
+                    "to fakeproject"
+                ),
                 queue="success",
             )
         ]
diff --git a/tests/unit/oidc/test_models.py b/tests/unit/oidc/test_models.py
--- a/tests/unit/oidc/test_models.py
+++ b/tests/unit/oidc/test_models.py
@@ -89,12 +89,8 @@ def test_github_publisher_computed_properties(self):
         for claim_name in publisher.__required_verifiable_claims__.keys():
             assert getattr(publisher, claim_name) is not None
 
-        assert str(publisher) == "fakeworkflow.yml @ fakeowner/fakerepo"
-        assert (
-            publisher.publisher_url
-            == "https://github.com/fakeowner/fakerepo/blob/HEAD/"
-            f".github/workflows/{publisher.workflow_filename}"
-        )
+        assert str(publisher) == "fakeworkflow.yml"
+        assert publisher.publisher_url == "https://github.com/fakeowner/fakerepo"
 
     def test_github_publisher_unaccounted_claims(self, monkeypatch):
         publisher = models.GitHubPublisher(
diff --git a/tests/unit/oidc/test_views.py b/tests/unit/oidc/test_views.py
--- a/tests/unit/oidc/test_views.py
+++ b/tests/unit/oidc/test_views.py
@@ -440,7 +440,7 @@ def find_service(iface, **kw):
     assert macaroon_service.create_macaroon.calls == [
         pretend.call(
             "fakedomain",
-            f"OpenID token: https://fake/url ({datetime.fromtimestamp(0).isoformat()})",
+            f"OpenID token: fakespecifier ({datetime.fromtimestamp(0).isoformat()})",
             [
                 caveats.OIDCPublisher(oidc_publisher_id="fakepublisherid"),
                 caveats.ProjectID(project_ids=["fakeprojectid"]),