Skip to content
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
45 changes: 34 additions & 11 deletions src/git_commit_guard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from argparse import ArgumentParser
from dataclasses import dataclass, field
from enum import StrEnum
from http import HTTPStatus
from pathlib import Path

import nltk
Expand Down Expand Up @@ -387,22 +388,44 @@ def _verify_ssh(rev, email, ssh_text):
Path(signers_path).unlink(missing_ok=True)


def _resolve_github_username(rev, email):
username = None
commits_api_404 = False
remote = _get_github_remote_info()
if remote:
owner, repo = remote
try:
username = _fetch_github_commit_author(owner, repo, rev)
except urllib.error.HTTPError as e:
if e.code == HTTPStatus.NOT_FOUND:
commits_api_404 = True
except (urllib.error.URLError, TimeoutError):
pass
if username is None:
username = _parse_noreply_username(email)
if username is None:
username = _fetch_github_username(email)
return username, commits_api_404


def _author_not_found_message(commits_api_404):
had_token = bool(os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN"))
if commits_api_404 and not had_token:
return (
"commit author not found on GitHub — if the repo is private, "
"set GITHUB_TOKEN in the workflow step "
"(env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }})"
)
return "commit author not found on GitHub — cannot verify signature"


def check_signature(rev, result):
try:
email = _get_author_email(rev)
username = None
remote = _get_github_remote_info()
if remote:
owner, repo = remote
with contextlib.suppress(urllib.error.URLError, TimeoutError):
username = _fetch_github_commit_author(owner, repo, rev)
if username is None:
username = _parse_noreply_username(email)
if username is None:
username = _fetch_github_username(email)
username, commits_api_404 = _resolve_github_username(rev, email)
if username is None:
result.error(
"commit author not found on GitHub — cannot verify signature",
_author_not_found_message(commits_api_404),
check=Check.SIGNATURE,
)
return
Expand Down
73 changes: 73 additions & 0 deletions tests/test_git_commit_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,79 @@ def test_username_not_found_fails(self):
assert not r.ok
assert any("not found on GitHub" in msg for _, _, msg in r.errors)

def test_commits_api_404_without_token_hints_at_token(self):
r = Result()
env = {
k: v for k, v in os.environ.items() if k not in ("GITHUB_TOKEN", "GH_TOKEN")
}
with (
patch(
"git_commit_guard._get_author_email", return_value="user@example.com"
),
patch(
"git_commit_guard._get_github_remote_info",
return_value=("owner", "repo"),
),
patch(
"git_commit_guard._fetch_github_commit_author",
side_effect=urllib.error.HTTPError(
url="", code=404, msg="Not Found", hdrs=None, fp=None
),
),
patch("git_commit_guard._fetch_github_username", return_value=None),
patch.dict("os.environ", env, clear=True),
):
check_signature("abc123", r)
assert not r.ok
assert any("set GITHUB_TOKEN" in msg for _, _, msg in r.errors)

def test_commits_api_404_with_token_keeps_generic_message(self):
r = Result()
with (
patch(
"git_commit_guard._get_author_email", return_value="user@example.com"
),
patch(
"git_commit_guard._get_github_remote_info",
return_value=("owner", "repo"),
),
patch(
"git_commit_guard._fetch_github_commit_author",
side_effect=urllib.error.HTTPError(
url="", code=404, msg="Not Found", hdrs=None, fp=None
),
),
patch("git_commit_guard._fetch_github_username", return_value=None),
patch.dict("os.environ", {"GITHUB_TOKEN": "x"}, clear=False),
):
check_signature("abc123", r)
assert not r.ok
assert not any("set GITHUB_TOKEN" in msg for _, _, msg in r.errors)
assert any("cannot verify signature" in msg for _, _, msg in r.errors)

def test_commits_api_non_404_http_error_falls_through(self):
r = Result()
with (
patch(
"git_commit_guard._get_author_email", return_value="user@example.com"
),
patch(
"git_commit_guard._get_github_remote_info",
return_value=("owner", "repo"),
),
patch(
"git_commit_guard._fetch_github_commit_author",
side_effect=urllib.error.HTTPError(
url="", code=500, msg="Server Error", hdrs=None, fp=None
),
),
patch("git_commit_guard._fetch_github_username", return_value="emailuser"),
patch("git_commit_guard._fetch_github_keys", return_value=("GPG KEY", "")),
patch("git_commit_guard._verify_gpg", return_value=True),
):
check_signature("abc123", r)
assert r.ok

def test_url_error_fails(self):
r = Result()
with patch(
Expand Down
Loading