Skip to content

[oidc] Make 'trusted publishers' generally available #13460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed docs/user/assets/dropdown.png
Binary file not shown.
16 changes: 1 addition & 15 deletions docs/user/main.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
from pathlib import Path


OIDC_PUBLISHING = """
!!! info

Trusted publishing functionality is currently in closed beta.

You can request access to the closed beta using
[this form](https://forms.gle/XUsRT8KTKy66TuUp7).

**NOTE**: Access to the beta is provided on a *per-user* basis: users can
register trusted publishers against projects once added to the beta, but
other owners of the project can't modify trusted publisher settings unless
they're *also* in the beta.
"""

ORG_ACCOUNTS = """
!!! info

Expand All @@ -25,7 +11,7 @@
to be one of the first to know how you can begin using them.
"""

PREVIEW_FEATURES = {"oidc-publishing": OIDC_PUBLISHING, "org-accounts": ORG_ACCOUNTS}
PREVIEW_FEATURES = {"org-accounts": ORG_ACCOUNTS}

_HERE = Path(__file__).parent.resolve()

Expand Down
2 changes: 0 additions & 2 deletions docs/user/trusted-publishers/adding-a-publisher.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
title: Adding a Trusted Publisher to an Existing PyPI Project
---

<!--[[ preview('oidc-publishing') ]]-->

# Adding a trusted publisher to an existing PyPI project

Adding a trusted publisher to a PyPI project only requires a single setup step.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
title: Creating a PyPI Project with a Trusted Publisher
---

<!--[[ preview('oidc-publishing') ]]-->

# Creating a PyPI project with a trusted publisher

Trusted publishers are not just for pre-existing PyPI projects: you can also use
Expand Down
23 changes: 9 additions & 14 deletions docs/user/trusted-publishers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,19 @@
title: Getting Started
---

<!--[[ preview('oidc-publishing') ]]-->

# Publishing to PyPI with a Trusted Publisher

## Confirming that you're in the beta

Before we do anything else: let's confirm that you're actually in the
beta! You can do this by [logging into PyPI](https://pypi.org/account/login/)
and clicking on "Account settings" in the dropdown under your profile:

![](/assets/dropdown.png)

On the "Account settings" page, you should see a right-side menu that
contains a "Publishing" link:
"Trusted publishing" is our term for using the [OpenID Connect
(OIDC)](https://openid.net/connect/) standard to exchange short-lived identity
tokens between a trusted third-party service and PyPI. This method can be used
in automated environments and eliminates the need to use username/password
combinations or manually generated API tokens to authenticate with PyPI when
publishing.

![](/assets/publishing-link.png)
To for a quickstart, see:

If you see that link and can click on it, then you're in the beta group!
* [Adding a trusted publisher to an existing PyPI project](adding-a-publisher.md)
* [Creating a PyPI project with a trusted publisher](creating-a-project-through-oidc.md)

## Quick background: Publishing with OpenID Connect

Expand Down
2 changes: 0 additions & 2 deletions docs/user/trusted-publishers/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
title: Security Model and Considerations
---

<!--[[ preview('oidc-publishing') ]]-->

# Security model and considerations

## Security model
Expand Down
2 changes: 0 additions & 2 deletions docs/user/trusted-publishers/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
title: Troubleshooting
---

<!--[[ preview('oidc-publishing') ]]-->

# Troubleshooting

## Ratelimiting
Expand Down
2 changes: 0 additions & 2 deletions docs/user/trusted-publishers/using-a-publisher.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
title: Publishing with a Trusted Publisher
---

<!--[[ preview('oidc-publishing') ]]-->

# Publishing with a Trusted Publisher

## The easy way
Expand Down
17 changes: 0 additions & 17 deletions tests/unit/accounts/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,20 +162,3 @@ def test_acl(self, db_session):
("Allow", "group:admins", "admin"),
("Allow", "group:moderators", "moderator"),
]

@pytest.mark.parametrize(
("is_superuser", "has_oidc_beta_access", "in_oidc_beta"),
[
(False, False, False),
(True, False, True),
(True, True, True),
(False, True, True),
],
)
def test_in_oidc_beta(
self, db_session, is_superuser, has_oidc_beta_access, in_oidc_beta
):
user = DBUserFactory.create(
is_superuser=is_superuser, has_oidc_beta_access=has_oidc_beta_access
)
assert user.in_oidc_beta == in_oidc_beta
78 changes: 12 additions & 66 deletions tests/unit/accounts/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
HTTPSeeOther,
HTTPTooManyRequests,
)
from pyramid.response import Response
from sqlalchemy.exc import NoResultFound
from webauthn.authentication.verify_authentication_response import (
VerifiedAuthentication,
Expand Down Expand Up @@ -3003,9 +3002,7 @@ def find_service(iface, name=None, context=None):
def test_manage_publishing(self, monkeypatch):
metrics = pretend.stub()
request = pretend.stub(
user=pretend.stub(
in_oidc_beta=True,
),
user=pretend.stub(),
registry=pretend.stub(
settings={
"warehouse.oidc.enabled": True,
Expand Down Expand Up @@ -3059,23 +3056,8 @@ def test_manage_publishing_oidc_disabled(self):
with pytest.raises(HTTPNotFound):
view.manage_publishing()

def test_manage_publishing_not_in_beta(self):
request = pretend.stub(
user=pretend.stub(in_oidc_beta=False),
registry=pretend.stub(settings={"warehouse.oidc.enabled": True}),
find_service=lambda *a, **kw: None,
)

view = views.ManageAccountPublishingViews(request)
resp = view.manage_publishing()

assert isinstance(resp, Response)
assert resp.status_code == 403

def test_manage_publishing_admin_disabled(self, monkeypatch, pyramid_request):
pyramid_request.user = pretend.stub(
in_oidc_beta=True,
)
pyramid_request.user = pretend.stub()
pyramid_request.registry = pretend.stub(
settings={
"warehouse.oidc.enabled": True,
Expand Down Expand Up @@ -3139,25 +3121,10 @@ def test_add_pending_github_oidc_publisher_oidc_disabled(self):
with pytest.raises(HTTPNotFound):
view.add_pending_github_oidc_publisher()

def test_add_pending_github_oidc_publisher_not_in_beta(self):
request = pretend.stub(
user=pretend.stub(in_oidc_beta=False),
registry=pretend.stub(settings={"warehouse.oidc.enabled": True}),
find_service=lambda *a, **kw: None,
)

view = views.ManageAccountPublishingViews(request)
resp = view.add_pending_github_oidc_publisher()

assert isinstance(resp, Response)
assert resp.status_code == 403

def test_add_pending_github_oidc_publisher_admin_disabled(
self, monkeypatch, pyramid_request
):
pyramid_request.user = pretend.stub(
in_oidc_beta=True,
)
pyramid_request.user = pretend.stub()
pyramid_request.registry = pretend.stub(
settings={
"warehouse.oidc.enabled": True,
Expand Down Expand Up @@ -3220,7 +3187,7 @@ def test_add_pending_github_oidc_publisher_user_cannot_register(
}
)
pyramid_request.user = pretend.stub(
has_primary_verified_email=False, in_oidc_beta=True
has_primary_verified_email=False,
)
pyramid_request.flags = pretend.stub(
enabled=pretend.call_recorder(lambda f: False)
Expand Down Expand Up @@ -3278,7 +3245,7 @@ def test_add_pending_github_oidc_publisher_user_cannot_register(
def test_add_pending_github_oidc_publisher_too_many_already(
self, monkeypatch, db_request
):
db_request.user = UserFactory.create(has_oidc_beta_access=True)
db_request.user = UserFactory.create()
EmailFactory(user=db_request.user, verified=True, primary=True)
for i in range(3):
pending_publisher = PendingGitHubPublisher(
Expand Down Expand Up @@ -3342,7 +3309,6 @@ def test_add_pending_github_oidc_publisher_ratelimited(
self, monkeypatch, pyramid_request
):
pyramid_request.user = pretend.stub(
in_oidc_beta=True,
has_primary_verified_email=True,
pending_oidc_publishers=[],
)
Expand Down Expand Up @@ -3397,7 +3363,6 @@ def test_add_pending_github_oidc_publisher_invalid_form(
self, monkeypatch, pyramid_request
):
pyramid_request.user = pretend.stub(
in_oidc_beta=True,
has_primary_verified_email=True,
pending_oidc_publishers=[],
)
Expand Down Expand Up @@ -3460,7 +3425,7 @@ def test_add_pending_github_oidc_publisher_invalid_form(
def test_add_pending_github_oidc_publisher_already_exists(
self, monkeypatch, db_request
):
db_request.user = UserFactory.create(has_oidc_beta_access=True)
db_request.user = UserFactory.create()
EmailFactory(user=db_request.user, verified=True, primary=True)
pending_publisher = PendingGitHubPublisher(
project_name="some-project-name",
Expand Down Expand Up @@ -3534,7 +3499,7 @@ def test_add_pending_github_oidc_publisher_already_exists(
]

def test_add_pending_github_oidc_publisher(self, monkeypatch, db_request):
db_request.user = UserFactory(has_oidc_beta_access=True)
db_request.user = UserFactory()
db_request.user.record_event = pretend.call_recorder(lambda **kw: None)
EmailFactory(user=db_request.user, verified=True, primary=True)
db_request.registry = pretend.stub(
Expand Down Expand Up @@ -3628,25 +3593,10 @@ def test_delete_pending_oidc_publisher_oidc_disabled(self):
with pytest.raises(HTTPNotFound):
view.delete_pending_oidc_publisher()

def test_delete_pending_oidc_publisher_not_in_beta(self):
request = pretend.stub(
user=pretend.stub(in_oidc_beta=False),
registry=pretend.stub(settings={"warehouse.oidc.enabled": True}),
find_service=lambda *a, **kw: None,
)

view = views.ManageAccountPublishingViews(request)
resp = view.delete_pending_oidc_publisher()

assert isinstance(resp, Response)
assert resp.status_code == 403

def test_delete_pending_oidc_publisher_admin_disabled(
self, monkeypatch, pyramid_request
):
pyramid_request.user = pretend.stub(
in_oidc_beta=True,
)
pyramid_request.user = pretend.stub()
pyramid_request.registry = pretend.stub(
settings={
"warehouse.oidc.enabled": True,
Expand Down Expand Up @@ -3702,9 +3652,7 @@ def test_delete_pending_oidc_publisher_admin_disabled(
def test_delete_pending_oidc_publisher_invalid_form(
self, monkeypatch, pyramid_request
):
pyramid_request.user = pretend.stub(
in_oidc_beta=True,
)
pyramid_request.user = pretend.stub()
pyramid_request.registry = pretend.stub(
settings={"warehouse.oidc.enabled": True}
)
Expand Down Expand Up @@ -3735,7 +3683,7 @@ def test_delete_pending_oidc_publisher_invalid_form(
]

def test_delete_pending_oidc_publisher_not_found(self, monkeypatch, db_request):
db_request.user = UserFactory.create(has_oidc_beta_access=True)
db_request.user = UserFactory.create()
pending_publisher = PendingGitHubPublisher(
project_name="some-project-name",
repository_name="some-repository",
Expand Down Expand Up @@ -3786,9 +3734,7 @@ def test_delete_pending_oidc_publisher_no_access(self, monkeypatch, db_request):
db_request.db.add(pending_publisher)
db_request.db.flush() # To get the id

db_request.user = pretend.stub(
in_oidc_beta=True,
)
db_request.user = pretend.stub()
db_request.registry = pretend.stub(settings={"warehouse.oidc.enabled": True})
db_request.flags = pretend.stub(enabled=pretend.call_recorder(lambda f: False))
db_request.session = pretend.stub(
Expand Down Expand Up @@ -3816,7 +3762,7 @@ def test_delete_pending_oidc_publisher_no_access(self, monkeypatch, db_request):
assert db_request.db.query(PendingGitHubPublisher).all() == [pending_publisher]

def test_delete_pending_oidc_publisher(self, monkeypatch, db_request):
db_request.user = UserFactory.create(has_oidc_beta_access=True)
db_request.user = UserFactory.create()
pending_publisher = PendingGitHubPublisher(
project_name="some-project-name",
repository_name="some-repository",
Expand Down
Loading