From fab9e18fba53862a7906ed2e118b80f1321690e1 Mon Sep 17 00:00:00 2001 From: Pawel Lipski Date: Thu, 8 Aug 2024 19:41:16 +0200 Subject: [PATCH] Honor `http.sslVerify` git config key for GitHub and GitLab API requests --- RELEASE_NOTES.md | 4 ++++ docs/man/git-machete.1 | 4 ++-- git_machete/__init__.py | 2 +- git_machete/code_hosting.py | 13 ++++++++++++- git_machete/github.py | 2 +- git_machete/gitlab.py | 2 +- tests/mockers_github.py | 6 ++++-- tests/mockers_gitlab.py | 6 ++++-- tests/test_gitlab.py | 1 + tox.ini | 2 +- 10 files changed, 31 insertions(+), 11 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6d49d2572..703dc1a5d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,9 @@ # Release notes +## New in git-machete 3.27.0 + +- added: git config key `http.sslVerify` is honored when connecting to GitHub and GitLab APIs (suggested by @scamden) + ## New in git-machete 3.26.4 - fixed: avoid detecting a cycle when there's a PR/MR from `main` or `master` in a fork to the original repo (reported by @Joibel) diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1 index c3a22abbd..1fcf784bd 100644 --- a/docs/man/git-machete.1 +++ b/docs/man/git-machete.1 @@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "GIT-MACHETE" "1" "Aug 07, 2024" "" "git-machete" +.TH "GIT-MACHETE" "1" "Aug 08, 2024" "" "git-machete" .SH NAME -git-machete \- git-machete 3.26.4 +git-machete \- git-machete 3.27.0 .sp git machete is a robust tool that \fBsimplifies your git workflows\fP\&. .sp diff --git a/git_machete/__init__.py b/git_machete/__init__.py index bcf02c5c1..5a85b26cc 100644 --- a/git_machete/__init__.py +++ b/git_machete/__init__.py @@ -1 +1 @@ -__version__ = '3.26.4' +__version__ = '3.27.0' diff --git a/git_machete/code_hosting.py b/git_machete/code_hosting.py index 55a2fe47f..0eb4ddbae 100644 --- a/git_machete/code_hosting.py +++ b/git_machete/code_hosting.py @@ -1,8 +1,9 @@ import re +import ssl from abc import ABCMeta, abstractmethod from typing import Dict, List, NamedTuple, Optional -from git_machete.git_operations import LocalBranchShortName +from git_machete.git_operations import GitContext, LocalBranchShortName from git_machete.utils import bold @@ -125,8 +126,18 @@ def __init__(self, spec: CodeHostingSpec, domain: str, organization: str, reposi self.domain: str = domain self.organization: str = organization self.repository: str = repository + self.ssl_context = self.__create_ssl_context() self.__org_repo_and_git_url_by_repo_id: Dict[int, Optional[OrganizationAndRepositoryAndGitUrl]] = {} + @staticmethod + def __create_ssl_context() -> ssl.SSLContext: + ctx = ssl.create_default_context() + ssl_verify = GitContext().get_boolean_config_attr_or_none("http.sslVerify") + if not ssl_verify: + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + return ctx + @abstractmethod def create_pull_request(self, head: str, head_org_repo: OrganizationAndRepository, base: str, title: str, description: str, draft: bool) -> PullRequest: diff --git a/git_machete/github.py b/git_machete/github.py index 35f6641ce..a76f7ae74 100644 --- a/git_machete/github.py +++ b/git_machete/github.py @@ -221,7 +221,7 @@ def __fire_github_api_request(self, method: str, path: str, request_body: Option f'bearer token and request body {compact_dict(request_body) if request_body else ""}') try: - with urllib.request.urlopen(http_request) as response: + with urllib.request.urlopen(http_request, context=self.ssl_context) as response: parsed_response_body: Any = json.loads(response.read().decode()) # https://docs.github.com/en/rest/guides/using-pagination-in-the-rest-api?apiVersion=2022-11-28#using-link-headers link_header: str = response.info()["link"] diff --git a/git_machete/gitlab.py b/git_machete/gitlab.py index f3100d97b..ede15b608 100644 --- a/git_machete/gitlab.py +++ b/git_machete/gitlab.py @@ -165,7 +165,7 @@ def __fire_gitlab_api_request(self, method: str, path: str, request_body: Option f'bearer token and request body {compact_dict(request_body) if request_body else ""}') try: - with urllib.request.urlopen(http_request) as response: + with urllib.request.urlopen(http_request, context=self.ssl_context) as response: parsed_response_body: Any = json.loads(response.read().decode()) # https://docs.gitlab.com/ee/api/rest/#pagination-link-header link_header: str = response.info()["link"] diff --git a/tests/mockers_github.py b/tests/mockers_github.py index 9470eb90e..8e95b05e6 100644 --- a/tests/mockers_github.py +++ b/tests/mockers_github.py @@ -1,5 +1,6 @@ import json import re +import ssl from contextlib import AbstractContextManager, contextmanager from http import HTTPStatus from typing import Any, Callable, Dict, Iterator, List, Optional @@ -79,9 +80,10 @@ def add_pull(self, pull: Dict[str, Any]) -> None: # Not including [MockAPIResponse] type argument to maintain compatibility with Python <= 3.8 -def mock_urlopen(github_api_state: MockGitHubAPIState) -> Callable[[Request], AbstractContextManager]: # type: ignore[type-arg] +def mock_urlopen(github_api_state: MockGitHubAPIState, + _context: Optional[ssl.SSLContext] = None) -> Callable[[Request], AbstractContextManager]: # type: ignore[type-arg] @contextmanager - def inner(request: Request) -> Iterator[MockAPIResponse]: + def inner(request: Request, **_kwargs: Any) -> Iterator[MockAPIResponse]: yield __mock_urlopen_impl(github_api_state, request) return inner diff --git a/tests/mockers_gitlab.py b/tests/mockers_gitlab.py index ae4cf12c4..163b88cde 100644 --- a/tests/mockers_gitlab.py +++ b/tests/mockers_gitlab.py @@ -1,5 +1,6 @@ import json import re +import ssl import urllib from contextlib import AbstractContextManager, contextmanager from http import HTTPStatus @@ -94,9 +95,10 @@ def add_mr(self, mr: Dict[str, Any]) -> None: # Not including [MockGitLabAPIResponse] type argument to maintain compatibility with Python <= 3.8 -def mock_urlopen(gitlab_api_state: MockGitLabAPIState) -> Callable[[Request], AbstractContextManager]: # type: ignore[type-arg] +def mock_urlopen(gitlab_api_state: MockGitLabAPIState, + _context: Optional[ssl.SSLContext] = None) -> Callable[[Request], AbstractContextManager]: # type: ignore[type-arg] @contextmanager - def inner(request: Request) -> Iterator[MockAPIResponse]: + def inner(request: Request, **_kwargs: Any) -> Iterator[MockAPIResponse]: yield __mock_urlopen_impl(gitlab_api_state, request) return inner diff --git a/tests/test_gitlab.py b/tests/test_gitlab.py index 7d64ba8eb..aa171b7da 100644 --- a/tests/test_gitlab.py +++ b/tests/test_gitlab.py @@ -84,6 +84,7 @@ def test_gitlab_enterprise_domain_unauthorized_without_token(self, mocker: Mocke self.patch_symbol(mocker, 'git_machete.code_hosting.OrganizationAndRepository.from_url', mock_from_url) self.patch_symbol(mocker, 'urllib.request.urlopen', mock_urlopen(MockGitLabAPIState.with_mrs())) + self.repo_sandbox.set_git_config_key('http.sslVerify', 'false') self.repo_sandbox.set_git_config_key('machete.gitlab.domain', '403.example.org') expected_error_message = ( diff --git a/tox.ini b/tox.ini index 2aac28494..0f846dc23 100644 --- a/tox.ini +++ b/tox.ini @@ -75,7 +75,7 @@ commands = isort --check-only . description = "Run `vulture` static code analyzer to detect unused code" deps = -r{[requirements]dir}/vulture-check.txt -commands = vulture git_machete/ tests/ +commands = vulture --ignore-names check_hostname,verify_mode git_machete/ tests/ [testenv:venv] commands = {posargs}