Hello,
I try to hide passwords displayed in Django error reports
https://docs.djangoproject.com/en/1.7/howto/error-reporting/#django.views.decorators.debug.sensitive_post_parameters
from django.views.decorators.debug import sensitive_variables, sensitive_post_parameters
...
class SensitiveViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
...
@sensitive_post_parameters()
@sensitive_variables()
def create(self, request):
raise Exception
_sensitive_variables_ works perfectly but unfortunately _sensitive_post_parameters_ throw an exception.
sensitive_post_parameters didn't receive an HttpRequest. If you are decorating a classmethod, be sure to use @method_decorator.
I tried to find a solution by myself
in DRF request.py
class Request(object):
@property
def sensitive_post_parameters(self):
if _hasattr(self._request, 'sensitive_post_parameters'):
return self._request.sensitive_post_parameters
else :
return None
@sensitive_post_parameters.setter
def sensitive_post_parameters(self, value):
if value :
self._request.sensitive_post_parameters = value
With the following decorator definition:
import functools
from rest_framework.request import Request as HttpRequest
def sensitive_post_parameters(*parameters):
def decorator(obj):
@functools.wraps(obj)
def sensitive_post_parameters_wrapper(view, request, *args, **kwargs):
assert isinstance(request, HttpRequest), (
"sensitive_post_parameters didn't receive an HttpRequest. "
"If you are decorating a classmethod, be sure to use "
"@method_decorator."
)
if parameters:
request.sensitive_post_parameters = parameters
else:
request.sensitive_post_parameters = '__ALL__'
return obj(view, request, *args, **kwargs)
return sensitive_post_parameters_wrapper
return decorator
This code looks like a monkey-patch
It鈥檚 really difficult to find a pretty solution to this problem.
Do you have an idea ?
or maybe
from django.views.decorators.debug import sensitive_variables
...
class SensitiveViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
...
@sensitive_variables()
def create(self, request):
setattr(request._request,"sensitive_post_parameters",('password',))
raise Exception
Looks like this should first be raised as a Django project issue - document (or fix?) how to apply that decorator to a class based view.
Thank you @tomchristie
sensitive_post_parameters decorator works fine on standards django views.
In my opinion this error is typically due to DRF request object itself.
In fact, DRF Request does not inherit django HttpRequest
And that鈥檚 why I get the message:
didn't receive an HttpReques
Inherit HttpRequest do not solve this issue single-handedly, but i think it's a part of the solution.
sensitive_post_parameters could be a good feature of DFR
Not obvious what the right resolution would be here.
We _can't_ simply inherit from HttpRequest, because we currently have a composition-not-inheritance implementation, that would make that too difficult.
Happy to accept suggestions of how to resolve this, but otherwise I'd say it's out of scope for what we can deal with at the moment.
What about re-implement the decorator in drf and add or isinstance(request, Request) to the assert in the decorator?
How about overriding __instancecheck__ in the Request proxy.
https://docs.python.org/2/reference/datamodel.html#customizing-instance-and-subclass-checks
@tomchristie I think that the issue need to be reopened because it's not resolved.
The suggestion of @willmcgugan sounds reasonable. In most cases DRF request tries to mimic Django's request plus adding some extra stuff specific to DRF. I know that in Python we prefer duck typing and isinstance checks are far from duck typing but in some cases is easy to just test for the instance and raise appropriate error rather than relying on some cryptic attribute or type errors.
I have the same error:
AssertionError: sensitive_post_parameters didn't receive an HttpRequest. If you are decorating a classmethod, be sure to use @method_decorator.
The check that Django is doing is tell you if you used method_decorator on dispatch or not.
I can try to prepare a patch with @willmcgugan suggestion to see if it will work but first can you say that it looks reasonable?
Now when I look more closely I see that @willmcgugan's suggestion is imposible because __instancecheck__ should be implemented in Django's HttpResponse which is not possible.
Still there should be some way of interoperability between existing Django class based views and DRF.
Happy to accept suggestions of how to resolve this, but otherwise I'd say it's out of scope for what we can deal with at the moment.
@vstoykov This stands. We'd be very happy to see a suggestion (in a PR) but it's not something we'll address directly otherwise.
Here is a possible alternative...
class Request(object):
pass
class ProxyRequest(object):
@property
def __class__(self):
return Request
request = Request()
print(isinstance(request, Request))
proxy_request = ProxyRequest()
print(isinstance(proxy_request, Request))
I'm not sure that messing with class is a very good idea. I don't know if there will be some other side effects.
Another hacky solution which I've started using in a project is this:
import django.views.decorators.debug
from django.http.request import HttpRequest
from rest_framework.request import Request
class HttpRequestABC(abc.ABCMeta('ABC', (object,), {'__slots__': ()})):
# https://stackoverflow.com/a/38668373/608407
pass
HttpRequestABC.register(HttpRequest)
HttpRequestABC.register(Request)
# Patch django.views.decorators.debug in order sensitive_post_parameters
# to think that DRF Request object is subclass of HttpRequest
django.views.decorators.debug.HttpRequest = HttpRequestABC
This is Python2/3 compatible.
This is still an issue. Is there no way to omit sensitive post parameters from logging, aside from using the existing Django decorator?
I guess, we should override the sensitive_post_parameters, as it should be applied to original Django request: request._request, because AdminEmailHandler (or so) uses it (not DRF request) to populate report info...
for sake of calm mind, apply it to both requests, as traceback can (?) include info from any of them...
def sensitive_post_parameters(*parameters):
"""
adapted from: django.views.decorators.debug.sensitive_post_parameters
"""
import functools
from django.http import HttpRequest
from rest_framework.request import Request
def decorator(view):
@functools.wraps(view)
def sensitive_post_parameters_wrapper(request, *args, **kwargs):
assert isinstance(request, (HttpRequest, Request)), (
"sensitive_post_parameters didn't receive an HttpRequest or DRF Request. "
"If you are decorating a classmethod, be sure to use "
"@method_decorator."
)
request.sensitive_post_parameters = parameters or '__ALL__'
if isinstance(request, Request): # for DRF, don't forget original Django request
request._request.sensitive_post_parameters = parameters or '__ALL__'
return view(request, *args, **kwargs)
return sensitive_post_parameters_wrapper
return decorator
For future Googlers. I tried to override sensitive_post_parameters like @dz0 outlined above, which I thought was a decent approach, but I kept getting an error, so I switched tactics. Instead, I just created a custom SafeExceptionReporterFilter as outlined here: https://docs.djangoproject.com/en/2.2/howto/error-reporting/#custom-error-reports
This isn't as graceful a solution as overriding sensitive_post_parameters but this one worked for me.
in settings.py:
DEFAULT_EXCEPTION_REPORTER_FILTER = 'my.app.things.MyCustomExceptionReporterFilter'
MY_EXCEPTION_SENSITIVE_POST_PARAMS = [
'password',
'password1',
'password2',
]
and in my/app/things.py:
from django.conf import settings
from django.views.debug import SafeExceptionReporterFilter
class MyCustomExceptionReporterFilter(SafeExceptionReporterFilter):
def get_post_parameters(self, request):
immutable_post_params = super(MyCustomExceptionReporterFilter, self).get_post_parameters(request)
post_params = immutable_post_params.copy()
for param_name in settings.MY_EXCEPTION_SENSITIVE_POST_PARAMS:
if param_name in post_params:
post_params[param_name] = CLEANSED_SUBSTITUTE
return post_params
Most helpful comment
I guess, we should override the
sensitive_post_parameters, as it should be applied to original Django request:request._request, becauseAdminEmailHandler(or so) uses it (not DRF request) to populate report info...for sake of calm mind, apply it to both requests, as traceback can (?) include info from any of them...