Raise error on unsupported redirects, log supported redirects (#2524)

This commit is contained in:
Enrico Minack
2023-05-13 20:36:08 +02:00
committed by GitHub
parent 600217f04f
commit 17cd0b7964
9 changed files with 220 additions and 9 deletions
+32 -5
View File
@@ -386,7 +386,7 @@ class Requester:
if not self.__installation_authorization:
return
if self._must_refresh_token():
logging.debug("Refreshing access token")
self._logger.debug("Refreshing access token")
self._refresh_token()
def _refresh_token(self) -> None:
@@ -617,7 +617,30 @@ class Requester:
return self.__requestRaw(original_cnx, verb, url, requestHeaders, input)
if status == 301 and "location" in responseHeaders:
o = urllib.parse.urlparse(responseHeaders["location"])
location = responseHeaders["location"]
o = urllib.parse.urlparse(location)
if o.scheme != self.__scheme:
raise RuntimeError(
f"Github server redirected from {self.__scheme} protocol to {o.scheme}, "
f"please correct your Github server URL via base_url: Github(base_url=...)"
)
if o.hostname != self.__hostname:
raise RuntimeError(
f"Github server redirected from host {self.__hostname} to {o.hostname}, "
f"please correct your Github server URL via base_url: Github(base_url=...)"
)
if o.path == url:
port = ":" + str(self.__port) if self.__port is not None else ""
requested_location = f"{self.__scheme}://{self.__hostname}{port}{url}"
raise RuntimeError(
f"Requested {requested_location} but server redirected to {location}, "
f"you may need to correct your Github server URL "
f"via base_url: Github(base_url=...)"
)
if self._logger.isEnabledFor(logging.INFO):
self._logger.info(
f"Following Github server redirection from {url} to {o.path}"
)
return self.__requestRaw(original_cnx, verb, o.path, requestHeaders, input)
return status, responseHeaders, output
@@ -671,10 +694,14 @@ class Requester:
return self.__connection
def __log(self, verb, url, requestHeaders, input, status, responseHeaders, output):
@property
def _logger(self):
if self.__logger is None:
self.__logger = logging.getLogger(__name__)
if self.__logger.isEnabledFor(logging.DEBUG):
return self.__logger
def __log(self, verb, url, requestHeaders, input, status, responseHeaders, output):
if self._logger.isEnabledFor(logging.DEBUG):
headersForRequest = requestHeaders.copy()
if "Authorization" in requestHeaders:
if requestHeaders["Authorization"].startswith("Basic"):
@@ -689,7 +716,7 @@ class Requester:
headersForRequest[
"Authorization"
] = "(unknown auth removed)" # pragma no cover (Cannot happen, but could if we add an authentication method => be prepared)
self.__logger.debug(
self._logger.debug(
"%s %s://%s%s %s %s ==> %i %s %s",
verb,
self.__scheme,
+4
View File
@@ -1,5 +1,6 @@
from collections import OrderedDict
from io import BufferedReader
from logging import Logger
from typing import Any, Callable, Dict, Iterator, Optional, Tuple, Union
from requests.models import Response
@@ -51,6 +52,7 @@ class HTTPSRequestsConnectionClass:
class Requester:
__installation_authorization: Optional[InstallationAuthorization] = ...
__app_auth: Optional[AppAuthentication] = ...
__logger: Logger
def DEBUG_ON_RESPONSE(
self, statusCode: int, responseHeader: Dict[str, str], data: str
) -> None: ...
@@ -85,6 +87,8 @@ class Requester:
headers: Dict[str, Any],
output: str,
) -> Any: ...
@property
def _logger(self) -> Logger: ...
def __log(
self,
verb: str,
+14 -4
View File
@@ -72,12 +72,22 @@ from .InputFileContent import InputFileContent
from .InputGitAuthor import InputGitAuthor
from .InputGitTreeElement import InputGitTreeElement
# set log level to INFO for github
logger = logging.getLogger("github")
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler())
def set_log_level(level: int):
"""
Set the log level of the github logger, e.g. set_log_level(logging.WARNING)
:param level: log level
"""
logger.setLevel(level)
def enable_console_debug_logging(): # pragma no cover (Function useful only outside test environment)
"""
This function sets up a very simple logging configuration (log everything on standard output) that is useful for troubleshooting.
"""
logger = logging.getLogger("github")
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())
set_log_level(logging.DEBUG)
@@ -0,0 +1,11 @@
https
GET
www.github.com
None
/repos/PyGithub/PyGithub
{'User-Agent': 'PyGithub/Python'}
None
301
[('Content-Length', '0'), ('Location', 'https://github.com/repos/PyGithub/PyGithub')]
@@ -0,0 +1,11 @@
https
GET
api.github.com
None
/repos/PyGithub/PyGithub
{'User-Agent': 'PyGithub/Python'}
None
301
[('Content-Length', '0'), ('Location', 'https://api.github.com:443/repos/PyGithub/PyGithub')]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
http
GET
api.github.com
None
/repos/PyGithub/PyGithub
{'User-Agent': 'PyGithub/Python'}
None
301
[('Content-Length', '0'), ('Location', 'https://api.github.com/repos/PyGithub/PyGithub')]
File diff suppressed because one or more lines are too long
+93
View File
@@ -0,0 +1,93 @@
############################ Copyrights and license ############################
# #
# Copyright 2023 Enrico Minack <github@enrico.minack.dev> #
# #
# This file is part of PyGithub. #
# http://pygithub.readthedocs.io/ #
# #
# PyGithub is free software: you can redistribute it and/or modify it under #
# the terms of the GNU Lesser General Public License as published by the Free #
# Software Foundation, either version 3 of the License, or (at your option) #
# any later version. #
# #
# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY #
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more #
# details. #
# #
# You should have received a copy of the GNU Lesser General Public License #
# along with PyGithub. If not, see <http://www.gnu.org/licenses/>. #
# #
################################################################################
from unittest import mock
import github
from . import Framework
class Requester(Framework.TestCase):
logger = None
def setUp(self):
super().setUp()
self.logger = mock.MagicMock()
github.Requester.Requester.injectLogger(self.logger)
def tearDown(self):
github.Requester.Requester.resetLogger()
super().tearDown()
def testLoggingRedirection(self):
self.assertEqual(self.g.get_repo("EnricoMi/test").name, "test-renamed")
self.logger.info.assert_called_once_with(
"Following Github server redirection from /repos/EnricoMi/test to /repositories/638123443"
)
def testBaseUrlSchemeRedirection(self):
gh = github.Github(base_url="http://api.github.com")
with self.assertRaises(RuntimeError) as exc:
gh.get_repo("PyGithub/PyGithub")
self.assertEqual(
exc.exception.args,
(
"Github server redirected from http protocol to https, please correct your "
"Github server URL via base_url: Github(base_url=...)",
),
)
def testBaseUrlHostRedirection(self):
gh = github.Github(base_url="https://www.github.com")
with self.assertRaises(RuntimeError) as exc:
gh.get_repo("PyGithub/PyGithub")
self.assertEqual(
exc.exception.args,
(
"Github server redirected from host www.github.com to github.com, "
"please correct your Github server URL via base_url: Github(base_url=...)",
),
)
def testBaseUrlPortRedirection(self):
# replay data forged
gh = github.Github(base_url="https://api.github.com")
with self.assertRaises(RuntimeError) as exc:
gh.get_repo("PyGithub/PyGithub")
self.assertEqual(
exc.exception.args,
(
"Requested https://api.github.com/repos/PyGithub/PyGithub but server "
"redirected to https://api.github.com:443/repos/PyGithub/PyGithub, "
"you may need to correct your Github server URL "
"via base_url: Github(base_url=...)",
),
)
def testBaseUrlPrefixRedirection(self):
# replay data forged
gh = github.Github(base_url="https://api.github.com/api/v3")
self.assertEqual(gh.get_repo("PyGithub/PyGithub").name, "PyGithub")
self.logger.info.assert_called_once_with(
"Following Github server redirection from /api/v3/repos/PyGithub/PyGithub to /repos/PyGithub/PyGithub"
)