Skip to content

Commit a081651

Browse files
committed
feat: update mint_token for org-owned publishers
Signed-off-by: Mike Fiedler <miketheman@gmail.com>
1 parent d9bce6a commit a081651

File tree

2 files changed

+97
-6
lines changed

2 files changed

+97
-6
lines changed

tests/unit/oidc/test_views.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
GooglePublisherFactory,
1616
PendingGitHubPublisherFactory,
1717
)
18+
from tests.common.db.organizations import OrganizationFactory
1819
from tests.common.db.packaging import ProhibitedProjectFactory, ProjectFactory
1920
from warehouse.events.tags import EventTag
2021
from warehouse.macaroons import caveats
@@ -27,6 +28,7 @@
2728
is_from_reusable_workflow,
2829
should_send_environment_warning_email,
2930
)
31+
from warehouse.organizations.models import OrganizationProject
3032
from warehouse.packaging import services
3133
from warehouse.packaging.models import Project
3234
from warehouse.rate_limiting.interfaces import IRateLimiter
@@ -508,6 +510,74 @@ def test_mint_token_from_oidc_pending_publisher_ok(monkeypatch, db_request):
508510
}
509511

510512

513+
def test_mint_token_from_oidc_pending_publisher_for_organization_ok(
514+
monkeypatch, db_request
515+
):
516+
"""Test creating a project from an organization-owned pending publisher"""
517+
user = UserFactory.create()
518+
organization = OrganizationFactory.create()
519+
520+
pending_publisher = PendingGitHubPublisherFactory.create(
521+
project_name="org-owned-project",
522+
added_by=user,
523+
repository_name="bar",
524+
repository_owner="foo",
525+
repository_owner_id="123",
526+
workflow_filename="example.yml",
527+
environment="fake",
528+
organization_id=organization.id,
529+
)
530+
531+
db_request.flags.disallow_oidc = lambda f=None: False
532+
db_request.body = json.dumps({"token": DUMMY_GITHUB_OIDC_JWT})
533+
db_request.remote_addr = "0.0.0.0"
534+
535+
ratelimiter = pretend.stub(clear=pretend.call_recorder(lambda id: None))
536+
ratelimiters = {
537+
"user.oidc": ratelimiter,
538+
"ip.oidc": ratelimiter,
539+
}
540+
monkeypatch.setattr(views, "_ratelimiters", lambda r: ratelimiters)
541+
542+
resp = views.mint_token_from_oidc(db_request)
543+
assert resp["success"]
544+
assert resp["token"].startswith("pypi-")
545+
546+
# Verify project was created
547+
project = (
548+
db_request.db.query(Project)
549+
.filter(Project.name == pending_publisher.project_name)
550+
.one()
551+
)
552+
553+
# Verify project is associated with organization
554+
org_project = (
555+
db_request.db.query(OrganizationProject)
556+
.filter(
557+
OrganizationProject.organization_id == organization.id,
558+
OrganizationProject.project_id == project.id,
559+
)
560+
.one()
561+
)
562+
assert org_project.organization_id == organization.id
563+
assert org_project.project_id == project.id
564+
565+
# Verify publisher was created
566+
publisher = db_request.db.query(GitHubPublisher).one()
567+
event = project.events.where(
568+
Project.Event.tag == EventTag.Project.OIDCPublisherAdded
569+
).one()
570+
assert event.additional == {
571+
"publisher": publisher.publisher_name,
572+
"id": str(publisher.id),
573+
"specifier": str(publisher),
574+
"url": publisher.publisher_url(),
575+
"submitted_by": "OpenID created token",
576+
"reified_from_pending_publisher": True,
577+
"constrained_from_existing_publisher": False,
578+
}
579+
580+
511581
def test_mint_token_from_pending_trusted_publisher_invalidates_others(
512582
monkeypatch, db_request
513583
):

warehouse/oidc/views.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
OIDC_ISSUER_SERVICE_NAMES,
3030
lookup_custom_issuer_type,
3131
)
32+
from warehouse.organizations.models import OrganizationProject
3233
from warehouse.packaging.interfaces import IProjectService
3334
from warehouse.packaging.models import ProjectFactory
3435
from warehouse.rate_limiting.interfaces import IRateLimiter
@@ -217,12 +218,32 @@ def mint_token(
217218
# Try creating the new project
218219
project_service = request.find_service(IProjectService)
219220
try:
220-
new_project = project_service.create_project(
221-
pending_publisher.project_name,
222-
pending_publisher.added_by,
223-
request,
224-
ratelimited=False,
225-
)
221+
# Check if this pending publisher is for an organization
222+
if pending_publisher.organization_id:
223+
# For organization-owned projects,
224+
# create without making the user an owner
225+
new_project = project_service.create_project(
226+
pending_publisher.project_name,
227+
pending_publisher.added_by,
228+
request,
229+
creator_is_owner=False,
230+
ratelimited=False,
231+
)
232+
# Add the project to the organization
233+
request.db.add(
234+
OrganizationProject(
235+
organization_id=pending_publisher.organization_id,
236+
project_id=new_project.id,
237+
)
238+
)
239+
else:
240+
# For user-owned projects, create normally
241+
new_project = project_service.create_project(
242+
pending_publisher.project_name,
243+
pending_publisher.added_by,
244+
request,
245+
ratelimited=False,
246+
)
226247
except HTTPException as exc:
227248
return _invalid(
228249
errors=[{"code": "invalid-payload", "description": str(exc)}],

0 commit comments

Comments
 (0)