Skip to content

Commit bc73124

Browse files
oidc: Refactor lookup strategies into single functions (#18169)
Co-authored-by: Mike Fiedler <[email protected]>
1 parent d48e278 commit bc73124

File tree

14 files changed

+532
-149
lines changed

14 files changed

+532
-149
lines changed

tests/common/constants.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,88 @@
9797
"YzMjY1LCJleHAiOjE2NTA2NjQxNjUsImlhdCI6MTY1MDY2Mzg2NX0.R4q-vWAFXHrBSBK"
9898
"AZuHHIsGOkqlirPxEtLfjLIDiLr0"
9999
)
100+
101+
"""
102+
{
103+
"namespace_id": "123",
104+
"namespace_path": "foo",
105+
"project_id": "55235664",
106+
"project_path": "foo/bar",
107+
"user_id": "123",
108+
"user_login": "user",
109+
"user_email": "[email protected]",
110+
"user_access_level": "owner",
111+
"pipeline_id": "123",
112+
"pipeline_source": "push",
113+
"job_id": "123",
114+
"ref": "main",
115+
"ref_type": "branch",
116+
"ref_path": "refs/heads/main",
117+
"ref_protected": "true",
118+
"runner_id": 123,
119+
"runner_environment": "gitlab-hosted",
120+
"sha": "93969f556a29853b507bdcd9dec6b4217a4ea2e7",
121+
"project_visibility": "private",
122+
"ci_config_ref_uri": "gitlab.com/foo/bar//.gitlab-ci.yml@refs/heads/main",
123+
"ci_config_sha": "93969f556a29853b507bdcd9dec6b4217a4ea2e7",
124+
"jti": "2a5381a8-baf5-43c5-823d-544a08a067fb",
125+
"iat": 1750405794,
126+
"nbf": 1750405789,
127+
"exp": 1750409394,
128+
"iss": "https://gitlab.com",
129+
"sub": "project_path:foo/bar:ref_type:branch:ref:main",
130+
"aud": "pypi"
131+
}
132+
"""
133+
DUMMY_GITLAB_OIDC_JWT = (
134+
"eyJraWQiOiI0aTNzRkU3c3hxTlBPVDdGZHZjR0ExWlZHR0lfci10c0RYbkV1WVQ0WnFFI"
135+
"iwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJuYW1lc3BhY2VfaWQiOiIxMjMiLCJ"
136+
"uYW1lc3BhY2VfcGF0aCI6ImZvbyIsInByb2plY3RfaWQiOiI1NTIzNTY2NCIsInByb2pl"
137+
"Y3RfcGF0aCI6ImZvby9iYXIiLCJ1c2VyX2lkIjoiMTIzIiwidXNlcl9sb2dpbiI6InVzZ"
138+
"XIiLCJ1c2VyX2VtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsInVzZXJfYWNjZXNzX2xldm"
139+
"VsIjoib3duZXIiLCJwaXBlbGluZV9pZCI6IjEyMyIsInBpcGVsaW5lX3NvdXJjZSI6InB"
140+
"1c2giLCJqb2JfaWQiOiIxMjMiLCJyZWYiOiJtYWluIiwicmVmX3R5cGUiOiJicmFuY2gi"
141+
"LCJyZWZfcGF0aCI6InJlZnMvaGVhZHMvbWFpbiIsInJlZl9wcm90ZWN0ZWQiOiJ0cnVlI"
142+
"iwicnVubmVyX2lkIjoxMjMsInJ1bm5lcl9lbnZpcm9ubWVudCI6ImdpdGxhYi1ob3N0ZW"
143+
"QiLCJzaGEiOiI5Mzk2OWY1NTZhMjk4NTNiNTA3YmRjZDlkZWM2YjQyMTdhNGVhMmU3Iiw"
144+
"icHJvamVjdF92aXNpYmlsaXR5IjoicHJpdmF0ZSIsImNpX2NvbmZpZ19yZWZfdXJpIjoi"
145+
"Z2l0bGFiLmNvbS9mb28vYmFyLy8uZ2l0bGFiLWNpLnltbEByZWZzL2hlYWRzL21haW4iL"
146+
"CJjaV9jb25maWdfc2hhIjoiOTM5NjlmNTU2YTI5ODUzYjUwN2JkY2Q5ZGVjNmI0MjE3YT"
147+
"RlYTJlNyIsImp0aSI6IjJhNTM4MWE4LWJhZjUtNDNjNS04MjNkLTU0NGEwOGEwNjdmYiI"
148+
"sImlhdCI6MTc1MDQwNTc5NCwibmJmIjoxNzUwNDA1Nzg5LCJleHAiOjE3NTA0MDkzOTQs"
149+
"ImlzcyI6Imh0dHBzOi8vZ2l0bGFiLmNvbSIsInN1YiI6InByb2plY3RfcGF0aDpmb28vY"
150+
"mFyOnJlZl90eXBlOmJyYW5jaDpyZWY6bWFpbiIsImF1ZCI6InB5cGkifQ.g3ceS-5KkGC"
151+
"28nIKI-9HNf6lOPmBNyUAcQI-IjwZKpwcrWTcIM0lb6qKn1DyIqz2nE2W-SW3-hfZOIq9"
152+
"6xJhGuZfl2bAV7uj_WwUnEoh2hSqW99T2_2bqkrLqkwcJ2w5yvEE2WtzUsxpRqEmuhxHH"
153+
"uTOMLJPgydTqnJ2qe2oQxxWtpv_P0VjpZ_QXOk_KP6dBOHNdj7Iu7myCgybU5BTSyT33N"
154+
"kbWMiZHHBL6Andl3dU9eQD17-BYkuoQCzLxepoAzNKgiIRL3OULljfWP8wXcLymDXaj1Q"
155+
"A0sI9uulFYYx3ZgEmziSjv_e297kkrW3E_kbGyyGkTFxiOX6zSsKfUg"
156+
)
157+
158+
"""
159+
{
160+
"iss": "https://accounts.google.com",
161+
"azp": "32555350559.apps.googleusercontent.com",
162+
"aud": "pypi",
163+
"sub": "111260650121185072906",
164+
"hd": "google.com",
165+
"email": "[email protected]",
166+
"email_verified": "true",
167+
"at_hash": "_LLKKivfvfme9eoQ3WcMIg",
168+
"iat": "1650053185",
169+
"exp": "1650056785",
170+
"alg": "RS256",
171+
"kid": "f1338ca26835863f671403941738a7b49e740fc0",
172+
"typ": "JWT"
173+
}
174+
"""
175+
DUMMY_GOOGLE_OIDC_JWT = (
176+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRz"
177+
"Lmdvb2dsZS5jb20iLCJhenAiOiIzMjU1NTM1MDU1OS5hcHBzLmdvb2dsZXVzZXJjb250Z"
178+
"W50LmNvbSIsImF1ZCI6InB5cGkiLCJzdWIiOiIxMTEyNjA2NTAxMjExODUwNzI5MDYiLC"
179+
"JoZCI6Imdvb2dsZS5jb20iLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJlbWFpbF9"
180+
"2ZXJpZmllZCI6InRydWUiLCJhdF9oYXNoIjoiX0xMS0tpdmZ2Zm1lOWVvUTNXY01JZyIs"
181+
"ImlhdCI6IjE2NTAwNTMxODUiLCJleHAiOiIxNjUwMDU2Nzg1IiwiYWxnIjoiUlMyNTYiL"
182+
"CJraWQiOiJmMTMzOGNhMjY4MzU4NjNmNjcxNDAzOTQxNzM4YTdiNDllNzQwZmMwIiwidH"
183+
"lwIjoiSldUIn0.wlPNSE6eTFvznJawgpa6cHC3a8sU5_VBH8si9h-sgi0"
184+
)

tests/conftest.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,9 @@ def get_app_config(database, nondefaults=None):
339339
"statuspage.url": "https://2p66nmmycsj3.statuspage.io",
340340
"warehouse.xmlrpc.cache.url": "redis://localhost:0/",
341341
"terms.revision": "initial",
342+
"oidc.jwk_cache_url": "redis://localhost:0/",
343+
"warehouse.oidc.audience": "pypi",
344+
"oidc.backend": "warehouse.oidc.services.NullOIDCPublisherService",
342345
}
343346

344347
if nondefaults:
@@ -599,6 +602,28 @@ def db_request(pyramid_request, db_session, tm):
599602
return pyramid_request
600603

601604

605+
@pytest.fixture
606+
def _enable_all_oidc_providers(webtest):
607+
flags = (
608+
AdminFlagValue.DISALLOW_ACTIVESTATE_OIDC,
609+
AdminFlagValue.DISALLOW_GITLAB_OIDC,
610+
AdminFlagValue.DISALLOW_GITHUB_OIDC,
611+
AdminFlagValue.DISALLOW_GOOGLE_OIDC,
612+
)
613+
original_flag_values = {}
614+
db_sess = webtest.extra_environ["warehouse.db_session"]
615+
616+
for flag in flags:
617+
flag_db = db_sess.get(AdminFlag, flag.value)
618+
original_flag_values[flag] = flag_db.enabled
619+
flag_db.enabled = False
620+
yield
621+
622+
for flag in flags:
623+
flag_db = db_sess.get(AdminFlag, flag.value)
624+
flag_db.enabled = original_flag_values[flag]
625+
626+
602627
@pytest.fixture
603628
def _enable_organizations(db_request):
604629
flag = db_request.db.get(AdminFlag, AdminFlagValue.DISABLE_ORGANIZATIONS.value)

tests/functional/forklift/test_legacy.py

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,21 @@
1111

1212
from webob.multidict import MultiDict
1313

14-
from tests.common.db.oidc import GitHubPublisherFactory
14+
from tests.common.db.oidc import (
15+
ActiveStatePublisherFactory,
16+
GitHubPublisherFactory,
17+
GitLabPublisherFactory,
18+
GooglePublisherFactory,
19+
)
1520
from tests.common.db.packaging import ProjectFactory, RoleFactory
1621
from warehouse.macaroons import caveats
1722

23+
from ...common.constants import (
24+
DUMMY_ACTIVESTATE_OIDC_JWT,
25+
DUMMY_GITHUB_OIDC_JWT,
26+
DUMMY_GITLAB_OIDC_JWT,
27+
DUMMY_GOOGLE_OIDC_JWT,
28+
)
1829
from ...common.db.accounts import UserFactory
1930
from ...common.db.macaroons import MacaroonFactory
2031

@@ -397,3 +408,173 @@ def test_provenance_upload(webtest):
397408
status=HTTPStatus.OK,
398409
)
399410
assert response.json == project.releases[0].files[0].provenance.provenance
411+
412+
413+
@pytest.mark.parametrize(
414+
("publisher_factory", "publisher_data", "oidc_jwt"),
415+
[
416+
(
417+
GitHubPublisherFactory,
418+
{
419+
"repository_name": "bar",
420+
"repository_owner": "foo",
421+
"repository_owner_id": "123",
422+
"workflow_filename": "example.yml",
423+
"environment": "fake",
424+
},
425+
DUMMY_GITHUB_OIDC_JWT,
426+
),
427+
(
428+
ActiveStatePublisherFactory,
429+
{
430+
"organization": "fakeorg",
431+
"activestate_project_name": "fakeproject",
432+
"actor": "foo",
433+
"actor_id": "fake",
434+
},
435+
DUMMY_ACTIVESTATE_OIDC_JWT,
436+
),
437+
(
438+
GitLabPublisherFactory,
439+
{
440+
"namespace": "foo",
441+
"project": "bar",
442+
"workflow_filepath": ".gitlab-ci.yml",
443+
"environment": "",
444+
},
445+
DUMMY_GITLAB_OIDC_JWT,
446+
),
447+
(
448+
GooglePublisherFactory,
449+
{
450+
"email": "[email protected]",
451+
"sub": "111260650121185072906",
452+
},
453+
DUMMY_GOOGLE_OIDC_JWT,
454+
),
455+
],
456+
)
457+
@pytest.mark.usefixtures("_enable_all_oidc_providers")
458+
def test_trusted_publisher_upload_ok(
459+
webtest, publisher_factory, publisher_data, oidc_jwt
460+
):
461+
user = UserFactory.create(with_verified_primary_email=True, clear_pwd="password")
462+
project = ProjectFactory.create(name="sampleproject")
463+
RoleFactory.create(user=user, project=project, role_name="Owner")
464+
publisher_factory.create(
465+
projects=[project],
466+
**publisher_data,
467+
)
468+
469+
response = webtest.post_json(
470+
"/_/oidc/mint-token",
471+
params={
472+
"token": oidc_jwt,
473+
},
474+
status=HTTPStatus.OK,
475+
)
476+
477+
assert "success" in response.json
478+
assert response.json["success"]
479+
assert "token" in response.json
480+
pypi_token = response.json["token"]
481+
assert pypi_token.startswith("pypi-")
482+
483+
with open(_ASSETS / "sampleproject-3.0.0.tar.gz", "rb") as f:
484+
content = f.read()
485+
486+
webtest.set_authorization(("Basic", ("__token__", pypi_token)))
487+
webtest.post(
488+
"/legacy/?:action=file_upload",
489+
params={
490+
"name": "sampleproject",
491+
"sha256_digest": (
492+
"117ed88e5db073bb92969a7545745fd977ee85b7019706dd256a64058f70963d"
493+
),
494+
"filetype": "sdist",
495+
"metadata_version": "2.1",
496+
"version": "3.0.0",
497+
},
498+
upload_files=[("content", "sampleproject-3.0.0.tar.gz", content)],
499+
status=HTTPStatus.OK,
500+
)
501+
502+
assert len(project.releases) == 1
503+
release = project.releases[0]
504+
assert release.files.count() == 1
505+
506+
507+
@pytest.mark.parametrize(
508+
("publisher_factory", "publisher_data", "oidc_jwt"),
509+
[
510+
(
511+
GitHubPublisherFactory,
512+
{
513+
"repository_name": "wrong",
514+
"repository_owner": "foo",
515+
"repository_owner_id": "123",
516+
"workflow_filename": "example.yml",
517+
"environment": "fake",
518+
},
519+
DUMMY_GITHUB_OIDC_JWT,
520+
),
521+
(
522+
ActiveStatePublisherFactory,
523+
{
524+
"organization": "wrong",
525+
"activestate_project_name": "fakeproject",
526+
"actor": "foo",
527+
"actor_id": "fake",
528+
},
529+
DUMMY_ACTIVESTATE_OIDC_JWT,
530+
),
531+
(
532+
GitLabPublisherFactory,
533+
{
534+
"namespace": "wrong",
535+
"project": "bar",
536+
"workflow_filepath": ".gitlab-ci.yml",
537+
"environment": "fake",
538+
},
539+
DUMMY_GITLAB_OIDC_JWT,
540+
),
541+
(
542+
GooglePublisherFactory,
543+
{
544+
"email": "[email protected]",
545+
"sub": "111260650121185072906",
546+
},
547+
DUMMY_GOOGLE_OIDC_JWT,
548+
),
549+
],
550+
)
551+
@pytest.mark.usefixtures("_enable_all_oidc_providers")
552+
def test_trusted_publisher_upload_fails_wrong_publisher(
553+
webtest, publisher_factory, publisher_data, oidc_jwt
554+
):
555+
user = UserFactory.create(with_verified_primary_email=True, clear_pwd="password")
556+
project = ProjectFactory.create(name="sampleproject")
557+
RoleFactory.create(user=user, project=project, role_name="Owner")
558+
publisher_factory.create(
559+
projects=[project],
560+
**publisher_data,
561+
)
562+
response = webtest.post_json(
563+
"/_/oidc/mint-token",
564+
params={
565+
"token": oidc_jwt,
566+
},
567+
status=HTTPStatus.UNPROCESSABLE_CONTENT,
568+
)
569+
570+
assert "token" not in response.json
571+
assert "message" in response.json
572+
assert response.json["message"] == "Token request failed"
573+
assert "errors" in response.json
574+
assert response.json["errors"] == [
575+
{
576+
"code": "invalid-publisher",
577+
"description": "valid token, but no corresponding publisher "
578+
"(Publisher with matching claims was not found)",
579+
}
580+
]

0 commit comments

Comments
 (0)