-
-
Notifications
You must be signed in to change notification settings - Fork 421
Adds logger filter for TOKENS when logging in DEBUG MODE #539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 5 commits
e0e20cd
f350642
103d28c
a77b392
577a085
54676d1
33d5bb5
6b8f87c
5a6e94a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,3 +25,5 @@ Maintainers & Contributors | |
- Sylvain Marie <[email protected]> | ||
- Craig Anderson <[email protected]> | ||
- Hugo van Kemenade <https://github.com/hugovk> | ||
- Jacques Troussard <https://github.com/jtroussard> | ||
- Erland Vollset <https://github.com/erlendvollset> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import os | ||
import re | ||
import logging | ||
|
||
class DebugModeTokenFilter(logging.Filter): | ||
""" | ||
A logging filter that while in DEBUG mode can filter TOKENS dependent on configuration. | ||
|
||
This filter uses an environment variable to determine its mode, | ||
which can either mask sensitive tokens in log messages, suppress logging, | ||
or default to standard logging behavior with a warning. | ||
|
||
Attributes: | ||
mode (str): The mode of operation based on the environment variable | ||
'DEBUG_MODE_TOKEN_FILTER'. Can be 'MASK', 'SUPPRESS', or 'DEFAULT'. | ||
""" | ||
def __init__(self): | ||
""" | ||
Initializes the DebugModeTokenFilter with the 'DEBUG_MODE_TOKEN_FILTER' | ||
environment variable. | ||
""" | ||
super().__init__() | ||
self.mode = os.getenv('DEBUG_MODE_TOKEN_FILTER', 'DEFAULT').upper() | ||
|
||
def filter(self, record): | ||
""" | ||
Filters logs of TOKENS dependent on the configured mode. | ||
|
||
Args: | ||
record (logging.LogRecord): The log record to filter. | ||
|
||
Returns: | ||
bool: True if the record should be logged, False otherwise. | ||
""" | ||
if record.levelno == logging.DEBUG: | ||
if self.mode == "MASK": | ||
record.msg = re.sub(r'Bearer\s+([A-Za-z0-9\-._~+\/]+)', '[MASKED]', record.getMessage()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
elif self.mode == "SUPPRESS": | ||
return False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe only suppress log messages which match the above regex? Otherwise this equivalent to disabling the logger entirely. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am hoping a single space would suffice as a replacement? Or maybe just pass None to the msg? |
||
return True # if mode is not MASKED then DEFAULT is implied |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import unittest | ||
from unittest.mock import patch | ||
import logging | ||
from requests_oauthlib.log_filters import DebugModeTokenFilter | ||
|
||
class TestDebugModeTokenFilter(unittest.TestCase): | ||
|
||
def setUp(self): | ||
self.record = logging.LogRecord(name="test", level=logging.DEBUG, pathname=None, lineno=None, msg="Bearer i-am-a-little-token-here-is-my-scope-and-here-is-my-signature", args=None, exc_info=None) | ||
|
||
@patch.dict('os.environ', {'DEBUG_MODE_TOKEN_FILTER': 'MASK'}) | ||
def test_mask_mode(self): | ||
filter = DebugModeTokenFilter() | ||
filter.filter(self.record) | ||
self.assertIn('[MASKED]', self.record.msg) | ||
|
||
@patch.dict('os.environ', {'DEBUG_MODE_TOKEN_FILTER': 'SUPPRESS'}) | ||
def test_suppress_mode(self): | ||
filter = DebugModeTokenFilter() | ||
result = filter.filter(self.record) | ||
self.assertFalse(result) # No logging | ||
|
||
@patch.dict('os.environ', {'DEBUG_MODE_TOKEN_FILTER': 'DEFAULT'}) | ||
def test_default_mode_raises_warning(self): | ||
filter = DebugModeTokenFilter() | ||
result = filter.filter(self.record) | ||
self.assertTrue(result) | ||
|
||
if __name__ == '__main__': | ||
unittest.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest prefixing the envvar with the library name to reduce likelihood of collisions with other environment variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this comment. In fact I was running into this issue in another project. This project then variable name pattern is clean. I'm going to borrow and apply this pattern there as well. 🙏 Thank you!