Skip to content

Commit 2be4e29

Browse files
feat: Add upload url validation before making requests (#701)
* feat: Validate service part of api request url * fix: Add upload url validation to upload sender
1 parent f7cb28c commit 2be4e29

11 files changed

Lines changed: 113 additions & 43 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import click
2+
3+
from codecov_cli.helpers.git import GitService
4+
5+
# The set of acceptable upload services from Shelter
6+
_CODECOV_UPLOAD_SERVICES = frozenset(s.value for s in GitService) | {"github-actions"}
7+
8+
9+
def validate_upload_service(
10+
service: str,
11+
) -> None:
12+
allowed = ", ".join(sorted(_CODECOV_UPLOAD_SERVICES))
13+
if not service:
14+
raise click.ClickException(
15+
"Upload service is missing (Codecov requires it for upload URLs). "
16+
f"Pass --git-service with one of: {allowed}"
17+
)
18+
if service not in _CODECOV_UPLOAD_SERVICES:
19+
raise click.ClickException(
20+
f"Invalid upload service {service!r}. Use one of: {allowed}"
21+
)

codecov-cli/codecov_cli/services/commit/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
log_warnings_and_errors_if_any,
1010
send_post_request,
1111
)
12+
from codecov_cli.helpers.upload_url_validation import validate_upload_service
1213

1314
logger = logging.getLogger("codecovcli")
1415

15-
1616
def create_commit_logic(
1717
commit_sha: str,
1818
parent_sha: typing.Optional[str],
@@ -78,7 +78,9 @@ def send_commit_data(
7878
}
7979

8080
upload_url = enterprise_url or CODECOV_INGEST_URL
81-
url = f"{upload_url}/upload/{service}/{slug}/commits"
81+
service_part = (service or "").strip()
82+
validate_upload_service(service_part)
83+
url = f"{upload_url}/upload/{service_part}/{slug}/commits"
8284
return send_post_request(
8385
url=url,
8486
data=data,

codecov-cli/codecov_cli/services/empty_upload/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from codecov_cli.helpers.config import CODECOV_API_URL
55
from codecov_cli.helpers.encoder import encode_slug
6+
from codecov_cli.helpers.upload_url_validation import validate_upload_service
7+
68
from codecov_cli.helpers.request import (
79
get_token_header,
810
log_warnings_and_errors_if_any,
@@ -25,7 +27,9 @@ def empty_upload_logic(
2527
encoded_slug = encode_slug(slug)
2628
headers = get_token_header(token)
2729
upload_url = enterprise_url or CODECOV_API_URL
28-
url = f"{upload_url}/upload/{git_service}/{encoded_slug}/commits/{commit_sha}/empty-upload"
30+
service_part = (git_service or "").strip()
31+
validate_upload_service(service_part)
32+
url = f"{upload_url}/upload/{service_part}/{encoded_slug}/commits/{commit_sha}/empty-upload"
2933
sending_result = send_post_request(
3034
url=url,
3135
headers=headers,

codecov-cli/codecov_cli/services/report/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
request_result,
1515
send_post_request,
1616
)
17+
from codecov_cli.helpers.upload_url_validation import validate_upload_service
1718

1819
logger = logging.getLogger("codecovcli")
1920
MAX_NUMBER_TRIES = 3
@@ -61,7 +62,9 @@ def send_create_report_request(
6162
}
6263
headers = get_token_header(token)
6364
upload_url = enterprise_url or CODECOV_INGEST_URL
64-
url = f"{upload_url}/upload/{service}/{encoded_slug}/commits/{commit_sha}/reports"
65+
service_part = (service or "").strip()
66+
validate_upload_service(service_part)
67+
url = f"{upload_url}/upload/{service_part}/{encoded_slug}/commits/{commit_sha}/reports"
6568
return send_post_request(url=url, headers=headers, data=data)
6669

6770

@@ -106,7 +109,9 @@ def send_reports_result_request(
106109
}
107110
headers = get_token_header(token)
108111
upload_url = enterprise_url or CODECOV_API_URL
109-
url = f"{upload_url}/upload/{service}/{encoded_slug}/commits/{commit_sha}/reports/{report_code}/results"
112+
service_part = (service or "").strip()
113+
validate_upload_service(service_part)
114+
url = f"{upload_url}/upload/{service_part}/{encoded_slug}/commits/{commit_sha}/reports/{report_code}/results"
110115
return send_post_request(url=url, data=data, headers=headers)
111116

112117

@@ -121,7 +126,9 @@ def send_reports_result_get_request(
121126
):
122127
headers = get_token_header(token)
123128
upload_url = enterprise_url or CODECOV_API_URL
124-
url = f"{upload_url}/upload/{service}/{encoded_slug}/commits/{commit_sha}/reports/{report_code}/results"
129+
service_part = (service or "").strip()
130+
validate_upload_service(service_part)
131+
url = f"{upload_url}/upload/{service_part}/{encoded_slug}/commits/{commit_sha}/reports/{report_code}/results"
125132
number_tries = 0
126133
while number_tries < MAX_NUMBER_TRIES:
127134
resp = request.get(url=url, headers=headers)

codecov-cli/codecov_cli/services/upload/upload_sender.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from codecov_cli.helpers.config import CODECOV_INGEST_URL
1313
from codecov_cli.helpers.encoder import encode_slug
1414
from codecov_cli.helpers.upload_type import ReportType
15+
from codecov_cli.helpers.upload_url_validation import validate_upload_service
1516
from codecov_cli.helpers.request import (
1617
get_token_header,
1718
send_post_request,
@@ -218,8 +219,11 @@ def get_url_and_possibly_update_data(
218219
upload_coverage=False,
219220
file_not_found=False,
220221
):
222+
service_part = (git_service or "").strip()
223+
221224
if report_type == ReportType.COVERAGE:
222-
base_url = f"{upload_url}/upload/{git_service}/{encoded_slug}"
225+
validate_upload_service(service_part)
226+
base_url = f"{upload_url}/upload/{service_part}/{encoded_slug}"
223227
if upload_coverage:
224228
url = f"{base_url}/upload-coverage"
225229
else:
@@ -228,7 +232,7 @@ def get_url_and_possibly_update_data(
228232
data["slug"] = encoded_slug
229233
data["branch"] = branch
230234
data["commit"] = commit_sha
231-
data["service"] = git_service
235+
data["service"] = service_part if service_part else None
232236
data["file_not_found"] = file_not_found
233237
url = f"{upload_url}/upload/test_results/v1"
234238

codecov-cli/codecov_cli/services/upload_completion/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from codecov_cli.helpers.config import CODECOV_API_URL
55
from codecov_cli.helpers.encoder import encode_slug
6+
from codecov_cli.helpers.upload_url_validation import validate_upload_service
67
from codecov_cli.helpers.request import (
78
get_token_header,
89
log_warnings_and_errors_if_any,
@@ -24,7 +25,9 @@ def upload_completion_logic(
2425
encoded_slug = encode_slug(slug)
2526
headers = get_token_header(token)
2627
upload_url = enterprise_url or CODECOV_API_URL
27-
url = f"{upload_url}/upload/{git_service}/{encoded_slug}/commits/{commit_sha}/upload-complete"
28+
service_part = (git_service or "").strip()
29+
validate_upload_service(service_part)
30+
url = f"{upload_url}/upload/{service_part}/{encoded_slug}/commits/{commit_sha}/upload-complete"
2831
data = {
2932
"cli_args": args,
3033
}

codecov-cli/tests/services/commit/test_commit_service.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import uuid
22

3+
import click
4+
import pytest
35
from click.testing import CliRunner
46

57
from codecov_cli.services.commit import create_commit_logic, send_commit_data
@@ -26,7 +28,7 @@ def test_commit_command_with_warnings(mocker):
2628
branch="branch",
2729
slug="owner/repo",
2830
token="token",
29-
service="service",
31+
service="github",
3032
)
3133

3234
out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue())
@@ -43,7 +45,7 @@ def test_commit_command_with_warnings(mocker):
4345
branch="branch",
4446
slug="owner::::repo",
4547
token="token",
46-
service="service",
48+
service="github",
4749
enterprise_url=None,
4850
args=None,
4951
)
@@ -72,7 +74,7 @@ def test_commit_command_with_error(mocker):
7274
branch="branch",
7375
slug="owner/repo",
7476
token="token",
75-
service="service",
77+
service="github",
7678
enterprise_url=None,
7779
args={},
7880
)
@@ -93,7 +95,7 @@ def test_commit_command_with_error(mocker):
9395
branch="branch",
9496
slug="owner::::repo",
9597
token="token",
96-
service="service",
98+
service="github",
9799
enterprise_url=None,
98100
args={},
99101
)
@@ -112,7 +114,7 @@ def test_commit_sender_200(mocker):
112114
"branch",
113115
"owner::::repo",
114116
token,
115-
"service",
117+
"github",
116118
None,
117119
None,
118120
)
@@ -134,7 +136,7 @@ def test_commit_sender_403(mocker):
134136
"branch",
135137
"owner::::repo",
136138
token,
137-
"service",
139+
"github",
138140
None,
139141
None,
140142
)
@@ -176,6 +178,33 @@ def test_commit_sender_with_forked_repo(mocker):
176178
)
177179

178180

181+
@pytest.mark.parametrize(
182+
"service,slug,enterprise_url,fragment",
183+
[
184+
(None, "o::::r", None, "Upload service is missing"),
185+
("", "o::::r", None, "Upload service is missing"),
186+
("circleci", "o::::r", None, "Invalid upload service"),
187+
],
188+
)
189+
def test_commit_sender_rejects_invalid_url_parts(
190+
mocker, service, slug, enterprise_url, fragment
191+
):
192+
mocker.patch("codecov_cli.helpers.request.requests.post")
193+
with pytest.raises(click.ClickException) as excinfo:
194+
send_commit_data(
195+
"commit_sha",
196+
"parent_sha",
197+
"pr",
198+
"branch",
199+
slug,
200+
uuid.uuid4(),
201+
service,
202+
enterprise_url,
203+
None,
204+
)
205+
assert fragment in str(excinfo.value)
206+
207+
179208
def test_commit_without_token(mocker):
180209
mocked_response = mocker.patch(
181210
"codecov_cli.services.commit.send_post_request",

codecov-cli/tests/services/empty_upload/test_empty_upload.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_empty_upload_with_warnings(mocker):
2626
"commit_sha",
2727
"owner/repo",
2828
uuid.uuid4(),
29-
"service",
29+
"github",
3030
None,
3131
False,
3232
False,
@@ -62,7 +62,7 @@ def test_empty_upload_with_error(mocker):
6262
"commit_sha",
6363
"owner/repo",
6464
uuid.uuid4(),
65-
"service",
65+
"github",
6666
None,
6767
False,
6868
False,
@@ -93,7 +93,7 @@ def test_empty_upload_200(mocker):
9393
runner = CliRunner()
9494
with runner.isolation() as outstreams:
9595
res = empty_upload_logic(
96-
"commit_sha", "owner/repo", token, "service", None, False, False, None
96+
"commit_sha", "owner/repo", token, "github", None, False, False, None
9797
)
9898
out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue())
9999
assert out_bytes == [
@@ -113,7 +113,7 @@ def test_empty_upload_403(mocker):
113113
)
114114
token = uuid.uuid4()
115115
res = empty_upload_logic(
116-
"commit_sha", "owner/repo", token, "service", None, False, False, None
116+
"commit_sha", "owner/repo", token, "github", None, False, False, None
117117
)
118118
assert res.error == RequestError(
119119
code="HTTP Error 403",
@@ -138,7 +138,7 @@ def test_empty_upload_force(mocker):
138138
runner = CliRunner()
139139
with runner.isolation() as outstreams:
140140
res = empty_upload_logic(
141-
"commit_sha", "owner/repo", token, "service", None, False, True, None
141+
"commit_sha", "owner/repo", token, "github", None, False, True, None
142142
)
143143
out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue())
144144
assert out_bytes == [
@@ -165,7 +165,7 @@ def test_empty_upload_no_token(mocker):
165165
runner = CliRunner()
166166
with runner.isolation() as outstreams:
167167
res = empty_upload_logic(
168-
"commit_sha", "owner/repo", None, "service", None, False, False, None
168+
"commit_sha", "owner/repo", None, "github", None, False, False, None
169169
)
170170

171171
out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue())

0 commit comments

Comments
 (0)