Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions codecov_auth/authentication/repo_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.utils import timezone
from jwt import PyJWTError
from rest_framework import authentication, exceptions, serializers
from rest_framework.exceptions import NotAuthenticated
from rest_framework.exceptions import NotAuthenticated, ValidationError
from rest_framework.response import Response
from rest_framework.views import exception_handler
from shared.django_apps.codecov_auth.models import Owner
Expand Down Expand Up @@ -200,7 +200,7 @@ def authenticate(
if not using_global_token:
return None # continue to next auth class

service = global_tokens[token]
service = global_tokens.get(token, "")
upload_info = get_upload_info_from_request_path(request)
if upload_info is None:
return None # continue to next auth class
Expand Down Expand Up @@ -257,7 +257,7 @@ def authenticate_credentials(

try:
repository = get_repo_with_github_actions_oidc_token(token)
except (ObjectDoesNotExist, PyJWTError):
except (ObjectDoesNotExist, PyJWTError, ValidationError):
return None # continue to next auth class

log.info(
Expand Down
8 changes: 6 additions & 2 deletions upload/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
from json import dumps
from typing import Any, Dict, Optional
from urllib.parse import urlparse

import jwt
from asgiref.sync import async_to_sync
Expand Down Expand Up @@ -237,12 +238,15 @@ def parse_params(data: Dict[str, Any]) -> Dict[str, Any]:
def get_repo_with_github_actions_oidc_token(token: str) -> Repository:
unverified_contents = jwt.decode(token, options={"verify_signature": False})
token_issuer = str(unverified_contents.get("iss"))
if token_issuer == "https://token.actions.githubusercontent.com":
parsed_url = urlparse(token_issuer)
if parsed_url.hostname == "token.actions.githubusercontent.com":
service = "github"
jwks_url = "https://token.actions.githubusercontent.com/.well-known/jwks"
else:
service = "github_enterprise"
github_enterprise_url = get_config("github_enterprise", "url")
if not github_enterprise_url:
raise ValidationError("GitHub Enterprise URL configuration is not set")
# remove trailing slashes if present
github_enterprise_url = re.sub(r"/+$", "", github_enterprise_url)
jwks_url = f"{github_enterprise_url}/_services/token/.well-known/jwks"
Expand Down Expand Up @@ -525,7 +529,7 @@ def insert_commit(
return commit


def get_global_tokens() -> Dict[str, Any]:
def get_global_tokens() -> Dict[str | None, Any]:
"""
Enterprise only: check the config to see if global tokens were set for this organization's uploads.

Expand Down
2 changes: 1 addition & 1 deletion upload/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ def test_determine_repo_for_upload_github_actions(codecov_vcr):
repository = RepositoryFactory.create()
token = jwt.encode(
{
"iss": "https://token.actions.githubusercontent.com",
"iss": "https://token.actions.githubusercontent.com/abcdefg",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

"aud": [f"{settings.CODECOV_API_URL}"],
"repository": f"{repository.author.username}/{repository.name}",
"repository_owner": repository.author.username,
Expand Down
56 changes: 56 additions & 0 deletions upload/tests/views/test_uploads.py
Original file line number Diff line number Diff line change
Expand Up @@ -929,3 +929,59 @@ def test_uploads_post_github_enterprise_oidc_auth_jwks_url(
mock_jwks_client.assert_called_with(
"https://example.com/_services/token/.well-known/jwks"
)

@patch("upload.views.uploads.AnalyticsService")
@patch("upload.helpers.jwt.decode")
@patch("upload.helpers.PyJWKClient")
def test_uploads_post_github_enterprise_oidc_auth_no_url(
self,
mock_jwks_client,
mock_jwt_decode,
analytics_service_mock,
):
mock_config_helper(self.mocker, configs={"github_enterprise.url": None})
self.mocker.patch(
"shared.api_archive.archive.StorageService.create_presigned_put",
return_value="presigned put",
)
self.mocker.patch("upload.views.uploads.trigger_upload_task", return_value=True)

repository = RepositoryFactory(
name="the_repo",
author__username="codecov",
author__service="github_enterprise",
author__upload_token_required_for_public_repos=True,
private=False,
)
mock_jwt_decode.return_value = {
"repository": f"url/{repository.name}",
"repository_owner": repository.author.username,
"iss": "https://enterprise-client.actions.githubusercontent.com",
"audience": [settings.CODECOV_API_URL],
}
token = "ThisValueDoesNotMatterBecauseOf_mock_jwt_decode"

commit = CommitFactory(repository=repository)
commit_report = CommitReport.objects.create(commit=commit, code="code")

client = APIClient()
url = reverse(
"new_upload.uploads",
args=[
"github_enterprise",
"codecov::::the_repo",
commit.commitid,
commit_report.code,
],
)
response = client.post(
url,
{
"state": "uploaded",
"flags": ["flag1", "flag2"],
"version": "version",
},
headers={"Authorization": f"token {token}"},
)
assert response.status_code == 401
assert response.json().get("detail") == "Not valid tokenless upload"