Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

push to staging #459

Merged
merged 15 commits into from
Mar 18, 2024
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: 5 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
upload/ @codecov/Platform
graphql_api/ @codecov/Applications
**/migrations/ @codecov/database-migration-reviewers
**/migrations/ @codecov/database-migration-reviewers

codecov_auth/models.py @adrian-codecov
core/models.py @adrian-codecov
reports/models.py @adrian-codecov
26 changes: 13 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ jobs:
uses: codecov/gha-workflows/.github/workflows/codecov-startup.yml@v1.2.12
secrets: inherit

ats:
name: ATS
needs: [build]
if: ${{ !github.event.pull_request.head.repo.fork && github.repository_owner == 'codecov' }}
uses: codecov/gha-workflows/.github/workflows/run-ats.yml@v1.2.12
secrets: inherit
with:
repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
codecov_cli_upload_args: '--plugin pycoverage --plugin compress-pycoverage --flag smart-labels'
app_container_name: api
# ats:
# name: ATS
# needs: [build]
# if: ${{ !github.event.pull_request.head.repo.fork && github.repository_owner == 'codecov' }}
# uses: codecov/gha-workflows/.github/workflows/run-ats.yml@v1.2.12
# secrets: inherit
# with:
# repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
# codecov_cli_upload_args: '--plugin pycoverage --plugin compress-pycoverage --flag smart-labels'
# app_container_name: api
test:
name: Test
needs: [build]
Expand All @@ -64,8 +64,8 @@ jobs:
staging:
name: Push Staging Image
needs: [build, test]
if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/staging' && github.repository_owner == 'codecov' }}
uses: codecov/gha-workflows/.github/workflows/push-env.yml@v1.2.12
if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/test-prodvana' && github.repository_owner == 'codecov' }}
uses: codecov/gha-workflows/.github/workflows/push-env.yml@v1.2.16
secrets: inherit
with:
environment: staging
Expand All @@ -89,4 +89,4 @@ jobs:
uses: codecov/gha-workflows/.github/workflows/self-hosted.yml@v1.2.12
with:
push_rolling: true
repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
2 changes: 2 additions & 0 deletions codecov/settings_dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@

# add for shelter
# SHELTER_SHARED_SECRET = "test-supertoken"

GUEST_ACCESS = True
2 changes: 2 additions & 0 deletions codecov/settings_enterprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,5 @@
CSRF_TRUSTED_ORIGINS = [
get_config("setup", "trusted_origin", default=DEFAULT_TRUSTED_ORIGIN)
]

GUEST_ACCESS = get_config("setup", "guest_access", default=True)
17 changes: 16 additions & 1 deletion codecov_auth/authentication/repo_auth.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from datetime import datetime
from typing import List
from uuid import UUID

Expand All @@ -8,7 +9,9 @@
from django.db.models import QuerySet
from django.utils import timezone
from rest_framework import authentication, exceptions
from shared.torngit.exceptions import TorngitObjectNotFoundError
from sentry_sdk import metrics as sentry_metrics
from shared.metrics import metrics
from shared.torngit.exceptions import TorngitObjectNotFoundError, TorngitRateLimitError

from codecov_auth.authentication.types import RepositoryAsUser, RepositoryAuthInterface
from codecov_auth.models import (
Expand Down Expand Up @@ -211,6 +214,7 @@ class TokenlessAuthentication(authentication.TokenAuthentication):
"""

auth_failed_message = "Not valid tokenless upload"
rate_limit_failed_message = "Tokenless has reached GitHub rate limit. Please upload using a token: https://docs.codecov.com/docs/adding-the-codecov-token."

def _get_repo_info_from_request_path(self, request) -> Repository:
path_info = request.get_full_path_info()
Expand Down Expand Up @@ -245,6 +249,17 @@ async def get_pull_request_info(self, repository_service, fork_pr: str):
return await repository_service.get_pull_request(fork_pr)
except TorngitObjectNotFoundError:
raise exceptions.AuthenticationFailed(self.auth_failed_message)
except TorngitRateLimitError as e:
metrics.incr("auth.get_pr_info.rate_limit_hit")
sentry_metrics.incr("auth.get_pr_info.rate_limit_hit")
if e.reset:
now_timestamp = datetime.now().timestamp()
retry_after = int(e.reset) - int(now_timestamp)
elif e.retry_after:
retry_after = int(e.retry_after)
raise exceptions.Throttled(
wait=retry_after, detail=self.rate_limit_failed_message
)

def authenticate(self, request):
fork_slug = request.headers.get("X-Tokenless", None)
Expand Down
3 changes: 2 additions & 1 deletion codecov_auth/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
ACCESS_CONTROL_ALLOW_ORIGIN,
)
from corsheaders.middleware import CorsMiddleware as BaseCorsMiddleware
from django.http import HttpRequest
from django.conf import settings
from django.http import HttpRequest, JsonResponse
from django.urls import resolve
from django.utils.deprecation import MiddlewareMixin
from rest_framework import exceptions
Expand Down
26 changes: 25 additions & 1 deletion codecov_auth/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
SentryUserFactory,
UserFactory,
)
from plan.constants import ENTERPRISE_CLOUD_USER_PLAN_REPRESENTATIONS, TrialStatus
from plan.constants import (
ENTERPRISE_CLOUD_USER_PLAN_REPRESENTATIONS,
PlanName,
TrialStatus,
)
from utils.test_utils import APIClient


Expand Down Expand Up @@ -293,6 +297,26 @@ def test_start_trial_action(self, mock_start_trial_service):
assert res.status_code == 302
assert mock_start_trial_service.called

@patch("plan.service.PlanService._start_trial_helper")
def test_extend_trial_action(self, mock_start_trial_service):
mock_start_trial_service.return_value = None
org_to_be_trialed = OwnerFactory()
org_to_be_trialed.plan = PlanName.TRIAL_PLAN_NAME.value
org_to_be_trialed.save()

res = self.client.post(
reverse("admin:codecov_auth_owner_changelist"),
{
"action": "extend_trial",
ACTION_CHECKBOX_NAME: [org_to_be_trialed.pk],
"end_date": "2024-01-01 01:02:03",
"extend_trial": True,
},
)
assert res.status_code == 302
assert mock_start_trial_service.called
assert mock_start_trial_service.call_args.kwargs == {"is_extension": True}

@patch("plan.service.PlanService.start_trial_manually")
def test_start_trial_paid_plan(self, mock_start_trial_service):
mock_start_trial_service.side_effect = ValidationError(
Expand Down
2 changes: 2 additions & 0 deletions codecov_auth/tests/unit/test_middleware.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.test import TestCase, override_settings
from django.urls import reverse

from codecov_auth.tests.factories import OwnerFactory
from utils.test_utils import Client


Expand Down
43 changes: 42 additions & 1 deletion codecov_auth/tests/unit/test_repo_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.utils import timezone
from rest_framework import exceptions
from rest_framework.test import APIRequestFactory
from shared.torngit.exceptions import TorngitObjectNotFoundError
from shared.torngit.exceptions import TorngitObjectNotFoundError, TorngitRateLimitError

from codecov_auth.authentication.repo_auth import (
GlobalTokenAuthentication,
Expand Down Expand Up @@ -532,3 +532,44 @@ def test_tokenless_success(self, mock_repo_provider, db, mocker):
assert repo_as_user.is_authenticated()
assert isinstance(auth_class, TokenlessAuth)
mock_adapter.get_pull_request.assert_called_with("15")

@patch("codecov_auth.authentication.repo_auth.RepoProviderService")
def test_tokenless_rate_limit(self, mock_repo_provider, db, mocker):
repo = RepositoryFactory(private=False)
err = TorngitRateLimitError(
"error", "err msg", int(datetime.now().timestamp()) + 20, None
)
mock_adapter = MagicMock(
name="mock_provider_adapter",
get_pull_request=AsyncMock(name="mock_get_pr", side_effect=err),
)
mock_repo_provider.return_value.get_adapter.return_value = mock_adapter

request = APIRequestFactory().post(
f"/upload/github/{repo.author.username}::::{repo.name}/commits/commit_sha/reports/report_code/uploads",
headers={"X-Tokenless": f"some-user/{repo.name}", "X-Tokenless-PR": "15"},
)
authentication = TokenlessAuthentication()

with pytest.raises(exceptions.Throttled):
res = authentication.authenticate(request)
mock_adapter.get_pull_request.assert_called_with("15")

@patch("codecov_auth.authentication.repo_auth.RepoProviderService")
def test_tokenless_rate_limit_retry_after(self, mock_repo_provider, db, mocker):
repo = RepositoryFactory(private=False)
err = TorngitRateLimitError("error", "err msg", None, 20)
mock_adapter = MagicMock(
name="mock_provider_adapter",
get_pull_request=AsyncMock(name="mock_get_pr", side_effect=err),
)
mock_repo_provider.return_value.get_adapter.return_value = mock_adapter

request = APIRequestFactory().post(
f"/upload/github/{repo.author.username}::::{repo.name}/commits/commit_sha/reports/report_code/uploads",
headers={"X-Tokenless": f"some-user/{repo.name}", "X-Tokenless-PR": "15"},
)
authentication = TokenlessAuthentication()
with pytest.raises(exceptions.Throttled):
res = authentication.authenticate(request)
mock_adapter.get_pull_request.assert_called_with("15")
Loading
Loading