To quote the Django docs:
@sensitive_post_parameters('pass_word', 'credit_card_number')
def record_user_profile(request):
UserProfile.create(user=request.user,
password=request.POST['pass_word'],
credit_card=request.POST['credit_card_number'],
name=request.POST['name'])
In the above example, the values for the pass_word and credit_card_number POST parameters will be hidden and replaced with stars (******) in the request’s representation inside the error reports, whereas the value of the name parameter will be disclosed.
To systematically hide all POST parameters of a request in error reports, do not provide any argument to the sensitive_post_parameters decorator:
@sensitive_post_parameters()
def my_view(request):
...
As a test, I added the following code to my Django 1.6 application:
views.py:
@sensitive_post_parameters('sensitive')
def sensitive(request):
if request.method == 'POST':
raise IntegrityError(unicode(timezone.now()))
return render(request, 'sensitive-test.html',
{'form': forms.SensitiveParamForm()})
forms.py:
class SensitiveParamForm(forms.Form):
not_sensitive = forms.CharField(max_length=255)
sensitive = forms.CharField(max_length=255)
When I submit this form via POST
, I can see the values of both fields (including sensitive
) clear as day in the Sentry report.
What am I doing wrong here? I'm using Django 1.6 and Raven 3.5.2.
Thanks in advance for your help!
Turns out that this stemmed from a bug in Django itself!
If you haven't changed DEFAULT_EXCEPTION_REPORTER_FILTER
in your settings
file, you get the default filter of SafeExceptionReporterFilter
.
If you've used the sensitive_post_parameters
decorator, this will result in your calling SafeExceptionReporterFilter
's get_post_parameters
method:
def get_post_parameters(self, request):
"""
Replaces the values of POST parameters marked as sensitive with
stars (*********).
"""
if request is None:
return {}
else:
sensitive_post_parameters = getattr(request, 'sensitive_post_parameters', [])
if self.is_active(request) and sensitive_post_parameters:
cleansed = request.POST.copy()
if sensitive_post_parameters == '__ALL__':
# Cleanse all parameters.
for k, v in cleansed.items():
cleansed[k] = CLEANSED_SUBSTITUTE
return cleansed
else:
# Cleanse only the specified parameters.
for param in sensitive_post_parameters:
if param in cleansed:
cleansed[param] = CLEANSED_SUBSTITUTE
return cleansed
else:
return request.POST
The problem with the above is that while it will correctly return a QuerySet
with the sensitive POST
parameters set to CLEANSED_SUBSTITUTE
('********************'
)...it won't in any way alter request.body
.
This is a problem when working with Raven/Sentry for Django, because it turns out that the get_data_from_request
method of Raven's DjangoClient
first attempts to get the request's POST
parameters from request.body
:
def get_data_from_request(self, request):
[snip]
if request.method != 'GET':
try:
data = request.body
except Exception:
try:
data = request.raw_post_data
except Exception:
# assume we had a partial read.
try:
data = request.POST or '<unavailable>'
except Exception:
data = '<unavailable>'
else:
data = None
[snip]
The fastest fix turned out to just involve subclassing DjangoClient
and manually replacing its output with the cleansed QuerySet
produced by SafeExceptionReporterFilter
:
from django.views.debug import SafeExceptionReporterFilter
from raven.contrib.django.client import DjangoClient
class SafeDjangoClient(DjangoClient):
def get_data_from_request(self, request):
request.POST = SafeExceptionReporterFilter().get_post_parameters(request)
result = super(SafeDjangoClient, self).get_data_from_request(request)
result['sentry.interfaces.Http']['data'] = request.POST
return result