Skip to content

Commit 373d305

Browse files
committed
publisher_name to method. Add more factories. Publisher agnostic token handler tests
1 parent 40b1708 commit 373d305

File tree

4 files changed

+172
-93
lines changed

4 files changed

+172
-93
lines changed

tests/unit/oidc/test_views.py

Lines changed: 119 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,13 @@
1818
import pytest
1919

2020
from tests.common.db.accounts import UserFactory
21-
from tests.common.db.oidc import PendingGitHubPublisherFactory
21+
from tests.common.db.oidc import GitHubPublisherFactory, PendingGitHubPublisherFactory
2222
from tests.common.db.packaging import ProjectFactory
2323
from warehouse.events.tags import EventTag
2424
from warehouse.macaroons import caveats
2525
from warehouse.macaroons.interfaces import IMacaroonService
2626
from warehouse.oidc import errors, views
2727
from warehouse.oidc.interfaces import IOIDCPublisherService
28-
from warehouse.oidc.models import github
29-
from warehouse.packaging.models import Project
3028
from warehouse.rate_limiting.interfaces import IRateLimiter
3129

3230

@@ -111,7 +109,7 @@ def test_mint_token_from_github_oidc_not_enabled():
111109
{"token": {}},
112110
],
113111
)
114-
def test_mint_token_from_github_oidc_invalid_payload(body):
112+
def test_mint_token_oidc_invalid_payload(body):
115113
class Request:
116114
def __init__(self):
117115
self.response = pretend.stub(status=None)
@@ -202,7 +200,9 @@ def test_mint_token_from_trusted_publisher_lookup_fails():
202200

203201
def test_mint_token_from_oidc_pending_publisher_project_already_exists(db_request):
204202
project = ProjectFactory.create()
205-
pending_publisher = PendingGitHubPublisherFactory.create(project_name=project.name)
203+
pending_publisher = PendingGitHubPublisherFactory.create(
204+
project_name=project.name,
205+
)
206206

207207
db_request.flags.disallow_oidc = lambda f=None: False
208208
db_request.body = json.dumps({"token": "faketoken"})
@@ -325,22 +325,25 @@ def test_mint_token_from_pending_trusted_publisher_invalidates_others(
325325
)
326326

327327
db_request.flags.oidc_enabled = lambda f: False
328-
token = (
329-
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2ZTY3YjFjYi0yYjhkLTRi"
330-
"ZTUtOTFjYi03NTdlZGIyZWM5NzAiLCJzdWIiOiJyZXBvOmZvby9iYXIiLCJhdWQiOiJwe"
331-
"XBpIiwicmVmIjoiZmFrZSIsInNoYSI6ImZha2UiLCJyZXBvc2l0b3J5IjoiZm9vL2Jhci"
332-
"IsInJlcG9zaXRvcnlfb3duZXIiOiJmb28iLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiMTI"
333-
"zIiwicnVuX2lkIjoiZmFrZSIsInJ1bl9udW1iZXIiOiJmYWtlIiwicnVuX2F0dGVtcHQi"
334-
"OiIxIiwicmVwb3NpdG9yeV9pZCI6ImZha2UiLCJhY3Rvcl9pZCI6ImZha2UiLCJhY3Rvc"
335-
"iI6ImZvbyIsIndvcmtmbG93IjoiZmFrZSIsImhlYWRfcmVmIjoiZmFrZSIsImJhc2Vfcm"
336-
"VmIjoiZmFrZSIsImV2ZW50X25hbWUiOiJmYWtlIiwicmVmX3R5cGUiOiJmYWtlIiwiZW5"
337-
"2aXJvbm1lbnQiOiJmYWtlIiwiam9iX3dvcmtmbG93X3JlZiI6ImZvby9iYXIvLmdpdGh1"
338-
"Yi93b3JrZmxvd3MvZXhhbXBsZS55bWxAZmFrZSIsImlzcyI6Imh0dHBzOi8vdG9rZW4uY"
339-
"WN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20iLCJuYmYiOjE2NTA2NjMyNjUsImV4cC"
340-
"I6MTY1MDY2NDE2NSwiaWF0IjoxNjUwNjYzODY1fQ.f-FMv5FF5sdxAWeUilYDt9NoE7Et"
341-
"0vbdNhK32c2oC-E"
328+
db_request.body = json.dumps(
329+
{
330+
"token": (
331+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2ZTY3YjFjYi0yYjhkLTRi"
332+
"ZTUtOTFjYi03NTdlZGIyZWM5NzAiLCJzdWIiOiJyZXBvOmZvby9iYXIiLCJhdWQiOiJwe"
333+
"XBpIiwicmVmIjoiZmFrZSIsInNoYSI6ImZha2UiLCJyZXBvc2l0b3J5IjoiZm9vL2Jhci"
334+
"IsInJlcG9zaXRvcnlfb3duZXIiOiJmb28iLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiMTI"
335+
"zIiwicnVuX2lkIjoiZmFrZSIsInJ1bl9udW1iZXIiOiJmYWtlIiwicnVuX2F0dGVtcHQi"
336+
"OiIxIiwicmVwb3NpdG9yeV9pZCI6ImZha2UiLCJhY3Rvcl9pZCI6ImZha2UiLCJhY3Rvc"
337+
"iI6ImZvbyIsIndvcmtmbG93IjoiZmFrZSIsImhlYWRfcmVmIjoiZmFrZSIsImJhc2Vfcm"
338+
"VmIjoiZmFrZSIsImV2ZW50X25hbWUiOiJmYWtlIiwicmVmX3R5cGUiOiJmYWtlIiwiZW5"
339+
"2aXJvbm1lbnQiOiJmYWtlIiwiam9iX3dvcmtmbG93X3JlZiI6ImZvby9iYXIvLmdpdGh1"
340+
"Yi93b3JrZmxvd3MvZXhhbXBsZS55bWxAZmFrZSIsImlzcyI6Imh0dHBzOi8vdG9rZW4uY"
341+
"WN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20iLCJuYmYiOjE2NTA2NjMyNjUsImV4cC"
342+
"I6MTY1MDY2NDE2NSwiaWF0IjoxNjUwNjYzODY1fQ.f-FMv5FF5sdxAWeUilYDt9NoE7Et"
343+
"0vbdNhK32c2oC-E"
344+
)
345+
}
342346
)
343-
db_request.body = json.dumps({"token": token})
344347
db_request.remote_addr = "0.0.0.0"
345348

346349
ratelimiter = pretend.stub(clear=pretend.call_recorder(lambda id: None))
@@ -350,9 +353,7 @@ def test_mint_token_from_pending_trusted_publisher_invalidates_others(
350353
}
351354
monkeypatch.setattr(views, "_ratelimiters", lambda r: ratelimiters)
352355

353-
oidc_service = db_request.find_service(IOIDCPublisherService, name="github")
354-
355-
resp = views.mint_token(oidc_service, db_request)
356+
resp = views.mint_token_from_oidc_github(db_request)
356357
assert resp["success"]
357358
assert resp["token"].startswith("pypi-")
358359

@@ -370,6 +371,73 @@ def test_mint_token_from_pending_trusted_publisher_invalidates_others(
370371
]
371372

372373

374+
def test_mint_token_from_oidc_only_pending_publisher_fail(monkeypatch, db_request):
375+
pending_publisher = PendingGitHubPublisherFactory()
376+
377+
def _find_publisher(claims, pending=False):
378+
return pending_publisher
379+
380+
oidc_service = pretend.stub(
381+
verify_jwt_signature=pretend.call_recorder(
382+
lambda token: {"ref": "someref", "sha": "somesha"}
383+
),
384+
find_publisher=pretend.call_recorder(_find_publisher),
385+
reify_pending_publisher=pretend.call_recorder(
386+
lambda *a, **kw: pending_publisher
387+
),
388+
)
389+
390+
db_request.body = json.dumps(
391+
{
392+
"token": (
393+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2ZTY3YjFjYi0yYjhkLTRi"
394+
"ZTUtOTFjYi03NTdlZGIyZWM5NzAiLCJzdWIiOiJyZXBvOmZvby9iYXIiLCJhdWQiOiJwe"
395+
"XBpIiwicmVmIjoiZmFrZSIsInNoYSI6ImZha2UiLCJyZXBvc2l0b3J5IjoiZm9vL2Jhci"
396+
"IsInJlcG9zaXRvcnlfb3duZXIiOiJmb28iLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiMTI"
397+
"zIiwicnVuX2lkIjoiZmFrZSIsInJ1bl9udW1iZXIiOiJmYWtlIiwicnVuX2F0dGVtcHQi"
398+
"OiIxIiwicmVwb3NpdG9yeV9pZCI6ImZha2UiLCJhY3Rvcl9pZCI6ImZha2UiLCJhY3Rvc"
399+
"iI6ImZvbyIsIndvcmtmbG93IjoiZmFrZSIsImhlYWRfcmVmIjoiZmFrZSIsImJhc2Vfcm"
400+
"VmIjoiZmFrZSIsImV2ZW50X25hbWUiOiJmYWtlIiwicmVmX3R5cGUiOiJmYWtlIiwiZW5"
401+
"2aXJvbm1lbnQiOiJmYWtlIiwiam9iX3dvcmtmbG93X3JlZiI6ImZvby9iYXIvLmdpdGh1"
402+
"Yi93b3JrZmxvd3MvZXhhbXBsZS55bWxAZmFrZSIsImlzcyI6Imh0dHBzOi8vdG9rZW4uY"
403+
"WN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20iLCJuYmYiOjE2NTA2NjMyNjUsImV4cC"
404+
"I6MTY1MDY2NDE2NSwiaWF0IjoxNjUwNjYzODY1fQ.f-FMv5FF5sdxAWeUilYDt9NoE7Et"
405+
"0vbdNhK32c2oC-E"
406+
)
407+
}
408+
)
409+
410+
db_request.remote_addr = "0.0.0.0"
411+
412+
ratelimiter = pretend.stub(clear=pretend.call_recorder(lambda id: None))
413+
ratelimiters = {
414+
"user.oidc": ratelimiter,
415+
"ip.oidc": ratelimiter,
416+
}
417+
monkeypatch.setattr(views, "_ratelimiters", lambda r: ratelimiters)
418+
419+
send_pending_trusted_publisher_invalidated_email = pretend.call_recorder(
420+
lambda *a, **kw: None
421+
)
422+
monkeypatch.setattr(
423+
views,
424+
"send_pending_trusted_publisher_invalidated_email",
425+
send_pending_trusted_publisher_invalidated_email,
426+
)
427+
428+
response = views.mint_token(oidc_service, db_request)
429+
430+
assert response == {
431+
"message": "Token request failed",
432+
"errors": [
433+
{
434+
"code": "invalid-publisher",
435+
"description": ("valid token, but no corresponding publisher"),
436+
}
437+
],
438+
}
439+
440+
373441
@pytest.mark.parametrize(
374442
("claims_in_token", "claims_input"),
375443
[
@@ -379,30 +447,25 @@ def test_mint_token_from_pending_trusted_publisher_invalidates_others(
379447
],
380448
)
381449
def test_mint_token_from_oidc_no_pending_publisher_ok(
382-
monkeypatch, claims_in_token, claims_input
450+
monkeypatch, db_request, claims_in_token, claims_input
383451
):
384452
time = pretend.stub(time=pretend.call_recorder(lambda: 0))
385453
monkeypatch.setattr(views, "time", time)
386454

387-
project = Project(id="fakeprojectid")
388-
monkeypatch.setattr(
389-
project, "record_event", pretend.call_recorder(lambda **kw: None)
455+
project = pretend.stub(
456+
id="fakeprojectid",
457+
record_event=pretend.call_recorder(lambda **kw: None),
390458
)
391459

392-
publisher = github.GitHubPublisher(
393-
repository_name="fakerepo",
394-
repository_owner="fakeowner",
395-
repository_owner_id="fakeid",
396-
workflow_filename="fakeworkflow.yml",
397-
environment="fakeenv",
398-
)
399-
publisher.projects = [project]
460+
publisher = GitHubPublisherFactory()
461+
monkeypatch.setattr(publisher.__class__, "projects", [project])
462+
publisher.publisher_url = pretend.call_recorder(lambda **kw: "https://fake/url")
400463
# NOTE: Can't set __str__ using pretend.stub()
401-
monkeypatch.setattr(publisher, "id", "fakepublisherid")
464+
monkeypatch.setattr(publisher.__class__, "__str__", lambda s: "fakespecifier")
402465

403466
def _find_publisher(claims, pending=False):
404467
if pending:
405-
raise errors.InvalidPublisherError
468+
return None
406469
else:
407470
return publisher
408471

@@ -425,16 +488,19 @@ def find_service(iface, **kw):
425488
return macaroon_service
426489
assert False, iface
427490

428-
request = pretend.stub(
429-
response=pretend.stub(status=None),
430-
body=json.dumps({"token": "faketoken"}),
431-
find_service=find_service,
432-
domain="fakedomain",
433-
remote_addr="0.0.0.0",
434-
flags=pretend.stub(disallow_oidc=lambda *a: False),
435-
)
436-
437-
response = views.mint_token(oidc_service, request)
491+
# request = pretend.stub(
492+
# response=pretend.stub(status=None),
493+
# body=json.dumps({"token": "faketoken"}),
494+
# find_service=find_service,
495+
# domain="fakedomain",
496+
# remote_addr="0.0.0.0",
497+
# flags=pretend.stub(disallow_oidc=lambda *a: False),
498+
# )
499+
monkeypatch.setattr(db_request, "find_service", find_service)
500+
monkeypatch.setattr(db_request, "body", json.dumps({"token": "faketoken"}))
501+
monkeypatch.setattr(db_request, "domain", "fakedomain")
502+
503+
response = views.mint_token(oidc_service, db_request)
438504
assert response == {
439505
"success": True,
440506
"token": "raw-macaroon",
@@ -445,29 +511,30 @@ def find_service(iface, **kw):
445511
pretend.call(claims_in_token, pending=True),
446512
pretend.call(claims_in_token, pending=False),
447513
]
514+
448515
assert macaroon_service.create_macaroon.calls == [
449516
pretend.call(
450517
"fakedomain",
451-
f"OpenID token: fakeworkflow.yml ({datetime.fromtimestamp(0).isoformat()})",
518+
f"OpenID token: fakespecifier ({datetime.fromtimestamp(0).isoformat()})",
452519
[
453520
caveats.OIDCPublisher(
454-
oidc_publisher_id="fakepublisherid",
521+
oidc_publisher_id=str(publisher.id),
455522
),
456523
caveats.ProjectID(project_ids=["fakeprojectid"]),
457524
caveats.Expiration(expires_at=900, not_before=0),
458525
],
459-
oidc_publisher_id="fakepublisherid",
526+
oidc_publisher_id=str(publisher.id),
460527
additional={"oidc": claims_input},
461528
)
462529
]
463530
assert project.record_event.calls == [
464531
pretend.call(
465532
tag=EventTag.Project.ShortLivedAPITokenAdded,
466-
request=request,
533+
request=db_request,
467534
additional={
468535
"expires": 900,
469536
"publisher_name": "GitHub",
470-
"publisher_url": f"https://github.com/{publisher.repository_owner}/{publisher.repository_name}", # noqa
537+
"publisher_url": "https://fake/url",
471538
},
472539
)
473540
]

warehouse/oidc/models/_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ def publisher_name(self) -> str: # pragma: no cover
234234

235235
def publisher_url(
236236
self, claims: SignedClaims | None = None
237-
) -> str: # pragma: no cover
237+
) -> str | None: # pragma: no cover
238238
"""
239239
NOTE: This is **NOT** a `@property` because we pass `claims` to it.
240240
When calling, make sure to use `publisher_url()`

warehouse/oidc/services.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ def verify_jwt_signature(self, unverified_token: str) -> SignedClaims | None:
283283
def find_publisher(
284284
self, signed_claims: SignedClaims, *, pending: bool = False
285285
) -> OIDCPublisher | PendingOIDCPublisher:
286+
"""Returns a publisher for the given claims, or raises an error."""
286287
metrics_tags = [f"publisher:{self.publisher}"]
287288
self.metrics.increment(
288289
"warehouse.oidc.find_publisher.attempt",
@@ -306,7 +307,7 @@ def find_publisher(
306307
)
307308
raise e
308309

309-
def reify_pending_publisher(self, pending_publisher, project):
310+
def reify_pending_publisher(self, pending_publisher, project) -> OIDCPublisher:
310311
new_publisher = pending_publisher.reify(self.db)
311312
project.oidc_publishers.append(new_publisher)
312313
return new_publisher

0 commit comments

Comments
 (0)