Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 2d4a7f5

Browse files
committed
fix: TA tokenless uploads
1 parent 5df99ba commit 2d4a7f5

File tree

4 files changed

+200
-2
lines changed

4 files changed

+200
-2
lines changed

codecov_auth/authentication/repo_auth.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,49 @@ def authenticate(
338338
raise exceptions.AuthenticationFailed(self.auth_failed_message)
339339

340340

341+
class TestAnalyticsTokenlessAuthentication(TokenlessAuthentication):
342+
def _get_info_from_request_path(
343+
self, request: HttpRequest
344+
) -> tuple[Repository, str | None]:
345+
try:
346+
body = json.loads(str(request.body, "utf8"))
347+
348+
# Validate provider
349+
service_enum = Service(body.get("service"))
350+
351+
# Validate that next group exists and decode slug
352+
repo = get_repository_from_string(service_enum, body.get("slug"))
353+
if repo is None:
354+
# Purposefully using the generic message so that we don't tell that
355+
# we don't have a certain repo
356+
raise exceptions.AuthenticationFailed(self.auth_failed_message)
357+
358+
return repo, body.get("commit")
359+
except json.JSONDecodeError:
360+
# Validate request body format
361+
raise exceptions.AuthenticationFailed(self.auth_failed_message)
362+
except ValueError:
363+
# Validate provider
364+
raise exceptions.AuthenticationFailed(self.auth_failed_message)
365+
366+
def get_branch(
367+
self,
368+
request: HttpRequest,
369+
repoid: Optional[int] = None,
370+
commitid: Optional[str] = None,
371+
) -> str:
372+
body = json.loads(str(request.body, "utf8"))
373+
374+
# If commit is not created yet (ie first upload for this commit), we just validate branch format.
375+
# However, if a commit exists already (ie not the first upload for this commit), we must additionally
376+
# validate the saved commit branch matches what is requested in this upload call.
377+
commit = Commit.objects.filter(repository_id=repoid, commitid=commitid).first()
378+
if commit and commit.branch != body.get("branch"):
379+
raise exceptions.AuthenticationFailed(self.auth_failed_message)
380+
381+
return body.get("branch")
382+
383+
341384
class BundleAnalysisTokenlessAuthentication(TokenlessAuthentication):
342385
def _get_info_from_request_path(
343386
self, request: HttpRequest

upload/tests/views/test_test_results.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,3 +447,157 @@ def test_upload_test_results_file_not_found(db, client, mocker, mock_redis):
447447
arguments=args,
448448
countdown=4,
449449
)
450+
451+
452+
def test_upload_test_results_tokenless_authentication(db, client, mocker, mock_redis):
453+
upload = mocker.patch.object(TaskService, "upload")
454+
_ = mocker.patch(
455+
"shared.storage.MinioStorageService.create_presigned_put",
456+
return_value="test-presigned-put",
457+
)
458+
459+
owner = OwnerFactory(service="github", username="codecov")
460+
repository = RepositoryFactory.create(author=owner, private=False)
461+
commit_sha = "6fd5b89357fc8cdf34d6197549ac7c6d7e5977ef"
462+
463+
client = APIClient()
464+
465+
res = client.post(
466+
reverse("upload-test-results"),
467+
{
468+
"commit": commit_sha,
469+
"slug": f"{repository.author.username}::::{repository.name}",
470+
"build": "test-build",
471+
"buildURL": "test-build-url",
472+
"job": "test-job",
473+
"service": "github",
474+
"branch": "fork:branch",
475+
},
476+
format="json",
477+
headers={"User-Agent": "codecov-cli/0.4.7"},
478+
)
479+
assert res.status_code == 201
480+
481+
assert res.data == {"raw_upload_location": "test-presigned-put"}
482+
483+
commit = Commit.objects.get(commitid=commit_sha)
484+
assert commit
485+
assert commit.branch is not None
486+
487+
# triggers upload task
488+
upload.assert_called_with(
489+
commitid=commit_sha,
490+
repoid=repository.repoid,
491+
report_code=None,
492+
report_type="test_results",
493+
arguments=mocker.ANY,
494+
countdown=4,
495+
)
496+
497+
498+
def test_upload_test_results_tokenless_authentication_private_repo(
499+
db, client, mocker, mock_redis
500+
):
501+
_ = mocker.patch.object(TaskService, "upload")
502+
_ = mocker.patch(
503+
"shared.storage.MinioStorageService.create_presigned_put",
504+
return_value="test-presigned-put",
505+
)
506+
507+
owner = OwnerFactory(service="github", username="codecov")
508+
repository = RepositoryFactory.create(author=owner, private=True)
509+
commit_sha = "6fd5b89357fc8cdf34d6197549ac7c6d7e5977ef"
510+
511+
client = APIClient()
512+
513+
res = client.post(
514+
reverse("upload-test-results"),
515+
{
516+
"commit": commit_sha,
517+
"slug": f"{repository.author.username}::::{repository.name}",
518+
"build": "test-build",
519+
"buildURL": "test-build-url",
520+
"job": "test-job",
521+
"service": "github",
522+
"branch": "fork:branch",
523+
},
524+
format="json",
525+
headers={"User-Agent": "codecov-cli/0.4.7"},
526+
)
527+
assert res.status_code == 401
528+
assert res.json().get("detail") == "Not valid tokenless upload"
529+
530+
531+
def test_upload_test_results_tokenless_authentication_invalid_branch(
532+
db, client, mocker, mock_redis
533+
):
534+
_ = mocker.patch.object(TaskService, "upload")
535+
_ = mocker.patch(
536+
"shared.storage.MinioStorageService.create_presigned_put",
537+
return_value="test-presigned-put",
538+
)
539+
540+
owner = OwnerFactory(service="github", username="codecov")
541+
owner.upload_token_required_for_public_repos = True
542+
owner.save()
543+
544+
repository = RepositoryFactory.create(author=owner, private=False)
545+
commit_sha = "6fd5b89357fc8cdf34d6197549ac7c6d7e5977ef"
546+
547+
client = APIClient()
548+
549+
res = client.post(
550+
reverse("upload-test-results"),
551+
{
552+
"commit": commit_sha,
553+
"slug": f"{repository.author.username}::::{repository.name}",
554+
"build": "test-build",
555+
"buildURL": "test-build-url",
556+
"job": "test-job",
557+
"service": "github",
558+
"branch": "branch",
559+
},
560+
format="json",
561+
headers={"User-Agent": "codecov-cli/0.4.7"},
562+
)
563+
assert res.status_code == 401
564+
assert res.json().get("detail") == "Not valid tokenless upload"
565+
566+
567+
def test_upload_test_results_tokenless_authentication_invalid_branch_existing_commits(
568+
db, client, mocker, mock_redis
569+
):
570+
_ = mocker.patch.object(TaskService, "upload")
571+
_ = mocker.patch(
572+
"shared.storage.MinioStorageService.create_presigned_put",
573+
return_value="test-presigned-put",
574+
)
575+
576+
owner = OwnerFactory(service="github", username="codecov")
577+
owner.upload_token_required_for_public_repos = True
578+
owner.save()
579+
580+
repository = RepositoryFactory.create(author=owner, private=False)
581+
commit_sha = "6fd5b89357fc8cdf34d6197549ac7c6d7e5977ef"
582+
CommitFactory.create(repository=repository, commitid=commit_sha, branch="branch")
583+
client = APIClient()
584+
585+
res = client.post(
586+
reverse("upload-test-results"),
587+
{
588+
"commit": commit_sha,
589+
"slug": f"{repository.author.username}::::{repository.name}",
590+
"build": "test-build",
591+
"buildURL": "test-build-url",
592+
"job": "test-job",
593+
"service": "github",
594+
"branch": "branch",
595+
},
596+
format="json",
597+
headers={"User-Agent": "codecov-cli/0.4.7"},
598+
)
599+
assert res.status_code == 401
600+
assert res.json().get("detail") == "Not valid tokenless upload"
601+
commit = Commit.objects.get(commitid=commit_sha)
602+
assert commit
603+
assert commit.branch == "branch"

upload/views/test_results.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
GitHubOIDCTokenAuthentication,
1515
OrgLevelTokenAuthentication,
1616
RepositoryLegacyTokenAuthentication,
17-
TokenlessAuthentication,
17+
TestAnalyticsTokenlessAuthentication,
1818
UploadTokenRequiredGetFromBodyAuthenticationCheck,
1919
repo_auth_custom_exception_handler,
2020
)
@@ -64,7 +64,7 @@ class TestResultsView(
6464
OrgLevelTokenAuthentication,
6565
GitHubOIDCTokenAuthentication,
6666
RepositoryLegacyTokenAuthentication,
67-
TokenlessAuthentication,
67+
TestAnalyticsTokenlessAuthentication,
6868
]
6969

7070
def get_exception_handler(self):

upload/views/uploads.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ class UploadViews(ListCreateAPIView, GetterMixin):
207207
RepositoryLegacyTokenAuthentication,
208208
TokenlessAuthentication,
209209
]
210+
210211
throttle_classes = [UploadsPerCommitThrottle, UploadsPerWindowThrottle]
211212

212213
def get_exception_handler(self) -> Callable[[Exception, Dict[str, Any]], Response]:

0 commit comments

Comments
 (0)