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 6 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 @@ -337,7 +337,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"