Commit 2a22ad77 authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

webproject: Custom exception reporter filter

Remove 'cleanse' middleware that was used to clean sensitive variables
from admin mails. Instead, create a custom exception filter and set this
filter as Django's default reporter (DEFAULT_EXCEPTION_REPORTER_FILTER).
parent bb55355a
......@@ -17,6 +17,8 @@
## sets this header is in use.
#USE_X_FORWARDED_HOST = True
#
## Custom exception filter to 'cleanse' setting variables
#DEFAULT_EXCEPTION_REPORTER_FILTER = "synnefo.webproject.exception_filter.SynnefoExceptionReporterFilter"
## Settings / Cookies / Headers that should be 'cleansed'
#HIDDEN_SETTINGS = 'SECRET|PASSWORD|PROFANITIES_LIST|SIGNATURE|AMQP_HOSTS|'\
# 'PRIVATE_KEY|DB_CONNECTION|TOKEN'
......
# Copyright 2011-2012 GRNET S.A. All rights reserved.
# Copyright 2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
......@@ -32,71 +32,70 @@
# or implied, of GRNET S.A.
from django.conf import settings
from django.core.exceptions import MiddlewareNotUsed
from django.core import mail
from django.views.debug import SafeExceptionReporterFilter
from django.http import HttpRequest, build_request_repr
HIDDEN_ALL = settings.HIDDEN_COOKIES + settings.HIDDEN_HEADERS
def mail_admins_safe(subject, message, fail_silently=False,
connection=None, html_message=None):
'''
Wrapper function to cleanse email body from sensitive content before
sending it
'''
new_msg = ""
if len(message) > settings.MAIL_MAX_LEN:
new_msg += "Mail size over limit (truncated)\n\n"
message = message[:settings.MAIL_MAX_LEN]
for line in message.splitlines():
# Lines of interest in the mail are in the form of
# key:value.
try:
(key, value) = line.split(':', 1)
except ValueError:
new_msg += line + '\n'
continue
new_msg += key + ':'
# Special case when the first header / cookie printed
# (prefixed by 'META:{' or 'COOKIES:{') needs to be hidden.
if value.startswith('{'):
try:
(newkey, newval) = value.split(':', 1)
except ValueError:
new_msg += value + '\n'
continue
new_msg += newkey + ':'
key = newkey.lstrip('{')
value = newval
if key.strip(" '") not in HIDDEN_ALL:
new_msg += value + '\n'
continue
# Append value[-1] to the clensed string, so that commas / closing
# brackets are printed correctly.
# (it will 'eat up' the closing bracket if the header is the last one
# printed)
new_msg += ' ' + '*'*8 + value[-1] + '\n'
return mail.mail_admins_plain(subject, new_msg, fail_silently, connection)
class CleanseSettingsMiddleware(object):
'''
Prevent django from printing sensitive information (paswords, tokens
etc), when handling server errors (for both DEBUG and no-DEBUG
deployments.
'''
def __init__(self):
if not hasattr(mail, 'mail_admins_plain'):
mail.mail_admins_plain = mail.mail_admins
mail.mail_admins = mail_admins_safe
raise MiddlewareNotUsed('cleanse settings')
CLEANSED_SUBSTITUTE = u'********************'
class SynnefoExceptionReporterFilter(SafeExceptionReporterFilter):
def is_active(self, request):
# Ignore DEBUG setting. Always active filtering!
return True
def get_traceback_frame_variables(self, request, tb_frame):
sensitive_variables = HIDDEN_ALL
cleansed = []
if self.is_active(request) and sensitive_variables:
if sensitive_variables == '__ALL__':
# Cleanse all variables
for name, value in tb_frame.f_locals.items():
cleansed.append((name, CLEANSED_SUBSTITUTE))
return cleansed
else:
# Cleanse specified variables
for name, value in tb_frame.f_locals.items():
if name in sensitive_variables:
value = CLEANSED_SUBSTITUTE
elif isinstance(value, HttpRequest):
# Cleanse the request's POST parameters.
value = self.get_request_repr(value)
cleansed.append((name, value))
return cleansed
else:
# Potentially cleanse only the request if it's one of the frame
# variables.
for name, value in tb_frame.f_locals.items():
if isinstance(value, HttpRequest):
# Cleanse the request's POST parameters.
value = self.get_request_repr(value)
cleansed.append((name, value))
return cleansed
def get_request_repr(self, request):
if request is None:
return repr(None)
else:
# Use custom method method to build the request representation
# where all sensitive values will be cleansed
_repr = self.build_request_repr(request)
# Respect max mail size
if len(_repr) > settings.MAIL_MAX_LEN:
_repr += "Mail size over limit (truncated)\n\n" + _repr
return _repr[:settings.MAIL_MAX_LEN]
def build_request_repr(self, request):
cleansed = {}
for fields in ["GET", "POST", "COOKIES", "META"]:
_cleansed = getattr(request, fields).copy()
for key in _cleansed.keys():
for hidden in HIDDEN_ALL:
if hidden in key:
_cleansed[key] = CLEANSED_SUBSTITUTE
cleansed[fields] = _cleansed
return build_request_repr(request,
GET_override=cleansed["GET"],
POST_override=cleansed["POST"],
COOKIES_override=cleansed["COOKIES"],
META_override=cleansed["META"])
from log import LoggingConfigMiddleware
from secure import SecureMiddleware
from remoteaddr import RemoteAddrMiddleware
from cleanse import CleanseSettingsMiddleware
......@@ -65,7 +65,6 @@ MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
#'django.contrib.messages.middleware.MessageMiddleware',
'synnefo.webproject.middleware.LoggingConfigMiddleware',
'synnefo.webproject.middleware.CleanseSettingsMiddleware'
)
MIDDLEWARE_CLASSES = extend_list_from_entry_point(MIDDLEWARE_CLASSES, \
'synnefo', 'web_middleware')
......
......@@ -17,6 +17,8 @@ SECRET_KEY = 'ly6)mw6a7x%n)-e#zzk4jo6f2=uqu!1o%)2-(7lo+f9yd^k^bg'
# sets this header is in use.
USE_X_FORWARDED_HOST = True
# Custom exception filter to 'cleanse' setting variables
DEFAULT_EXCEPTION_REPORTER_FILTER = "synnefo.webproject.exception_filter.SynnefoExceptionReporterFilter"
# Settings / Cookies / Headers that should be 'cleansed'
HIDDEN_SETTINGS = 'SECRET|PASSWORD|PROFANITIES_LIST|SIGNATURE|AMQP_HOSTS|'\
'PRIVATE_KEY|DB_CONNECTION|TOKEN'
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment