Skip to content

Commit e0e20cd

Browse files
Jacques Troussarderlendvollset
andcommitted
532: adds a logger filter for tokens in debug mode
Co-authored-by: Erlend vollset <erlendvollset@gmail.com>
1 parent 7af9125 commit e0e20cd

File tree

5 files changed

+77
-0
lines changed

5 files changed

+77
-0
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ Maintainers & Contributors
2525
- Sylvain Marie <sylvain.marie@se.com>
2626
- Craig Anderson <craiga@craiga.id.au>
2727
- Hugo van Kemenade <https://github.com/hugovk>
28+
- Jacques Troussard <https://github.com/jtroussard>

README.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,22 @@ To install requests and requests_oauthlib you can use pip:
5656
:alt: Documentation Status
5757
:scale: 100%
5858
:target: https://requests-oauthlib.readthedocs.io/
59+
60+
Advanced Configurations
61+
-----------------------
62+
63+
### Logger Configuration Framework
64+
65+
`requests-oauthlib` now includes a flexible framework for applying custom filters and configurations to the logger, enhancing control over logging behavior and improving security.
66+
67+
#### Custom Filters
68+
69+
- **Debug Mode Token Filter**: To enhance security and provide more control over logging of sensitive information, requests-oauthlib introduces the Debug Mode Token Filter. This feature is controlled via the DEBUG_MODE_TOKEN_FILTER environment variable, allowing the suppression or masking of sensitive data in logs.
70+
71+
##### Configuring the Debug Mode Token Filter
72+
73+
- **Environment Variable**: `DEBUG_MODE_TOKEN_FILTER`
74+
- **Options**:
75+
- `DEFAULT`: No alteration to logging behavior.
76+
- `MASK`: Masks sensitive tokens in logs.
77+
- `SUPPRESS`: Prevents logging of potentially sensitive information. (logger ignores these logs entirely)

requests_oauthlib/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from .oauth2_auth import OAuth2
77
from .oauth2_session import OAuth2Session, TokenUpdated
88

9+
from .log_filters import DebugModeTokenFilter
10+
911
__version__ = "2.0.0"
1012

1113
import requests
@@ -18,3 +20,4 @@
1820
raise Warning(msg % requests.__version__)
1921

2022
logging.getLogger("requests_oauthlib").addHandler(logging.NullHandler())
23+
logging.getLogger("requests_oauthlib").addFilter(DebugModeTokenFilter())

requests_oauthlib/log_filters.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import os
2+
import re
3+
import logging
4+
5+
class DebugModeTokenFilter(logging.Filter): # <-- inherent from the Filter class
6+
def __init__(self):
7+
super().__init__()
8+
# set the behavior/configuration of the filter by the environment variable
9+
self.mode = os.getenv('DEBUG_MODE_TOKEN_FILTER', 'DEFAULT').upper()
10+
11+
def filter(self, record):
12+
if self.mode == "MASK":
13+
# While this doesn't directly target the headers as @erlendvollset 's post originally targets
14+
# this wider approach of targeting the "Bearer" key word I believe provides complete coverage.
15+
# However I would still recommend some more research to see if this regex would need to be improved
16+
# to provide a secure/trusted solution.
17+
record.msg = re.sub(r'Bearer (\w+)', '[MASKED]', record.getMessage())
18+
elif self.mode == "SUPPRESS":
19+
return False
20+
elif self.mode == "DEFAULT":
21+
msg = "Your logger, when in DEBUG mode, will log TOKENS"
22+
raise Warning(msg)
23+
return True

tests/test_log_filters.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import unittest
2+
from unittest.mock import patch
3+
from logging import LogRecord
4+
from requests_oauthlib.log_filters import DebugModeTokenFilter
5+
6+
class TestDebugModeTokenFilter(unittest.TestCase):
7+
8+
def setUp(self):
9+
self.record = LogRecord(name="test", level=20, pathname=None, lineno=None, msg="Bearer i-am-a-token", args=None, exc_info=None)
10+
11+
@patch.dict('os.environ', {'DEBUG_MODE_TOKEN_FILTER': 'MASK'})
12+
def test_mask_mode(self):
13+
filter = DebugModeTokenFilter()
14+
filter.filter(self.record)
15+
self.assertIn('[MASKED]', self.record.msg)
16+
17+
@patch.dict('os.environ', {'DEBUG_MODE_TOKEN_FILTER': 'SUPPRESS'})
18+
def test_suppress_mode(self):
19+
filter = DebugModeTokenFilter()
20+
result = filter.filter(self.record)
21+
self.assertFalse(result) # Check that nothing is logged
22+
23+
@patch.dict('os.environ', {'DEBUG_MODE_TOKEN_FILTER': 'DEFAULT'})
24+
def test_default_mode(self):
25+
filter = DebugModeTokenFilter()
26+
with self.assertRaises(Warning) as context:
27+
filter.filter(self.record)
28+
self.assertTrue("Your logger, when in DEBUG mode, will log TOKENS" in str(context.exception))
29+
30+
if __name__ == '__main__':
31+
unittest.main()

0 commit comments

Comments
 (0)