Skip to content

Commit c979275

Browse files
facutuescadi
andauthored
oidc: add missing claims check in publisher lookup (#16698)
Co-authored-by: Dustin Ingram <[email protected]>
1 parent 73165b8 commit c979275

File tree

8 files changed

+141
-101
lines changed

8 files changed

+141
-101
lines changed

tests/unit/oidc/models/test_activestate.py

+5-13
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,6 @@ def test_activestate_publisher_all_known_claims(self):
152152
}
153153

154154
def test_activestate_publisher_unaccounted_claims(self, monkeypatch):
155-
publisher = ActiveStatePublisher(
156-
organization=ORG_URL_NAME,
157-
activestate_project_name=PROJECT_NAME,
158-
actor_id=ACTOR_ID,
159-
)
160-
161155
scope = pretend.stub()
162156
sentry_sdk = pretend.stub(
163157
capture_message=pretend.call_recorder(lambda s: None),
@@ -173,9 +167,7 @@ def test_activestate_publisher_unaccounted_claims(self, monkeypatch):
173167
signed_claims["fake-claim"] = "fake"
174168
signed_claims["another-fake-claim"] = "also-fake"
175169

176-
assert publisher.verify_claims(
177-
signed_claims=signed_claims, publisher_service=pretend.stub()
178-
)
170+
ActiveStatePublisher.check_claims_existence(signed_claims)
179171

180172
assert sentry_sdk.capture_message.calls == [
181173
pretend.call(
@@ -225,17 +217,17 @@ def test_activestate_publisher_missing_claims(
225217

226218
assert claim_to_drop not in signed_claims
227219
if valid:
220+
ActiveStatePublisher.check_claims_existence(signed_claims)
228221
assert (
229222
publisher.verify_claims(
230-
signed_claims=signed_claims, publisher_service=pretend.stub()
223+
signed_claims=signed_claims, publisher_service=pretend.stub
231224
)
232225
is valid
233226
)
234227
else:
235228
with pytest.raises(InvalidPublisherError) as e:
236-
assert publisher.verify_claims(
237-
signed_claims=signed_claims, publisher_service=pretend.stub()
238-
)
229+
ActiveStatePublisher.check_claims_existence(signed_claims)
230+
239231
assert str(e.value) == error_msg
240232
assert sentry_sdk.capture_message.calls == [
241233
pretend.call(

tests/unit/oidc/models/test_core.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_oidc_publisher_not_default_verifiable(self):
5151
publisher = _core.OIDCPublisher(projects=[])
5252

5353
with pytest.raises(errors.InvalidPublisherError) as e:
54-
publisher.verify_claims(signed_claims={}, publisher_service=pretend.stub())
54+
publisher.check_claims_existence(signed_claims={})
5555
assert str(e.value) == "No required verifiable claims"
5656

5757
def test_supports_attestations(self):

tests/unit/oidc/models/test_github.py

+9-16
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,6 @@ def test_github_publisher_computed_properties(self):
234234
}
235235

236236
def test_github_publisher_unaccounted_claims(self, monkeypatch):
237-
publisher = github.GitHubPublisher(
238-
repository_name="fakerepo",
239-
repository_owner="fakeowner",
240-
repository_owner_id="fakeid",
241-
workflow_filename="fakeworkflow.yml",
242-
)
243-
244237
scope = pretend.stub()
245238
sentry_sdk = pretend.stub(
246239
capture_message=pretend.call_recorder(lambda s: None),
@@ -259,11 +252,8 @@ def test_github_publisher_unaccounted_claims(self, monkeypatch):
259252
}
260253
signed_claims["fake-claim"] = "fake"
261254
signed_claims["another-fake-claim"] = "also-fake"
262-
with pytest.raises(errors.InvalidPublisherError) as e:
263-
publisher.verify_claims(
264-
signed_claims=signed_claims, publisher_service=pretend.stub()
265-
)
266-
assert str(e.value) == "Check failed for required claim 'sub'"
255+
256+
github.GitHubPublisher.check_claims_existence(signed_claims)
267257
assert sentry_sdk.capture_message.calls == [
268258
pretend.call(
269259
"JWT for GitHubPublisher has unaccounted claims: "
@@ -272,7 +262,11 @@ def test_github_publisher_unaccounted_claims(self, monkeypatch):
272262
]
273263
assert scope.fingerprint == ["another-fake-claim", "fake-claim"]
274264

275-
@pytest.mark.parametrize("missing", ["sub", "ref"])
265+
@pytest.mark.parametrize(
266+
"missing",
267+
github.GitHubPublisher.__required_verifiable_claims__.keys()
268+
| github.GitHubPublisher.__required_unverifiable_claims__,
269+
)
276270
def test_github_publisher_missing_claims(self, monkeypatch, missing):
277271
publisher = github.GitHubPublisher(
278272
repository_name="fakerepo",
@@ -301,9 +295,7 @@ def test_github_publisher_missing_claims(self, monkeypatch, missing):
301295
assert missing not in signed_claims
302296
assert publisher.__required_verifiable_claims__
303297
with pytest.raises(errors.InvalidPublisherError) as e:
304-
publisher.verify_claims(
305-
signed_claims=signed_claims, publisher_service=pretend.stub()
306-
)
298+
github.GitHubPublisher.check_claims_existence(signed_claims)
307299
assert str(e.value) == f"Missing claim {missing!r}"
308300
assert sentry_sdk.capture_message.calls == [
309301
pretend.call(f"JWT for GitHubPublisher is missing claim: {missing}")
@@ -376,6 +368,7 @@ def test_github_publisher_verifies(self, monkeypatch, environment, missing_claim
376368
for claim_name in github.GitHubPublisher.all_known_claims()
377369
if claim_name not in missing_claims
378370
}
371+
github.GitHubPublisher.check_claims_existence(signed_claims)
379372
assert publisher.verify_claims(
380373
signed_claims=signed_claims, publisher_service=pretend.stub()
381374
)

tests/unit/oidc/models/test_gitlab.py

+8-15
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,6 @@ def test_gitlab_publisher_computed_properties(self):
195195
}
196196

197197
def test_gitlab_publisher_unaccounted_claims(self, monkeypatch):
198-
publisher = gitlab.GitLabPublisher(
199-
project="fakerepo",
200-
namespace="fakeowner",
201-
workflow_filepath="subfolder/fakeworkflow.yml",
202-
)
203-
204198
scope = pretend.stub()
205199
sentry_sdk = pretend.stub(
206200
capture_message=pretend.call_recorder(lambda s: None),
@@ -219,11 +213,8 @@ def test_gitlab_publisher_unaccounted_claims(self, monkeypatch):
219213
}
220214
signed_claims["fake-claim"] = "fake"
221215
signed_claims["another-fake-claim"] = "also-fake"
222-
with pytest.raises(errors.InvalidPublisherError) as e:
223-
publisher.verify_claims(
224-
signed_claims=signed_claims, publisher_service=pretend.stub()
225-
)
226-
assert str(e.value) == "Check failed for required claim 'sub'"
216+
217+
gitlab.GitLabPublisher.check_claims_existence(signed_claims)
227218
assert sentry_sdk.capture_message.calls == [
228219
pretend.call(
229220
"JWT for GitLabPublisher has unaccounted claims: "
@@ -232,7 +223,11 @@ def test_gitlab_publisher_unaccounted_claims(self, monkeypatch):
232223
]
233224
assert scope.fingerprint == ["another-fake-claim", "fake-claim"]
234225

235-
@pytest.mark.parametrize("missing", ["sub", "ref_path"])
226+
@pytest.mark.parametrize(
227+
"missing",
228+
gitlab.GitLabPublisher.__required_verifiable_claims__.keys()
229+
| gitlab.GitLabPublisher.__required_unverifiable_claims__,
230+
)
236231
def test_gitlab_publisher_missing_claims(self, monkeypatch, missing):
237232
publisher = gitlab.GitLabPublisher(
238233
project="fakerepo",
@@ -260,9 +255,7 @@ def test_gitlab_publisher_missing_claims(self, monkeypatch, missing):
260255
assert missing not in signed_claims
261256
assert publisher.__required_verifiable_claims__
262257
with pytest.raises(errors.InvalidPublisherError) as e:
263-
publisher.verify_claims(
264-
signed_claims=signed_claims, publisher_service=pretend.stub()
265-
)
258+
gitlab.GitLabPublisher.check_claims_existence(signed_claims)
266259
assert str(e.value) == f"Missing claim {missing!r}"
267260
assert sentry_sdk.capture_message.calls == [
268261
pretend.call(f"JWT for GitLabPublisher is missing claim: {missing}")

tests/unit/oidc/models/test_google.py

+16-25
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,6 @@ def test_google_publisher_all_known_claims(self):
7272
}
7373

7474
def test_google_publisher_unaccounted_claims(self, monkeypatch):
75-
publisher = google.GooglePublisher(
76-
sub="fakesubject",
77-
78-
)
79-
8075
scope = pretend.stub()
8176
sentry_sdk = pretend.stub(
8277
capture_message=pretend.call_recorder(lambda s: None),
@@ -95,11 +90,8 @@ def test_google_publisher_unaccounted_claims(self, monkeypatch):
9590
}
9691
signed_claims["fake-claim"] = "fake"
9792
signed_claims["another-fake-claim"] = "also-fake"
98-
with pytest.raises(errors.InvalidPublisherError) as e:
99-
publisher.verify_claims(
100-
signed_claims=signed_claims, publisher_service=pretend.stub()
101-
)
102-
assert str(e.value) == "Check failed for required claim 'email'"
93+
94+
google.GooglePublisher.check_claims_existence(signed_claims)
10395
assert sentry_sdk.capture_message.calls == [
10496
pretend.call(
10597
"JWT for GooglePublisher has unaccounted claims: "
@@ -108,12 +100,12 @@ def test_google_publisher_unaccounted_claims(self, monkeypatch):
108100
]
109101
assert scope.fingerprint == ["another-fake-claim", "fake-claim"]
110102

111-
def test_google_publisher_missing_claims(self, monkeypatch):
112-
publisher = google.GooglePublisher(
113-
sub="fakesubject",
114-
115-
)
116-
103+
@pytest.mark.parametrize(
104+
"missing",
105+
google.GooglePublisher.__required_verifiable_claims__.keys()
106+
| google.GooglePublisher.__required_unverifiable_claims__,
107+
)
108+
def test_google_publisher_missing_claims(self, monkeypatch, missing):
117109
scope = pretend.stub()
118110
sentry_sdk = pretend.stub(
119111
capture_message=pretend.call_recorder(lambda s: None),
@@ -130,18 +122,17 @@ def test_google_publisher_missing_claims(self, monkeypatch):
130122
for claim_name in google.GooglePublisher.all_known_claims()
131123
}
132124
# Pop the first signed claim, so that it's the first one to fail.
133-
signed_claims.pop("email")
134-
assert "email" not in signed_claims
135-
assert publisher.__required_verifiable_claims__
125+
signed_claims.pop(missing)
126+
assert missing not in signed_claims
127+
assert google.GooglePublisher.__required_verifiable_claims__
136128
with pytest.raises(errors.InvalidPublisherError) as e:
137-
publisher.verify_claims(
138-
signed_claims=signed_claims, publisher_service=pretend.stub()
139-
)
140-
assert str(e.value) == "Missing claim 'email'"
129+
google.GooglePublisher.check_claims_existence(signed_claims)
130+
131+
assert str(e.value) == f"Missing claim '{missing}'"
141132
assert sentry_sdk.capture_message.calls == [
142-
pretend.call("JWT for GooglePublisher is missing claim: email")
133+
pretend.call(f"JWT for GooglePublisher is missing claim: {missing}")
143134
]
144-
assert scope.fingerprint == ["email"]
135+
assert scope.fingerprint == [missing]
145136

146137
@pytest.mark.parametrize(
147138
("email_verified", "valid"),

tests/unit/oidc/test_utils.py

+71-12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
GooglePublisherFactory,
2525
)
2626
from warehouse.oidc import errors, utils
27+
from warehouse.oidc.models import (
28+
ActiveStatePublisher,
29+
GitHubPublisher,
30+
GitLabPublisher,
31+
GooglePublisher,
32+
)
33+
from warehouse.oidc.utils import OIDC_PUBLISHER_CLASSES
2734
from warehouse.utils.security_policy import principals_for
2835

2936

@@ -34,6 +41,35 @@ def test_find_publisher_by_issuer_bad_issuer_url():
3441
)
3542

3643

44+
@pytest.mark.parametrize(
45+
("issuer_url", "publisher_cls_dict"), OIDC_PUBLISHER_CLASSES.items()
46+
)
47+
def test_find_publisher_by_issuer_checks_claims_existence(
48+
monkeypatch, issuer_url, publisher_cls_dict
49+
):
50+
publisher_cls = pretend.stub(
51+
check_claims_existence=pretend.call_recorder(lambda x: None),
52+
lookup_by_claims=pretend.call_recorder(lambda x, y: None),
53+
)
54+
monkeypatch.setattr(
55+
utils,
56+
"OIDC_PUBLISHER_CLASSES",
57+
{issuer_url: {False: publisher_cls, True: publisher_cls}},
58+
)
59+
60+
signed_claims = {
61+
claim_name: "fake"
62+
for claim_name in publisher_cls_dict[False].all_known_claims()
63+
}
64+
session = pretend.stub()
65+
utils.find_publisher_by_issuer(session, issuer_url, signed_claims)
66+
67+
assert publisher_cls.check_claims_existence.calls == [pretend.call(signed_claims)]
68+
assert publisher_cls.lookup_by_claims.calls == [
69+
pretend.call(session, signed_claims)
70+
]
71+
72+
3773
@pytest.mark.parametrize(
3874
("environment", "expected_id"),
3975
[
@@ -62,10 +98,15 @@ def test_find_publisher_by_issuer_github(db_request, environment, expected_id):
6298
)
6399

64100
signed_claims = {
65-
"repository": "foo/bar",
66-
"job_workflow_ref": "foo/bar/.github/workflows/ci.yml@refs/heads/main",
67-
"repository_owner_id": "1234",
101+
claim_name: "fake" for claim_name in GitHubPublisher.all_known_claims()
68102
}
103+
signed_claims.update(
104+
{
105+
"repository": "foo/bar",
106+
"job_workflow_ref": "foo/bar/.github/workflows/ci.yml@refs/heads/main",
107+
"repository_owner_id": "1234",
108+
}
109+
)
69110
if environment:
70111
signed_claims["environment"] = environment
71112

@@ -104,9 +145,15 @@ def test_find_publisher_by_issuer_gitlab(db_request, environment, expected_id):
104145
)
105146

106147
signed_claims = {
107-
"project_path": "foo/bar",
108-
"ci_config_ref_uri": "gitlab.com/foo/bar//workflows/ci.yml@refs/heads/main",
148+
claim_name: "fake" for claim_name in GitLabPublisher.all_known_claims()
109149
}
150+
151+
signed_claims.update(
152+
{
153+
"project_path": "foo/bar",
154+
"ci_config_ref_uri": "gitlab.com/foo/bar//workflows/ci.yml@refs/heads/main",
155+
}
156+
)
110157
if environment:
111158
signed_claims["environment"] = environment
112159

@@ -140,10 +187,16 @@ def test_find_publisher_by_issuer_google(db_request, sub, expected_id):
140187
)
141188

142189
signed_claims = {
143-
"email": "[email protected]",
144-
"sub": sub,
190+
claim_name: "fake" for claim_name in GooglePublisher.all_known_claims()
145191
}
146192

193+
signed_claims.update(
194+
{
195+
"email": "[email protected]",
196+
"sub": sub,
197+
}
198+
)
199+
147200
assert (
148201
utils.find_publisher_by_issuer(
149202
db_request.db,
@@ -227,13 +280,19 @@ def test_find_publisher_by_issuer_activestate(
227280
)
228281

229282
signed_claims = {
230-
"sub": sub,
231-
"organization": organization,
232-
"project": project,
233-
"actor_id": actor_id,
234-
"actor": actor,
283+
claim_name: "fake" for claim_name in ActiveStatePublisher.all_known_claims()
235284
}
236285

286+
signed_claims.update(
287+
{
288+
"sub": sub,
289+
"organization": organization,
290+
"project": project,
291+
"actor_id": actor_id,
292+
"actor": actor,
293+
}
294+
)
295+
237296
assert (
238297
utils.find_publisher_by_issuer(
239298
db_request.db,

0 commit comments

Comments
 (0)