Skip to content

Commit dacf8a3

Browse files
nejchJohnVillalovos
authored andcommitted
feat(client): mask tokens by default when logging
1 parent 17414f7 commit dacf8a3

File tree

3 files changed

+71
-5
lines changed

3 files changed

+71
-5
lines changed

docs/api-usage.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,27 @@ user. For example:
384384
385385
p = gl.projects.create({'name': 'awesome_project'}, sudo='user1')
386386
387+
Logging
388+
=======
389+
390+
To enable debug logging from the underlying ``requests`` and ``http.client`` calls,
391+
you can use ``enable_debug()`` on your ``Gitlab`` instance. For example:
392+
393+
.. code-block:: python
394+
395+
import os
396+
import gitlab
397+
398+
gl = gitlab.Gitlab(private_token=os.getenv("GITLAB_TOKEN"))
399+
gl.enable_debug()
400+
401+
By default, python-gitlab will mask the token used for authentication in logging output.
402+
If you'd like to debug credentials sent to the API, you can disable masking explicitly:
403+
404+
.. code-block:: python
405+
406+
gl.enable_debug(mask_credentials=False)
407+
387408
.. _object_attributes:
388409

389410
Attributes in updated objects

gitlab/client.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -524,18 +524,39 @@ def _set_auth_info(self) -> None:
524524
self.http_username, self.http_password
525525
)
526526

527-
@staticmethod
528-
def enable_debug() -> None:
527+
def enable_debug(self, mask_credentials: bool = True) -> None:
529528
import logging
530-
from http.client import HTTPConnection # noqa
529+
from http import client
531530

532-
HTTPConnection.debuglevel = 1
531+
client.HTTPConnection.debuglevel = 1
533532
logging.basicConfig()
534-
logging.getLogger().setLevel(logging.DEBUG)
533+
logger = logging.getLogger()
534+
logger.setLevel(logging.DEBUG)
535+
536+
httpclient_log = logging.getLogger("http.client")
537+
httpclient_log.propagate = True
538+
httpclient_log.setLevel(logging.DEBUG)
539+
535540
requests_log = logging.getLogger("requests.packages.urllib3")
536541
requests_log.setLevel(logging.DEBUG)
537542
requests_log.propagate = True
538543

544+
# shadow http.client prints to log()
545+
# https://stackoverflow.com/a/16337639
546+
def print_as_log(*args: Any) -> None:
547+
httpclient_log.log(logging.DEBUG, " ".join(args))
548+
549+
setattr(client, "print", print_as_log)
550+
551+
if not mask_credentials:
552+
return
553+
554+
token = self.private_token or self.oauth_token or self.job_token
555+
handler = logging.StreamHandler()
556+
handler.setFormatter(utils.MaskingFormatter(masked=token))
557+
logger.handlers.clear()
558+
logger.addHandler(handler)
559+
539560
def _get_session_opts(self) -> Dict[str, Any]:
540561
return {
541562
"headers": self.headers.copy(),

gitlab/utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# You should have received a copy of the GNU Lesser General Public License
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717

18+
import logging
1819
import pathlib
1920
import traceback
2021
import urllib.parse
@@ -31,6 +32,29 @@ def __call__(self, chunk: Any) -> None:
3132
print(chunk)
3233

3334

35+
class MaskingFormatter(logging.Formatter):
36+
"""A logging formatter that can mask credentials"""
37+
38+
def __init__(
39+
self,
40+
*args: Any,
41+
masked: Optional[str] = None,
42+
**kwargs: Any,
43+
) -> None:
44+
super().__init__(*args, **kwargs)
45+
self.masked = masked
46+
47+
def _filter(self, entry: str) -> str:
48+
if not self.masked:
49+
return entry
50+
51+
return entry.replace(self.masked, "[MASKED]")
52+
53+
def format(self, record: logging.LogRecord) -> str:
54+
original = logging.Formatter.format(self, record)
55+
return self._filter(original)
56+
57+
3458
def response_content(
3559
response: requests.Response,
3660
streamed: bool,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy