diff --git a/README.md b/README.md index 81b673a..5a972c1 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,6 @@ Pre-Django 1.10, middleware modules can be added to `MIDDLEWARE_CLASSES` list in ... 'security.middleware.DoNotTrackMiddleware', 'security.middleware.ContentNoSniff', - 'security.middleware.XssProtectMiddleware', 'security.middleware.XFrameOptionsMiddleware', ) @@ -57,7 +56,6 @@ After Django 1.10, middleware modules can be added to `MIDDLEWARE` list in setti ... 'security.middleware.DoNotTrackMiddleware', 'security.middleware.ContentNoSniff', - 'security.middleware.XssProtectMiddleware', 'security.middleware.XFrameOptionsMiddleware', ) @@ -139,11 +137,6 @@ or minimum configuration. Disable framing of the website, mitigating Clickjacking attacks. Recommended. Optional. - -XssProtectMiddleware -DEPRECATED: Will be removed in future releases, consider django.middleware.security.SecurityMiddleware via SECURE_BROWSER_XSS_FILTER setting.
Enforce browser's Cross Site Scripting protection. Recommended. -None. - ## Views diff --git a/security/middleware.py b/security/middleware.py index d441a0a..ab5e2a1 100644 --- a/security/middleware.py +++ b/security/middleware.py @@ -23,10 +23,11 @@ logger = logging.getLogger(__name__) DJANGO_SECURITY_MIDDLEWARE_URL = ( "https://docs.djangoproject.com/en/1.11/ref" - "/middleware/#django.middleware.security.SecurityMiddleware") + "/middleware/#django.middleware.security.SecurityMiddleware" +) DJANGO_CLICKJACKING_MIDDLEWARE_URL = ( - "https://docs.djangoproject.com/en/1.11/" - "ref/clickjacking/") + "https://docs.djangoproject.com/en/1.11/" "ref/clickjacking/" +) class CustomLogoutMixin(object): @@ -36,20 +37,25 @@ class CustomLogoutMixin(object): """ class Messages(object): - NOT_A_MODULE_PATH = (u"Invalid CUSTOM_LOGOUT_MODULE setting '{0}'. " - u"Expected module path to a function") - FAILED_TO_LOAD = (u"Invalid CUSTOM_LOGOUT_MODULE setting. " - u"Failed to load module '{0}': {1}") - MISSING_FUNCTION = (u"Invalid CUSTOM_LOGOUT_MODULE setting. " - u"Could not find function '{0}' in module '{1}'") + NOT_A_MODULE_PATH = ( + "Invalid CUSTOM_LOGOUT_MODULE setting '{0}'. " + "Expected module path to a function" + ) + FAILED_TO_LOAD = ( + "Invalid CUSTOM_LOGOUT_MODULE setting. " "Failed to load module '{0}': {1}" + ) + MISSING_FUNCTION = ( + "Invalid CUSTOM_LOGOUT_MODULE setting. " + "Could not find function '{0}' in module '{1}'" + ) def perform_logout(self, request): - if not getattr(self, 'CUSTOM_LOGOUT_MODULE', None): + if not getattr(self, "CUSTOM_LOGOUT_MODULE", None): logout(request) return try: - module_path, func_name = self.CUSTOM_LOGOUT_MODULE.rsplit('.', 1) + module_path, func_name = self.CUSTOM_LOGOUT_MODULE.rsplit(".", 1) except ValueError: err = self.Messages.NOT_A_MODULE_PATH raise Exception(err.format(self.CUSTOM_LOGOUT_MODULE)) @@ -113,9 +119,7 @@ def __init__(self, get_response=None): ) for key in self.OPTIONAL_SETTINGS: - self.load_setting( - key, getattr(django.conf.settings, key, None) - ) + self.load_setting(key, getattr(django.conf.settings, key, None)) setting_changed.connect(self._on_setting_changed) @@ -169,8 +173,8 @@ def process_request(self, request): Read DNT header from browser request and create request attribute """ request.dnt = None - if 'HTTP_DNT' in request.META: - request.dnt = request.META['HTTP_DNT'] == '1' + if "HTTP_DNT" in request.META: + request.dnt = request.META["HTTP_DNT"] == "1" # returns None in normal conditions def process_response(self, request, response): @@ -178,84 +182,8 @@ def process_response(self, request, response): Echo DNT header in response per section 8.4 of draft-mayer-do-not- track-00 """ - if 'HTTP_DNT' in request.META: - response['DNT'] = request.META['HTTP_DNT'] - return response - - -class XssProtectMiddleware(BaseMiddleware): - """ - DEPRECATED: Will be removed in future releases. Consider - django.middleware.security.SecurityMiddleware as a replacement for this via - SECURE_BROWSER_XSS_FILTER setting. - - Sends X-XSS-Protection HTTP header that controls Cross-Site Scripting - filter on MSIE. Use XSS_PROTECT option in settings file with the following - values: - - ``sanitize`` enable XSS filter that tries to sanitize requests instead - of blocking (*default*) - - ``on`` enable full XSS filter blocking XSS requests (may `leak - document.referrer `_) - - ``off`` completely disable XSS filter - - **Note:** As of 1.8, Django's `SECURE_BROWSER_XSS_FILTER - `_ - controls the X-XSS-Protection header. - - Reference: - - - `Controlling the XSS Filter - `_ - """ - - OPTIONAL_SETTINGS = ("XSS_PROTECT",) - - OPTIONS = { - 'on': '1; mode=block', - 'off': '0', - 'sanitize': '1', - } - - DEFAULT = 'sanitize' - - def __init__(self, get_response=None): - super().__init__(get_response) - warnings.warn(( - 'DEPRECATED: The middleware "{name}" will no longer be ' - 'supported in future releases of this library. Refer to {url} for ' - 'an alternative approach with regards to the settings: {settings}' - ).format( - name=self.__class__.__name__, - url=DJANGO_SECURITY_MIDDLEWARE_URL, - settings="SECURE_BROWSER_XSS_FILTER")) - - def load_setting(self, setting, value): - if not value: - self.option = self.DEFAULT - return - - value = value.lower() - - if value in self.OPTIONS.keys(): - self.option = value - return - - raise ImproperlyConfigured( - self.__class__.__name__ + " invalid option for XSS_PROTECT." - ) - - def process_response(self, request, response): - """ - Add X-XSS-Protection to the response header. - """ - header = self.OPTIONS[self.option] - response['X-XSS-Protection'] = header + if "HTTP_DNT" in request.META: + response["DNT"] = request.META["HTTP_DNT"] return response @@ -274,22 +202,18 @@ class ClearSiteDataMiddleware(BaseMiddleware): `_ """ - REQUIRED_SETTINGS = ('CLEAR_SITE_DATA_URL_WHITELIST',) - OPTIONAL_SETTINGS = ('CLEAR_SITE_DATA_DIRECTIVES') + REQUIRED_SETTINGS = ("CLEAR_SITE_DATA_URL_WHITELIST",) + OPTIONAL_SETTINGS = "CLEAR_SITE_DATA_DIRECTIVES" - DEFAULT_DIRECTIVES = ['cookies', 'storage'] - ALLOWED_DIRECTIVES = ( - 'cache', 'cookies', 'storage', 'executionContexts', '*' - ) + DEFAULT_DIRECTIVES = ["cookies", "storage"] + ALLOWED_DIRECTIVES = ("cache", "cookies", "storage", "executionContexts", "*") def load_setting(self, setting, value): - if setting == 'CLEAR_SITE_DATA_URL_WHITELIST': + if setting == "CLEAR_SITE_DATA_URL_WHITELIST": self.clear_site_urls = value directives = getattr( - django.conf.settings, - 'CLEAR_SITE_DATA_DIRECTIVES', - self.DEFAULT_DIRECTIVES + django.conf.settings, "CLEAR_SITE_DATA_DIRECTIVES", self.DEFAULT_DIRECTIVES ) directives = [ @@ -298,9 +222,8 @@ def load_setting(self, setting, value): if directive.strip() in self.ALLOWED_DIRECTIVES ] - self.clear_site_directives = ', '.join( - '"{0}"'.format(directive) - for directive in directives + self.clear_site_directives = ", ".join( + '"{0}"'.format(directive) for directive in directives ) def process_response(self, request, response): @@ -310,7 +233,7 @@ def process_response(self, request, response): """ if request.path in self.clear_site_urls: - response['Clear-Site-Data'] = self.clear_site_directives + response["Clear-Site-Data"] = self.clear_site_directives return response @@ -340,20 +263,23 @@ class ContentNoSniff(MiddlewareMixin): def __init__(self, get_response=None): super().__init__(get_response) - warnings.warn(( - 'DEPRECATED: The middleware "{name}" will no longer be ' - 'supported in future releases of this library. Refer to {url} for ' - 'an alternative approach with regards to the settings: {settings}' - ).format( - name=self.__class__.__name__, - url=DJANGO_SECURITY_MIDDLEWARE_URL, - settings="SECURE_CONTENT_TYPE_NOSNIFF")) + warnings.warn( + ( + 'DEPRECATED: The middleware "{name}" will no longer be ' + "supported in future releases of this library. Refer to {url} for " + "an alternative approach with regards to the settings: {settings}" + ).format( + name=self.__class__.__name__, + url=DJANGO_SECURITY_MIDDLEWARE_URL, + settings="SECURE_CONTENT_TYPE_NOSNIFF", + ) + ) def process_response(self, request, response): """ Add ``X-Content-Options: nosniff`` to the response header. """ - response['X-Content-Options'] = 'nosniff' + response["X-Content-Options"] = "nosniff" return response @@ -402,7 +328,7 @@ def process_view(self, request, view, *args, **kwargs): # because the reason the URL is exempt may be because a special URL # config is in use (i.e. during a test) that doesn't have URL_NAME. - path = request.path_info.lstrip('/') + path = request.path_info.lstrip("/") if any(m.match(path) for m in self.exempt_urls): return @@ -418,6 +344,7 @@ def process_view(self, request, view, *args, **kwargs): return from .password_expiry import password_is_expired + if password_is_expired(request.user): return HttpResponseRedirect(password_change_url) @@ -460,12 +387,14 @@ def load_setting(self, setting, value): value = value or {} self.whitelist = value.get("WHITELIST_ON", False) if self.whitelist: - self.whitelist_url_regexes = \ - [compile(x) for x in value['WHITELIST_REGEXES']] + self.whitelist_url_regexes = [ + compile(x) for x in value["WHITELIST_REGEXES"] + ] self.blacklist = value.get("BLACKLIST_ON", False) if self.blacklist: - self.blacklist_url_regexes = \ - [compile(x) for x in value['BLACKLIST_REGEXES']] + self.blacklist_url_regexes = [ + compile(x) for x in value["BLACKLIST_REGEXES"] + ] def process_response(self, request, response): """ @@ -473,7 +402,7 @@ def process_response(self, request, response): whitelist non-confidential pages and treat all others as non- confidential, or specifically blacklist pages as confidential """ - path = request.path.lstrip('/') + path = request.path.lstrip("/") if self.whitelist: if not any(re.match(path) for re in self.whitelist_url_regexes): self._remove_response_caching(response) @@ -487,10 +416,9 @@ def _remove_response_caching(self, response): """ Overwrites specific headers to make the HTTP response confidential. """ - response['Cache-control'] = \ - 'no-cache, no-store, max-age=0, must-revalidate' - response['Pragma'] = "no-cache" - response['Expires'] = -1 + response["Cache-control"] = "no-cache, no-store, max-age=0, must-revalidate" + response["Pragma"] = "no-cache" + response["Expires"] = -1 # http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-01 @@ -528,29 +456,31 @@ class XFrameOptionsMiddleware(BaseMiddleware): `_ """ - OPTIONAL_SETTINGS = ('X_FRAME_OPTIONS', 'X_FRAME_OPTIONS_EXCLUDE_URLS') + OPTIONAL_SETTINGS = ("X_FRAME_OPTIONS", "X_FRAME_OPTIONS_EXCLUDE_URLS") - DEFAULT = 'deny' + DEFAULT = "deny" def __init__(self, get_response=None): super().__init__(get_response) - warnings.warn(( - 'An official middleware "{name}" is supported by Django. ' - 'Refer to {url} to see if its approach fits the use case.' - ).format( - name="XFrameOptionsMiddleware", - url=DJANGO_CLICKJACKING_MIDDLEWARE_URL)) + warnings.warn( + ( + 'An official middleware "{name}" is supported by Django. ' + "Refer to {url} to see if its approach fits the use case." + ).format( + name="XFrameOptionsMiddleware", url=DJANGO_CLICKJACKING_MIDDLEWARE_URL + ) + ) def load_setting(self, setting, value): - if setting == 'X_FRAME_OPTIONS': + if setting == "X_FRAME_OPTIONS": if not value: self.option = XFrameOptionsMiddleware.DEFAULT return value = value.lower() - options = ['sameorigin', 'deny'] + options = ["sameorigin", "deny"] - if value in options or value.startswith('allow-from:'): + if value in options or value.startswith("allow-from:"): self.option = value return @@ -558,7 +488,7 @@ def load_setting(self, setting, value): self.__class__.__name__ + " invalid option for X_FRAME_OPTIONS" ) - elif setting == 'X_FRAME_OPTIONS_EXCLUDE_URLS': + elif setting == "X_FRAME_OPTIONS_EXCLUDE_URLS": if not value: self.exclude_urls = [] return @@ -567,8 +497,10 @@ def load_setting(self, setting, value): self.exclude_urls = [compile(url) for url in value] except TypeError: raise ImproperlyConfigured( - "{0} invalid option for X_FRAME_OPTIONS_EXCLUDE_URLS" - .format(self.__class__.__name__)) + "{0} invalid option for X_FRAME_OPTIONS_EXCLUDE_URLS".format( + self.__class__.__name__ + ) + ) def process_response(self, request, response): """ @@ -578,7 +510,7 @@ def process_response(self, request, response): if url.match(request.path): break else: - response['X-Frame-Options'] = self.option + response["X-Frame-Options"] = self.option return response @@ -680,43 +612,44 @@ class ContentSecurityPolicyMiddleware(MiddlewareMixin): - `HTML5.1 - Sandboxing `_ """ + # these types accept CSP locations as arguments _CSP_LOC_TYPES = [ - 'default-src', - 'connect-src', - 'child-src', - 'font-src', - 'form-action', - 'frame-ancestors', - 'frame-src', - 'img-src', - 'media-src', - 'object-src', - 'script-src', - 'style-src', - 'plugin-types', - 'worker-src' + "default-src", + "connect-src", + "child-src", + "font-src", + "form-action", + "frame-ancestors", + "frame-src", + "img-src", + "media-src", + "object-src", + "script-src", + "style-src", + "plugin-types", + "worker-src", ] # arguments to location types - _CSP_LOCATIONS = ['self', 'none', 'unsafe-eval', 'unsafe-inline'] + _CSP_LOCATIONS = ["self", "none", "unsafe-eval", "unsafe-inline"] # sandbox allowed arguments # http://www.w3.org/html/wg/drafts/html/master/single-page.html#sandboxing # https://www.w3.org/TR/CSP2/ _CSP_SANDBOX_ARGS = [ - '', - 'allow-forms', - 'allow-pointer-lock', - 'allow-popups', - 'allow-same-origin', - 'allow-scripts', - 'allow-top-navigation', + "", + "allow-forms", + "allow-pointer-lock", + "allow-popups", + "allow-same-origin", + "allow-scripts", + "allow-top-navigation", ] # reflected-xss allowed arguments # http://www.w3.org/TR/CSP11/#directive-reflected-xss - _CSP_XSS_ARGS = ['allow', 'block', 'filter'] + _CSP_XSS_ARGS = ["allow", "block", "filter"] # referrer allowed arguments # http://www.w3.org/TR/CSP11/#directive-referrer @@ -734,24 +667,24 @@ class ContentSecurityPolicyMiddleware(MiddlewareMixin): def _csp_loc_builder(self, key, value): if not isinstance(value, (list, tuple)): - logger.warn('Arguments to %s must be given as list or tuple', key) + logger.warn("Arguments to %s must be given as list or tuple", key) raise django.core.exceptions.MiddlewareNotUsed csp_loc_string = "{0}".format(key) for loc in value: if loc in self._CSP_LOCATIONS: csp_loc_string += " '{0}'".format(loc) # quoted - elif loc == '*': - csp_loc_string += ' *' # not quoted + elif loc == "*": + csp_loc_string += " *" # not quoted else: # XXX: check for valid hostname or URL - csp_loc_string += " {0}".format(loc) # not quoted + csp_loc_string += " {0}".format(loc) # not quoted return csp_loc_string def _csp_sandbox_builder(self, key, value): if not isinstance(value, (list, tuple)): - logger.warn('Arguments to %s must be given as list or tuple', key) + logger.warn("Arguments to %s must be given as list or tuple", key) raise django.core.exceptions.MiddlewareNotUsed csp_sandbox_string = "{0}".format(key) @@ -759,25 +692,25 @@ def _csp_sandbox_builder(self, key, value): if opt in self._CSP_SANDBOX_ARGS: csp_sandbox_string += " {0}".format(opt) else: - logger.warn('Invalid CSP sandbox argument %s', opt) + logger.warn("Invalid CSP sandbox argument %s", opt) raise django.core.exceptions.MiddlewareNotUsed return csp_sandbox_string def _csp_report_uri_builder(self, key, value): # XXX: add valid URL check - return '{0} {1}'.format(key, value) + return "{0} {1}".format(key, value) def _csp_referrer_builder(self, key, value): if value not in self._CSP_REF_ARGS: - logger.warning('Invalid CSP %s value %s', key, value) + logger.warning("Invalid CSP %s value %s", key, value) raise django.core.exceptions.MiddlewareNotUsed return "{0} {1}".format(key, value) def _csp_reflected_xss_builder(self, key, value): if value not in self._CSP_XSS_ARGS: - logger.warning('Invalid CSP %s value %s', key, value) + logger.warning("Invalid CSP %s value %s", key, value) raise django.core.exceptions.MiddlewareNotUsed return "{0} {1}".format(key, value) @@ -786,52 +719,51 @@ def _csp_builder(self, csp_dict): csp_components = [] for key, value in csp_dict.items(): - if key in self._CSP_LOC_TYPES: csp_components.append(self._csp_loc_builder(key, value)) - elif key == 'sandbox': + elif key == "sandbox": csp_components.append(self._csp_sandbox_builder(key, value)) - elif key == 'report-uri': + elif key == "report-uri": csp_components.append(self._csp_report_uri_builder(key, value)) - elif key == 'referrer': + elif key == "referrer": csp_components.append(self._csp_referrer_builder(key, value)) - elif key == 'reflected-xss': + elif key == "reflected-xss": csp_components.append( self._csp_reflected_xss_builder(key, value), ) else: - logger.warning('Invalid CSP type %s', key) + logger.warning("Invalid CSP type %s", key) raise django.core.exceptions.MiddlewareNotUsed - return '; '.join(csp_components) + return "; ".join(csp_components) def __init__(self, get_response=None): # sanity checks self.get_response = get_response - conf_csp_mode = getattr(django.conf.settings, 'CSP_MODE', None) - self._csp_mode = conf_csp_mode or 'enforce' - csp_string = getattr(django.conf.settings, 'CSP_STRING', None) - csp_dict = getattr(django.conf.settings, 'CSP_DICT', None) - csp_report_string = getattr(django.conf.settings, 'CSP_REPORT_STRING', - None) - csp_report_dict = getattr(django.conf.settings, 'CSP_REPORT_DICT', - None) - - set_csp_str = self._csp_mode in ['enforce', 'enforce-and-report-only'] - set_csp_report_str = self._csp_mode in ['report-only', - 'enforce-and-report-only'] + conf_csp_mode = getattr(django.conf.settings, "CSP_MODE", None) + self._csp_mode = conf_csp_mode or "enforce" + csp_string = getattr(django.conf.settings, "CSP_STRING", None) + csp_dict = getattr(django.conf.settings, "CSP_DICT", None) + csp_report_string = getattr(django.conf.settings, "CSP_REPORT_STRING", None) + csp_report_dict = getattr(django.conf.settings, "CSP_REPORT_DICT", None) + + set_csp_str = self._csp_mode in ["enforce", "enforce-and-report-only"] + set_csp_report_str = self._csp_mode in [ + "report-only", + "enforce-and-report-only", + ] if not (set_csp_str or set_csp_report_str): logger.error( 'Invalid CSP_MODE %s, "enforce", "report-only" ' 'or "enforce-and-report-only" allowed', - self._csp_mode + self._csp_mode, ) raise django.core.exceptions.MiddlewareNotUsed @@ -842,20 +774,21 @@ def __init__(self, get_response=None): self._set_csp_report_str(csp_report_dict, csp_report_string) def _set_csp_str(self, csp_dict, csp_string): - err_msg = 'Middleware requires either CSP_STRING or CSP_DICT setting' + err_msg = "Middleware requires either CSP_STRING or CSP_DICT setting" if not (csp_dict or csp_string): - logger.error('%s, none found', err_msg) + logger.error("%s, none found", err_msg) raise django.core.exceptions.MiddlewareNotUsed - self._csp_string = self._choose_csp_str(csp_dict, csp_string, - err_msg + ', not both') + self._csp_string = self._choose_csp_str( + csp_dict, csp_string, err_msg + ", not both" + ) def _set_csp_report_str(self, csp_report_dict, csp_report_string): report_err_msg = ( - 'Middleware requires either CSP_REPORT_STRING, ' - 'CSP_REPORT_DICT setting, or neither. If neither, ' - 'middleware requires CSP_STRING or CSP_DICT, ' - 'but not both.' + "Middleware requires either CSP_REPORT_STRING, " + "CSP_REPORT_DICT setting, or neither. If neither, " + "middleware requires CSP_STRING or CSP_DICT, " + "but not both." ) # Default to the regular CSP string if report string not configured @@ -863,9 +796,7 @@ def _set_csp_report_str(self, csp_report_dict, csp_report_string): self._csp_report_string = self._csp_string else: self._csp_report_string = self._choose_csp_str( - csp_report_dict, - csp_report_string, - report_err_msg + csp_report_dict, csp_report_string, report_err_msg ) def _choose_csp_str(self, csp_dict, csp_str, err_msg): @@ -883,7 +814,7 @@ def _choose_csp_str(self, csp_dict, csp_str, err_msg): Log an error message if both are provided. """ if csp_dict and csp_str: - logger.error('%s', err_msg) + logger.error("%s", err_msg) raise django.core.exceptions.MiddlewareNotUsed if csp_dict: @@ -891,7 +822,7 @@ def _choose_csp_str(self, csp_dict, csp_str, err_msg): elif csp_str: return csp_str else: - return '' + return "" def process_response(self, request, response): """ @@ -900,21 +831,23 @@ def process_response(self, request, response): """ # choose headers based enforcement mode is_ie = False - if 'HTTP_USER_AGENT' in request.META: - parsed_ua = user_agent_parser.ParseUserAgent(request.META['HTTP_USER_AGENT']) - is_ie = parsed_ua['family'] == 'IE' + if "HTTP_USER_AGENT" in request.META: + parsed_ua = user_agent_parser.ParseUserAgent( + request.META["HTTP_USER_AGENT"] + ) + is_ie = parsed_ua["family"] == "IE" - csp_header = 'Content-Security-Policy' + csp_header = "Content-Security-Policy" if is_ie: - csp_header = 'X-Content-Security-Policy' - report_only_header = 'Content-Security-Policy-Report-Only' + csp_header = "X-Content-Security-Policy" + report_only_header = "Content-Security-Policy-Report-Only" # actually add appropriate headers - if self._csp_mode == 'enforce': + if self._csp_mode == "enforce": response[csp_header] = self._csp_string - elif self._csp_mode == 'report-only': + elif self._csp_mode == "report-only": response[report_only_header] = self._csp_report_string - elif self._csp_mode == 'enforce-and-report-only': + elif self._csp_mode == "enforce-and-report-only": response[csp_header] = self._csp_string response[report_only_header] = self._csp_report_string @@ -950,19 +883,25 @@ class StrictTransportSecurityMiddleware(MiddlewareMixin): `_ - `Preloaded HSTS sites `_ """ + def __init__(self, get_response=None): - warnings.warn(( - 'DEPRECATED: The middleware "{name}" will no longer be ' - 'supported in future releases of this library. Refer to {url} for ' - 'an alternative approach with regards to the settings: {settings}' - ).format( - name=self.__class__.__name__, - url=DJANGO_SECURITY_MIDDLEWARE_URL, - settings=", ".join([ - "SECURE_HSTS_SECONDS", - "SECURE_HSTS_INCLUDE_SUBDOMAINS", - "SECURE_HSTS_PRELOAD", - ]))) + warnings.warn( + ( + 'DEPRECATED: The middleware "{name}" will no longer be ' + "supported in future releases of this library. Refer to {url} for " + "an alternative approach with regards to the settings: {settings}" + ).format( + name=self.__class__.__name__, + url=DJANGO_SECURITY_MIDDLEWARE_URL, + settings=", ".join( + [ + "SECURE_HSTS_SECONDS", + "SECURE_HSTS_INCLUDE_SUBDOMAINS", + "SECURE_HSTS_PRELOAD", + ] + ), + ) + ) self.get_response = get_response @@ -981,19 +920,19 @@ def __init__(self, get_response=None): except AttributeError: self.preload = True - self.value = 'max-age={0}'.format(self.max_age) + self.value = "max-age={0}".format(self.max_age) if self.subdomains: - self.value += ' ; includeSubDomains' + self.value += " ; includeSubDomains" if self.preload: - self.value += ' ; preload' + self.value += " ; preload" def process_response(self, request, response): """ Add Strict-Transport-Security header. """ - response['Strict-Transport-Security'] = self.value + response["Strict-Transport-Security"] = self.value return response @@ -1021,22 +960,24 @@ class P3PPolicyMiddleware(BaseMiddleware): def __init__(self, get_response=None): super().__init__(get_response) - warnings.warn(( - 'DEPRECATED: The middleware "{name}" will no longer be ' - 'supported in future releases of this library.' - ).format(name=self.__class__.__name__)) + warnings.warn( + ( + 'DEPRECATED: The middleware "{name}" will no longer be ' + "supported in future releases of this library." + ).format(name=self.__class__.__name__) + ) def load_setting(self, setting, value): - if setting == 'P3P_COMPACT_POLICY': + if setting == "P3P_COMPACT_POLICY": self.policy = value - elif setting == 'P3P_POLICY_URL': - self.policy_url = value or '/w3c/p3p.xml' + elif setting == "P3P_POLICY_URL": + self.policy_url = value or "/w3c/p3p.xml" def process_response(self, request, response): """ Add P3P policy to the response header. """ - response['P3P'] = 'policyref="{0}" CP="{1}"'.format( + response["P3P"] = 'policyref="{0}" CP="{1}"'.format( self.policy_url, self.policy, ) @@ -1067,15 +1008,19 @@ class SessionExpiryPolicyMiddleware(CustomLogoutMixin, BaseMiddleware): e.g. 'django.contrib.auth.logout'. """ - OPTIONAL_SETTINGS = ('SESSION_COOKIE_AGE', 'SESSION_INACTIVITY_TIMEOUT', - 'SESSION_EXPIRY_EXEMPT_URLS', 'CUSTOM_LOGOUT_MODULE') + OPTIONAL_SETTINGS = ( + "SESSION_COOKIE_AGE", + "SESSION_INACTIVITY_TIMEOUT", + "SESSION_EXPIRY_EXEMPT_URLS", + "CUSTOM_LOGOUT_MODULE", + ) SECONDS_PER_DAY = 86400 SECONDS_PER_30MINS = 1800 # Session keys - START_TIME_KEY = 'starttime' - LAST_ACTIVITY_KEY = 'lastactivity' + START_TIME_KEY = "starttime" + LAST_ACTIVITY_KEY = "lastactivity" @classmethod def _get_datetime_in_session(cls, key, session): @@ -1087,47 +1032,34 @@ def _set_datetime_in_session(cls, key, value, session): @classmethod def get_start_time(cls, request): - return cls._get_datetime_in_session( - cls.START_TIME_KEY, - request.session - ) + return cls._get_datetime_in_session(cls.START_TIME_KEY, request.session) @classmethod def set_start_time(cls, request, date): - cls._set_datetime_in_session( - cls.START_TIME_KEY, - date, - request.session - ) + cls._set_datetime_in_session(cls.START_TIME_KEY, date, request.session) @classmethod def get_last_activity(cls, request): - return cls._get_datetime_in_session( - cls.LAST_ACTIVITY_KEY, - request.session - ) + return cls._get_datetime_in_session(cls.LAST_ACTIVITY_KEY, request.session) @classmethod def set_last_activity(cls, request, date): - cls._set_datetime_in_session( - cls.LAST_ACTIVITY_KEY, - date, - request.session - ) + cls._set_datetime_in_session(cls.LAST_ACTIVITY_KEY, date, request.session) def load_setting(self, setting, value): - if setting == 'SESSION_COOKIE_AGE': + if setting == "SESSION_COOKIE_AGE": self.SESSION_COOKIE_AGE = value or self.SECONDS_PER_DAY - logger.debug("Max Session Cookie Age is %d seconds", - self.SESSION_COOKIE_AGE - ) - elif setting == 'SESSION_INACTIVITY_TIMEOUT': + logger.debug( + "Max Session Cookie Age is %d seconds", self.SESSION_COOKIE_AGE + ) + elif setting == "SESSION_INACTIVITY_TIMEOUT": # half an hour in seconds self.SESSION_INACTIVITY_TIMEOUT = value or self.SECONDS_PER_30MINS - logger.debug("Session Inactivity Timeout is %d seconds", - self.SESSION_INACTIVITY_TIMEOUT - ) - elif setting == 'SESSION_EXPIRY_EXEMPT_URLS': + logger.debug( + "Session Inactivity Timeout is %d seconds", + self.SESSION_INACTIVITY_TIMEOUT, + ) + elif setting == "SESSION_EXPIRY_EXEMPT_URLS": self.exempt_urls = [compile(expr) for expr in (value or ())] else: setattr(self, setting, value) @@ -1139,13 +1071,13 @@ def process_request(self, request): is the case. We set the last activity time to now() if the session is still active. """ - if not hasattr(request, 'user'): + if not hasattr(request, "user"): raise ImproperlyConfigured( "The Login Required middleware " "requires authentication middleware to be installed." ) - path = request.path_info.lstrip('/') + path = request.path_info.lstrip("/") if any(m.match(path) for m in self.exempt_urls): return @@ -1177,14 +1109,10 @@ def process_existing_session(self, request): start_time = self.get_start_time(request) last_activity_time = self.get_last_activity(request) - logger.debug("Session %s started: %s", - session.session_key, - start_time - ) - logger.debug("Session %s last active: %s", - session.session_key, - last_activity_time - ) + logger.debug("Session %s started: %s", session.session_key, start_time) + logger.debug( + "Session %s last active: %s", session.session_key, last_activity_time + ) session_age = self.get_diff_in_seconds(now, start_time) session_too_old = session_age > self.SESSION_COOKIE_AGE @@ -1213,6 +1141,7 @@ def get_diff_in_seconds(self, now, time): age = diff.days * self.SECONDS_PER_DAY + diff.seconds return age + # Modified a little bit by us. # Copyright (c) 2008, Ryan Witt @@ -1262,19 +1191,19 @@ class LoginRequiredMiddleware(BaseMiddleware, CustomLogoutMixin): e.g. 'django.contrib.auth.logout'. """ - REQUIRED_SETTINGS = ('LOGIN_URL',) - OPTIONAL_SETTINGS = ('LOGIN_EXEMPT_URLS', 'CUSTOM_LOGOUT_MODULE') + REQUIRED_SETTINGS = ("LOGIN_URL",) + OPTIONAL_SETTINGS = ("LOGIN_EXEMPT_URLS", "CUSTOM_LOGOUT_MODULE") def load_setting(self, setting, value): - if setting == 'LOGIN_URL': + if setting == "LOGIN_URL": self.login_url = value - elif setting == 'LOGIN_EXEMPT_URLS': + elif setting == "LOGIN_EXEMPT_URLS": self.exempt_urls = [compile(expr) for expr in (value or ())] else: setattr(self, setting, value) def assert_authentication_middleware_installed(self, request): - if not hasattr(request, 'user'): + if not hasattr(request, "user"): raise ImproperlyConfigured( "The Login Required middleware " "requires authentication middleware to be installed." @@ -1292,12 +1221,12 @@ def process_request(self, request): if request.user.is_authenticated: return - path = request.path_info.lstrip('/') + path = request.path_info.lstrip("/") if any(m.match(path) for m in self.exempt_urls): return - if hasattr(request, 'login_url'): + if hasattr(request, "login_url"): login_url = request.login_url next_url = None else: @@ -1312,10 +1241,11 @@ def process_request(self, request): ) if next_url: - login_url = login_url + '?next=' + next_url + login_url = login_url + "?next=" + next_url return HttpResponseRedirect(login_url) + class ReferrerPolicyMiddleware(BaseMiddleware): """ Sends Referrer-Policy HTTP header that controls when the browser will set @@ -1339,11 +1269,19 @@ class ReferrerPolicyMiddleware(BaseMiddleware): OPTIONAL_SETTINGS = ("REFERRER_POLICY",) - OPTIONS = [ 'no-referrer', 'no-referrer-when-downgrade', 'origin', - 'origin-when-cross-origin', 'same-origin', 'strict-origin', - 'strict-origin-when-cross-origin', 'unsafe-url', 'off' ] + OPTIONS = [ + "no-referrer", + "no-referrer-when-downgrade", + "origin", + "origin-when-cross-origin", + "same-origin", + "strict-origin", + "strict-origin-when-cross-origin", + "unsafe-url", + "off", + ] - DEFAULT = 'same-origin' + DEFAULT = "same-origin" def load_setting(self, setting, value): if not value: @@ -1364,7 +1302,7 @@ def process_response(self, request, response): """ Add Referrer-Policy to the response header. """ - if self.option != 'off': + if self.option != "off": header = self.option - response['Referrer-Policy'] = header + response["Referrer-Policy"] = header return response diff --git a/testing/settings.py b/testing/settings.py index a81213b..ce0a287 100644 --- a/testing/settings.py +++ b/testing/settings.py @@ -7,93 +7,101 @@ ADMINS = () MANAGERS = ADMINS DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'testing.db', - 'USER': '', - 'PASSWORD': '', - 'HOST': '', - 'PORT': '', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "testing.db", + "USER": "", + "PASSWORD": "", + "HOST": "", + "PORT": "", } } -TIME_ZONE = 'America/Chicago' +TIME_ZONE = "America/Chicago" USE_TZ = True -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" SITE_ID = 1 USE_I18N = True USE_L10N = True -MEDIA_ROOT = '' -MEDIA_URL = '' -STATIC_ROOT = '' -STATIC_URL = '/static/' +MEDIA_ROOT = "" +MEDIA_URL = "" +STATIC_ROOT = "" +STATIC_URL = "/static/" STATICFILES_DIRS = () STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", ) -SECRET_KEY = 'p_2zsf+@4uw$kcdl$!tkf0lrh%w^!#@2@iwo4plef2n$(@uj4_' +SECRET_KEY = "p_2zsf+@4uw$kcdl$!tkf0lrh%w^!#@2@iwo4plef2n$(@uj4_" MIDDLEWARE = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'security.middleware.SessionExpiryPolicyMiddleware', - 'security.middleware.LoginRequiredMiddleware', - 'security.middleware.XFrameOptionsMiddleware', - 'security.middleware.ContentNoSniff', - 'security.middleware.ContentSecurityPolicyMiddleware', - 'security.middleware.StrictTransportSecurityMiddleware', - 'security.middleware.P3PPolicyMiddleware', - 'security.middleware.XssProtectMiddleware', - 'security.middleware.MandatoryPasswordChangeMiddleware', - 'security.middleware.NoConfidentialCachingMiddleware', - 'security.auth_throttling.Middleware', - 'security.middleware.ReferrerPolicyMiddleware', + "django.middleware.common.CommonMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "security.middleware.SessionExpiryPolicyMiddleware", + "security.middleware.LoginRequiredMiddleware", + "security.middleware.XFrameOptionsMiddleware", + "security.middleware.ContentNoSniff", + "security.middleware.ContentSecurityPolicyMiddleware", + "security.middleware.StrictTransportSecurityMiddleware", + "security.middleware.P3PPolicyMiddleware", + "security.middleware.MandatoryPasswordChangeMiddleware", + "security.middleware.NoConfidentialCachingMiddleware", + "security.auth_throttling.Middleware", + "security.middleware.ReferrerPolicyMiddleware", ) -ROOT_URLCONF = 'testing.urls' +ROOT_URLCONF = "testing.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [_os.path.join(_PROJECT_PATH, "templates")], - 'OPTIONS': { - 'context_processors': [ - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [_os.path.join(_PROJECT_PATH, "templates")], + "OPTIONS": { + "context_processors": [ + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ] - } + }, } ] INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.staticfiles', - 'django.contrib.messages', - 'django.contrib.admin', - 'security', - 'tests' + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.staticfiles", + "django.contrib.messages", + "django.contrib.admin", + "security", + "tests", ) -TEST_RUNNER = 'django.test.runner.DiscoverRunner' +TEST_RUNNER = "django.test.runner.DiscoverRunner" LOGIN_REDIRECT_URL = "/home/" # The tests for django.contrib.auth use certain URLs, and they'll fail if we # interfere with these. _DJANGO_TESTING_URLS = [ - 'login/', 'login_required/', 'login_required_login_url/', - 'admin_password_reset/', 'logout/', 'password_reset/', - 'password_reset_from_email/', 'reset/', 'password_change/', 'remote_user/', - 'auth_processor_messages/', 'auth_processor_perms/', - 'auth_processor_user/', 'auth_processor_perm_in_perms/', - 'admin/auth/user/', + "login/", + "login_required/", + "login_required_login_url/", + "admin_password_reset/", + "logout/", + "password_reset/", + "password_reset_from_email/", + "reset/", + "password_change/", + "remote_user/", + "auth_processor_messages/", + "auth_processor_perms/", + "auth_processor_user/", + "auth_processor_perm_in_perms/", + "admin/auth/user/", ] LOGIN_EXEMPT_URLS = [ @@ -104,7 +112,7 @@ SESSION_EXPIRY_EXEMPT_URLS = LOGIN_EXEMPT_URLS -CUSTOM_LOGOUT_MODULE = 'tests.tests.mocked_custom_logout' +CUSTOM_LOGOUT_MODULE = "tests.tests.mocked_custom_logout" MANDATORY_PASSWORD_CHANGE = { "URL_NAME": "change_password", @@ -114,37 +122,33 @@ AUTHENTICATION_THROTTLING = { "DELAY_FUNCTION": lambda x, y: (0, 0), - "LOGIN_URLS_WITH_TEMPLATES": [ - ("accounts/login/", "login.html") - ] + "LOGIN_URLS_WITH_TEMPLATES": [("accounts/login/", "login.html")], } -XSS_PROTECT = 'on' -X_FRAME_OPTIONS = 'allow-from: http://example.com' -X_FRAME_OPTIONS_EXCLUDE_URLS = ( - r'^/test\d/$', -) +XSS_PROTECT = "on" +X_FRAME_OPTIONS = "allow-from: http://example.com" +X_FRAME_OPTIONS_EXCLUDE_URLS = (r"^/test\d/$",) CSP_STRING = "allow 'self'; script-src *.google.com" -CSP_MODE = 'enforce' -P3P_POLICY_URL = '/w3c/p3p.xml' -P3P_COMPACT_POLICY = 'PRIVATE' +CSP_MODE = "enforce" +P3P_POLICY_URL = "/w3c/p3p.xml" +P3P_COMPACT_POLICY = "PRIVATE" LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", }, }, - 'loggers': { - '': { - 'handlers': ['console'], - 'level': 'WARNING', - 'propagate': True, + "loggers": { + "": { + "handlers": ["console"], + "level": "WARNING", + "propagate": True, }, }, } -CLEAR_SITE_DATA_URL_WHITELIST = ('/home/') +CLEAR_SITE_DATA_URL_WHITELIST = "/home/" diff --git a/testing/tests/tests.py b/testing/tests/tests.py index 3ce346d..f72f370 100644 --- a/testing/tests/tests.py +++ b/testing/tests/tests.py @@ -18,13 +18,21 @@ from security.auth import min_length from security.auth_throttling import ( - attempt_count, default_delay_function, delay_message, increment_counters, - reset_counters, Middleware as AuthThrottlingMiddleware + attempt_count, + default_delay_function, + delay_message, + increment_counters, + reset_counters, + Middleware as AuthThrottlingMiddleware, ) from security.middleware import ( - BaseMiddleware, ContentSecurityPolicyMiddleware, DoNotTrackMiddleware, - SessionExpiryPolicyMiddleware, MandatoryPasswordChangeMiddleware, - XssProtectMiddleware, XFrameOptionsMiddleware, ReferrerPolicyMiddleware + BaseMiddleware, + ContentSecurityPolicyMiddleware, + DoNotTrackMiddleware, + SessionExpiryPolicyMiddleware, + MandatoryPasswordChangeMiddleware, + XFrameOptionsMiddleware, + ReferrerPolicyMiddleware, ) from security.models import PasswordExpiry from security.password_expiry import never_expire_password @@ -46,10 +54,11 @@ def login_user(func): then log that user in. We expect self to be a DjangoTestCase, or some object with a similar interface. """ + def wrapper(self, *args, **kwargs): - username_local = 'a2fcf54f63993b7' - password_local = 'd8327deb882cf90' - email_local = 'testuser@example.com' + username_local = "a2fcf54f63993b7" + password_local = "d8327deb882cf90" + email_local = "testuser@example.com" user = User.objects.create_user( username=username_local, email=email_local, @@ -62,21 +71,23 @@ def wrapper(self, *args, **kwargs): func(self, *args, **kwargs) self.client.logout() user.delete() + return wrapper class CustomLoginURLMiddleware(BaseMiddleware): """Used to test the custom url support in the login required middleware.""" + def process_request(self, request): - request.login_url = '/custom-login/' + request.login_url = "/custom-login/" class BaseMiddlewareTestMiddleware(BaseMiddleware): - REQUIRED_SETTINGS = ('R1', 'R2') - OPTIONAL_SETTINGS = ('O1', 'O2') + REQUIRED_SETTINGS = ("R1", "R2") + OPTIONAL_SETTINGS = ("O1", "O2") def load_setting(self, setting, value): - if not hasattr(self, 'loaded_settings'): + if not hasattr(self, "loaded_settings"): self.loaded_settings = {} self.loaded_settings[setting] = value @@ -92,96 +103,90 @@ class BaseMiddlewareTests(TestCase): def __init__(self, *args, **kwargs): super(BaseMiddlewareTests, self).__init__(*args, **kwargs) module_name = BaseMiddlewareTests.__module__ - self.MIDDLEWARE_NAME = module_name + '.BaseMiddlewareTestMiddleware' + self.MIDDLEWARE_NAME = module_name + ".BaseMiddlewareTestMiddleware" def test_settings_initially_loaded(self): - expected_settings = {'R1': 1, 'R2': 2, 'O1': 3, 'O2': 4} - with self.settings( - MIDDLEWARE=(self.MIDDLEWARE_NAME,), **expected_settings - ): - response = self.client.get('/home/') + expected_settings = {"R1": 1, "R2": 2, "O1": 3, "O2": 4} + with self.settings(MIDDLEWARE=(self.MIDDLEWARE_NAME,), **expected_settings): + response = self.client.get("/home/") self.assertEqual(expected_settings, response.loaded_settings) def test_required_settings(self): with self.settings(MIDDLEWARE=(self.MIDDLEWARE_NAME,)): - self.assertRaises(ImproperlyConfigured, self.client.get, '/home/') + self.assertRaises(ImproperlyConfigured, self.client.get, "/home/") def test_optional_settings(self): - with self.settings( - MIDDLEWARE=(self.MIDDLEWARE_NAME,), R1=True, R2=True - ): - response = self.client.get('/home/') - self.assertEqual(None, response.loaded_settings['O1']) - self.assertEqual(None, response.loaded_settings['O2']) + with self.settings(MIDDLEWARE=(self.MIDDLEWARE_NAME,), R1=True, R2=True): + response = self.client.get("/home/") + self.assertEqual(None, response.loaded_settings["O1"]) + self.assertEqual(None, response.loaded_settings["O2"]) def test_setting_change(self): - with self.settings( - MIDDLEWARE=(self.MIDDLEWARE_NAME,), R1=123, R2=True - ): - response = self.client.get('/home/') - self.assertEqual(123, response.loaded_settings['R1']) + with self.settings(MIDDLEWARE=(self.MIDDLEWARE_NAME,), R1=123, R2=True): + response = self.client.get("/home/") + self.assertEqual(123, response.loaded_settings["R1"]) with override_settings(R1=456): - response = self.client.get('/home/') - self.assertEqual(456, response.loaded_settings['R1']) + response = self.client.get("/home/") + self.assertEqual(456, response.loaded_settings["R1"]) - response = self.client.get('/home/') - self.assertEqual(123, response.loaded_settings['R1']) + response = self.client.get("/home/") + self.assertEqual(123, response.loaded_settings["R1"]) def test_load_setting_abstract_method(self): base = BaseMiddleware() self.assertRaises(NotImplementedError, base.load_setting, None, None) -@override_settings(MIDDLEWARE=( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'security.middleware.LoginRequiredMiddleware', -)) +@override_settings( + MIDDLEWARE=( + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "security.middleware.LoginRequiredMiddleware", + ) +) class LoginRequiredMiddlewareTests(TestCase): def setUp(self): self.login_url = reverse("login") def test_aborts_if_auth_middleware_missing(self): middleware_classes = settings.MIDDLEWARE - auth_mw = 'django.contrib.auth.middleware.AuthenticationMiddleware' - middleware_classes = [ - m for m in middleware_classes if m != auth_mw - ] + auth_mw = "django.contrib.auth.middleware.AuthenticationMiddleware" + middleware_classes = [m for m in middleware_classes if m != auth_mw] with self.settings(MIDDLEWARE=middleware_classes): - self.assertRaises(ImproperlyConfigured, self.client.get, '/home/') + self.assertRaises(ImproperlyConfigured, self.client.get, "/home/") def test_redirects_unauthenticated_request(self): - response = self.client.get('/home/') + response = self.client.get("/home/") self.assertRedirects(response, self.login_url + "?next=/home/") def test_redirects_unauthenticated_ajax_request(self): response = self.client.get( - '/home/', - HTTP_X_REQUESTED_WITH='XMLHttpRequest', + "/home/", + HTTP_X_REQUESTED_WITH="XMLHttpRequest", ) self.assertEqual(response.status_code, 401) self.assertEqual( - json.loads(response.content.decode('utf-8')), + json.loads(response.content.decode("utf-8")), {"login_url": self.login_url}, ) def test_redirects_to_custom_login_url(self): middlware_classes = list(settings.MIDDLEWARE) - custom_login_middleware = 'tests.tests.CustomLoginURLMiddleware' + custom_login_middleware = "tests.tests.CustomLoginURLMiddleware" with self.settings( MIDDLEWARE=[custom_login_middleware] + middlware_classes, ): - response = self.client.get('/home/') - self.assertRedirects(response, '/custom-login/') + response = self.client.get("/home/") + self.assertRedirects(response, "/custom-login/") response = self.client.get( - '/home/', - HTTP_X_REQUESTED_WITH='XMLHttpRequest', + "/home/", + HTTP_X_REQUESTED_WITH="XMLHttpRequest", ) self.assertEqual(response.status_code, 401) self.assertEqual( - json.loads(response.content.decode('utf-8')), - {"login_url": '/custom-login/'}, + json.loads(response.content.decode("utf-8")), + {"login_url": "/custom-login/"}, ) def test_logs_out_inactive_users(self): @@ -192,28 +197,30 @@ def test_logs_out_inactive_users(self): ) never_expire_password(user) self.client.login(username="foo", password="foo") - resp = self.client.get('/home/') + resp = self.client.get("/home/") self.assertEqual(resp.status_code, 200) # check we are logged in user.is_active = False user.save() - resp = self.client.get('/home/') + resp = self.client.get("/home/") self.assertRedirects(resp, self.login_url + "?next=/home/") -@override_settings(MIDDLEWARE=( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'security.middleware.MandatoryPasswordChangeMiddleware', -)) +@override_settings( + MIDDLEWARE=( + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "security.middleware.MandatoryPasswordChangeMiddleware", + ) +) class RequirePasswordChangeTests(TestCase): def test_require_password_change(self): """ A brand-new user should have an already-expired password, and therefore be redirected to the password change form on any request. """ - user = User.objects.create_user(username="foo", - password="foo", - email="foo@foo.com") + user = User.objects.create_user( + username="foo", password="foo", email="foo@foo.com" + ) self.client.login(username="foo", password="foo") try: with self.settings( @@ -233,19 +240,20 @@ def test_superuser_password_change(self): """ A superuser can be forced to change their password via settings. """ - user = User.objects.create_superuser(username="foo", - password="foo", - email="foo@foo.com") + user = User.objects.create_superuser( + username="foo", password="foo", email="foo@foo.com" + ) self.client.login(username="foo", password="foo") - with self.settings(MANDATORY_PASSWORD_CHANGE={ - "URL_NAME": "change_password"}): + with self.settings(MANDATORY_PASSWORD_CHANGE={"URL_NAME": "change_password"}): self.assertEqual(self.client.get("/home/").status_code, 200) try: - with self.settings(MANDATORY_PASSWORD_CHANGE={ - "URL_NAME": "change_password", - "INCLUDE_SUPERUSERS": True - }): + with self.settings( + MANDATORY_PASSWORD_CHANGE={ + "URL_NAME": "change_password", + "INCLUDE_SUPERUSERS": True, + } + ): self.assertRedirects( self.client.get("/home/"), reverse("change_password"), @@ -256,18 +264,18 @@ def test_superuser_password_change(self): def test_dont_redirect_exempt_urls(self): user = User.objects.create_user( - username="foo", - password="foo", - email="foo@foo.com" + username="foo", password="foo", email="foo@foo.com" ) self.client.login(username="foo", password="foo") try: - with self.settings(MANDATORY_PASSWORD_CHANGE={ - "URL_NAME": "change_password", - "EXEMPT_URLS": (r'^test1/$', r'^test2/$'), - "EXEMPT_URL_NAMES": ("test3", "test4"), - }): + with self.settings( + MANDATORY_PASSWORD_CHANGE={ + "URL_NAME": "change_password", + "EXEMPT_URLS": (r"^test1/$", r"^test2/$"), + "EXEMPT_URL_NAMES": ("test3", "test4"), + } + ): # Redirect pages in general self.assertRedirects( self.client.get("/home/"), @@ -290,16 +298,18 @@ def test_dont_redirect_exempt_urls(self): user.delete() def test_dont_choke_on_exempt_urls_that_dont_resolve(self): - user = User.objects.create_user(username="foo", - password="foo", - email="foo@foo.com") + user = User.objects.create_user( + username="foo", password="foo", email="foo@foo.com" + ) self.client.login(username="foo", password="foo") try: - with self.settings(MANDATORY_PASSWORD_CHANGE={ - "URL_NAME": "change_password", - "EXEMPT_URL_NAMES": ("fake1", "fake2"), - }): + with self.settings( + MANDATORY_PASSWORD_CHANGE={ + "URL_NAME": "change_password", + "EXEMPT_URL_NAMES": ("fake1", "fake2"), + } + ): # Redirect pages in general self.assertRedirects( self.client.get("/home/"), @@ -314,8 +324,8 @@ def test_raises_improperly_configured(self): self.assertRaises( ImproperlyConfigured, change.load_setting, - 'MANDATORY_PASSWORD_CHANGE', - {'EXEMPT_URLS': []}, + "MANDATORY_PASSWORD_CHANGE", + {"EXEMPT_URLS": []}, ) @@ -332,33 +342,32 @@ def ajax_only_view(request): request = HttpRequest() response = ajax_only_view(request) self.assertTrue(isinstance(response, HttpResponseForbidden)) - request.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" response = ajax_only_view(request) self.assertFalse(isinstance(response, HttpResponseForbidden)) -@override_settings(MIDDLEWARE=( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'security.middleware.SessionExpiryPolicyMiddleware', - 'security.middleware.LoginRequiredMiddleware', -)) +@override_settings( + MIDDLEWARE=( + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "security.middleware.SessionExpiryPolicyMiddleware", + "security.middleware.LoginRequiredMiddleware", + ) +) class SessionExpiryTests(TestCase): - def test_session_variables_are_set(self): """ Verify the session cookie stores the start time and last active time. """ - self.client.get('/home/') + self.client.get("/home/") now = timezone.now() start_time = SessionExpiryPolicyMiddleware._get_datetime_in_session( - SessionExpiryPolicyMiddleware.START_TIME_KEY, - self.client.session + SessionExpiryPolicyMiddleware.START_TIME_KEY, self.client.session ) last_activity = SessionExpiryPolicyMiddleware._get_datetime_in_session( - SessionExpiryPolicyMiddleware.LAST_ACTIVITY_KEY, - self.client.session + SessionExpiryPolicyMiddleware.LAST_ACTIVITY_KEY, self.client.session ) self.assertTrue(now - start_time < datetime.timedelta(seconds=10)) @@ -369,17 +378,12 @@ def session_expiry_test(self, key, expired): Verify that expired sessions are cleared from the system. (And that we redirect to the login page.) """ - self.assertTrue(self.client.get('/home/').status_code, 200) + self.assertTrue(self.client.get("/home/").status_code, 200) session = self.client.session - SessionExpiryPolicyMiddleware._set_datetime_in_session( - key, - expired, - session - ) + SessionExpiryPolicyMiddleware._set_datetime_in_session(key, expired, session) session.save() - response = self.client.get('/home/') - self.assertRedirects(response, - reverse("login") + '?next=/home/') + response = self.client.get("/home/") + self.assertRedirects(response, reverse("login") + "?next=/home/") @login_user def test_session_too_old(self): @@ -389,8 +393,7 @@ def test_session_too_old(self): """ delta = SessionExpiryPolicyMiddleware().SESSION_COOKIE_AGE + 1 expired = timezone.now() - datetime.timedelta(seconds=delta) - self.session_expiry_test(SessionExpiryPolicyMiddleware.START_TIME_KEY, - expired) + self.session_expiry_test(SessionExpiryPolicyMiddleware.START_TIME_KEY, expired) @login_user def test_session_inactive_too_long(self): @@ -410,22 +413,19 @@ def test_exempted_session_expiry_urls(self): delta = SessionExpiryPolicyMiddleware().SESSION_INACTIVITY_TIMEOUT + 1 expired = timezone.now() - datetime.timedelta(seconds=delta) - self.assertTrue(self.client.get('/home/').status_code, 200) + self.assertTrue(self.client.get("/home/").status_code, 200) session = self.client.session SessionExpiryPolicyMiddleware._set_datetime_in_session( - SessionExpiryPolicyMiddleware.LAST_ACTIVITY_KEY, - expired, - session + SessionExpiryPolicyMiddleware.LAST_ACTIVITY_KEY, expired, session ) session.save() - exempted_response = self.client.get('/accounts/login/') - not_exempted_response = self.client.get('/home/') + exempted_response = self.client.get("/accounts/login/") + not_exempted_response = self.client.get("/home/") self.assertTrue(exempted_response.status_code, 200) - self.assertRedirects(not_exempted_response, - reverse("login") + '?next=/home/') + self.assertRedirects(not_exempted_response, reverse("login") + "?next=/home/") @login_user def test_custom_logout(self): @@ -438,26 +438,26 @@ def test_custom_logout(self): assert mocked_custom_logout.called -@override_settings(MIDDLEWARE=( - 'security.middleware.NoConfidentialCachingMiddleware', -)) +@override_settings(MIDDLEWARE=("security.middleware.NoConfidentialCachingMiddleware",)) class ConfidentialCachingTests(TestCase): def setUp(self): self.header_values = { - "Cache-Control": 'no-cache, no-store, max-age=0, must-revalidate', + "Cache-Control": "no-cache, no-store, max-age=0, must-revalidate", "Pragma": "no-cache", - "Expires": '-1' + "Expires": "-1", } - @override_settings(NO_CONFIDENTIAL_CACHING={ - "WHITELIST_ON": True, - "BLACKLIST_ON": False, - "WHITELIST_REGEXES": ["accounts/login/$"], - "BLACKLIST_REGEXES": ["accounts/logout/$"] - }) + @override_settings( + NO_CONFIDENTIAL_CACHING={ + "WHITELIST_ON": True, + "BLACKLIST_ON": False, + "WHITELIST_REGEXES": ["accounts/login/$"], + "BLACKLIST_REGEXES": ["accounts/logout/$"], + } + ) def test_whitelisting(self): # Get Non Confidential Page - response = self.client.get('/accounts/login/') + response = self.client.get("/accounts/login/") for header, value in self.header_values.items(): self.assertNotEqual(response.get(header, None), value) # Get Confidential Page @@ -465,15 +465,17 @@ def test_whitelisting(self): for header, value in self.header_values.items(): self.assertEqual(response.get(header, None), value) - @override_settings(NO_CONFIDENTIAL_CACHING={ - "WHITELIST_ON": False, - "BLACKLIST_ON": True, - "WHITELIST_REGEXES": ["accounts/login/$"], - "BLACKLIST_REGEXES": ["accounts/logout/$"] - }) + @override_settings( + NO_CONFIDENTIAL_CACHING={ + "WHITELIST_ON": False, + "BLACKLIST_ON": True, + "WHITELIST_REGEXES": ["accounts/login/$"], + "BLACKLIST_REGEXES": ["accounts/logout/$"], + } + ) def test_blacklisting(self): # Get Non Confidential Page - response = self.client.get('/accounts/login/') + response = self.client.get("/accounts/login/") for header, value in self.header_values.items(): self.assertNotEqual(response.get(header, None), value) # Get Confidential Page @@ -482,124 +484,90 @@ def test_blacklisting(self): self.assertEqual(response.get(header, None), value) -@override_settings(MIDDLEWARE=('security.middleware.XFrameOptionsMiddleware',)) +@override_settings(MIDDLEWARE=("security.middleware.XFrameOptionsMiddleware",)) class XFrameOptionsDenyTests(TestCase): - def test_option_set(self): """ Verify the HTTP Response Header is set. """ - response = self.client.get('/accounts/login/') - self.assertEqual(response['X-Frame-Options'], settings.X_FRAME_OPTIONS) + response = self.client.get("/accounts/login/") + self.assertEqual(response["X-Frame-Options"], settings.X_FRAME_OPTIONS) def test_exclude_urls(self): """ Verify that pages can be excluded from the X-Frame-Options header. """ - response = self.client.get('/home/') - self.assertEqual(response['X-Frame-Options'], settings.X_FRAME_OPTIONS) - response = self.client.get('/test1/') - self.assertNotIn('X-Frame-Options', response) + response = self.client.get("/home/") + self.assertEqual(response["X-Frame-Options"], settings.X_FRAME_OPTIONS) + response = self.client.get("/test1/") + self.assertNotIn("X-Frame-Options", response) def test_improperly_configured(self): xframe = XFrameOptionsMiddleware() self.assertRaises( ImproperlyConfigured, xframe.load_setting, - 'X_FRAME_OPTIONS', - 'invalid', + "X_FRAME_OPTIONS", + "invalid", ) self.assertRaises( ImproperlyConfigured, xframe.load_setting, - 'X_FRAME_OPTIONS_EXCLUDE_URLS', + "X_FRAME_OPTIONS_EXCLUDE_URLS", 1, ) @override_settings(X_FRAME_OPTIONS_EXCLUDE_URLS=None) def test_default_exclude_urls(self): # This URL is excluded in other tests, see settings.py - response = self.client.get('/test1/') + response = self.client.get("/test1/") self.assertEqual( - response['X-Frame-Options'], + response["X-Frame-Options"], settings.X_FRAME_OPTIONS, ) @override_settings(X_FRAME_OPTIONS=None) def test_default_xframe_option(self): - response = self.client.get('/home/') + response = self.client.get("/home/") self.assertEqual( - response['X-Frame-Options'], - 'deny', - ) - - -@override_settings(MIDDLEWARE=('security.middleware.XssProtectMiddleware',)) -class XXssProtectTests(TestCase): - - def test_option_set(self): - """ - Verify the HTTP Response Header is set. - """ - response = self.client.get('/accounts/login/') - self.assertNotEqual(response['X-XSS-Protection'], None) - - def test_default_setting(self): - with self.settings(XSS_PROTECT=None): - response = self.client.get('/accounts/login/') - self.assertEqual(response['X-XSS-Protection'], '1') # sanitize - - def test_option_off(self): - with self.settings(XSS_PROTECT='off'): - response = self.client.get('/accounts/login/') - self.assertEqual(response['X-XSS-Protection'], '0') # off - - def test_improper_configuration_raises(self): - xss = XssProtectMiddleware() - self.assertRaises( - ImproperlyConfigured, - xss.load_setting, - 'XSS_PROTECT', - 'invalid', + response["X-Frame-Options"], + "deny", ) -@override_settings(MIDDLEWARE=('security.middleware.ContentNoSniff',)) +@override_settings(MIDDLEWARE=("security.middleware.ContentNoSniff",)) class ContentNoSniffTests(TestCase): - def test_option_set(self): """ Verify the HTTP Response Header is set. """ - response = self.client.get('/accounts/login/') - self.assertEqual(response['X-Content-Options'], 'nosniff') + response = self.client.get("/accounts/login/") + self.assertEqual(response["X-Content-Options"], "nosniff") -@override_settings(MIDDLEWARE=( - 'security.middleware.StrictTransportSecurityMiddleware', -)) +@override_settings( + MIDDLEWARE=("security.middleware.StrictTransportSecurityMiddleware",) +) class StrictTransportSecurityTests(TestCase): - def test_option_set(self): """ Verify the HTTP Response Header is set. """ - response = self.client.get('/accounts/login/') - self.assertNotEqual(response['Strict-Transport-Security'], None) + response = self.client.get("/accounts/login/") + self.assertNotEqual(response["Strict-Transport-Security"], None) @override_settings( AUTHENTICATION_THROTTLING={ "DELAY_FUNCTION": lambda x, _: (2 ** (x - 1) if x else 0, 0), - "LOGIN_URLS_WITH_TEMPLATES": [ - ("accounts/login/", "registration/login.html") - ] + "LOGIN_URLS_WITH_TEMPLATES": [("accounts/login/", "registration/login.html")], }, MIDDLEWARE=( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'security.auth_throttling.Middleware',) + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "security.auth_throttling.Middleware", + ), ) class AuthenticationThrottlingTests(TestCase): def setUp(self): @@ -607,17 +575,17 @@ def setUp(self): self.old_time = time.time self.time = 0 time.time = lambda: self.time - self.user = User.objects.create_user(username="foo", password="foo", - email="a@foo.org") + self.user = User.objects.create_user( + username="foo", password="foo", email="a@foo.org" + ) def tearDown(self): time.time = self.old_time def attempt(self, password): - return self.client.post("/accounts/login/", - {"username": "foo", - "password": password}, - follow=True) + return self.client.post( + "/accounts/login/", {"username": "foo", "password": password}, follow=True + ) def reset(self): self.client.logout() @@ -627,8 +595,7 @@ def typo(self): self.assertTemplateUsed(self.attempt("bar"), "registration/login.html") def _succeed(self): - self.assertTemplateNotUsed(self.attempt("foo"), - "registration/login.html") + self.assertTemplateNotUsed(self.attempt("foo"), "registration/login.html") self.reset() def _fail(self): @@ -712,12 +679,12 @@ def test_per_account_throttling(self): self.set_time(3) self._succeed() - @override_settings(AUTHENTICATION_THROTTLING={ - "DELAY_FUNCTION": lambda x, y: (x, y), - "LOGIN_URLS_WITH_TEMPLATES": [ - ("accounts/login/", None) - ] - }) + @override_settings( + AUTHENTICATION_THROTTLING={ + "DELAY_FUNCTION": lambda x, y: (x, y), + "LOGIN_URLS_WITH_TEMPLATES": [("accounts/login/", None)], + } + ) def test_too_many_requests_error_when_no_template_provided(self): """ Verify we simply return a 429 error when there is no login template @@ -745,8 +712,9 @@ def test_reset_button(self): """ self.set_time(0) self.typo() - admin = User.objects.create_user(username="bar", password="bar", - email="a@bar.org") + admin = User.objects.create_user( + username="bar", password="bar", email="a@bar.org" + ) admin.is_superuser = True admin.save() self.client.login(username="bar", password="bar") @@ -756,9 +724,11 @@ def test_reset_button(self): self.client.logout() self._succeed() - @override_settings(AUTHENTICATION_THROTTLING={ - "DELAY_FUNCTION": lambda x, y: (x, y), - }) + @override_settings( + AUTHENTICATION_THROTTLING={ + "DELAY_FUNCTION": lambda x, y: (x, y), + } + ) def test_improperly_configured_middleware(self): self.assertRaises(ImproperlyConfigured, AuthThrottlingMiddleware) @@ -785,16 +755,15 @@ def test_throttle_reset_404_on_not_found(self): self.assertEqual(resp.status_code, 404) -@override_settings(MIDDLEWARE=('security.middleware.P3PPolicyMiddleware',)) +@override_settings(MIDDLEWARE=("security.middleware.P3PPolicyMiddleware",)) class P3PPolicyTests(TestCase): - def setUp(self): self.policy = "NN AD BLAH" settings.P3P_COMPACT_POLICY = self.policy def test_p3p_header(self): expected_header = 'policyref="/w3c/p3p.xml" CP="%s"' % self.policy - response = self.client.get('/accounts/login/') + response = self.client.get("/accounts/login/") self.assertEqual(response["P3P"], expected_header) @@ -804,13 +773,12 @@ def test_min_length(self): min_length(6)("abcdef") -@override_settings(MIDDLEWARE=( - 'security.middleware.ContentSecurityPolicyMiddleware', -)) +@override_settings(MIDDLEWARE=("security.middleware.ContentSecurityPolicyMiddleware",)) class ContentSecurityPolicyTests(TestCase): class FakeHttpRequest(object): - method = 'POST' - body = """{ + method = "POST" + body = ( + """{ "csp-report": { "document-uri": "http://example.org/page.html", "referrer": "http://evil.example.com/haxor.html", @@ -819,25 +787,26 @@ class FakeHttpRequest(object): "original-policy": "%s" } } - """ % settings.CSP_STRING + """ + % settings.CSP_STRING + ) META = { - 'CONTENT_TYPE': 'application/json', - 'REMOTE_ADDR': '127.0.0.1', - 'HTTP_USER_AGENT': 'FakeHTTPRequest' + "CONTENT_TYPE": "application/json", + "REMOTE_ADDR": "127.0.0.1", + "HTTP_USER_AGENT": "FakeHTTPRequest", } def test_option_set(self): """ Verify the HTTP Response Header is set. """ - response = self.client.get('/accounts/login/') + response = self.client.get("/accounts/login/") self.assertEqual( - response['Content-Security-Policy'], + response["Content-Security-Policy"], settings.CSP_STRING, ) def test_json(self): - req = ContentSecurityPolicyTests.FakeHttpRequest() parsed = json.loads(req.body) @@ -846,7 +815,6 @@ def test_json(self): # http://www.w3.org/TR/CSP/#sample-violation-report def test_csp_view(self): - req = ContentSecurityPolicyTests.FakeHttpRequest() # call the view @@ -855,21 +823,30 @@ def test_csp_view(self): self.assertEqual(resp.status_code, 204) def test_csp_gen_1(self): - csp_dict = { - 'default-src': ['self', 'cdn.example.com'], - 'script-src': ['self', 'js.example.com'], - 'style-src': ['self', 'css.example.com'], - 'img-src': ['self', 'img.example.com'], - 'connect-src': ['self', ], - 'font-src': ['fonts.example.com', ], - 'object-src': ['self'], - 'media-src': ['media.example.com', ], - 'frame-src': ['*', ], - 'sandbox': ['', ], - 'reflected-xss': 'filter', - 'referrer': 'origin', - 'report-uri': 'http://example.com/csp-report', + "default-src": ["self", "cdn.example.com"], + "script-src": ["self", "js.example.com"], + "style-src": ["self", "css.example.com"], + "img-src": ["self", "img.example.com"], + "connect-src": [ + "self", + ], + "font-src": [ + "fonts.example.com", + ], + "object-src": ["self"], + "media-src": [ + "media.example.com", + ], + "frame-src": [ + "*", + ], + "sandbox": [ + "", + ], + "reflected-xss": "filter", + "referrer": "origin", + "report-uri": "http://example.com/csp-report", } expected = ( @@ -894,36 +871,33 @@ def test_csp_gen_1(self): # We can't assume the iteration order on the csp_dict, so we split the # output, sort, and ensure we got all the results back, regardless of # the order. - expected_list = sorted(x.strip() for x in expected.split(';')) - generated_list = sorted(x.strip() for x in generated.split(';')) + expected_list = sorted(x.strip() for x in expected.split(";")) + generated_list = sorted(x.strip() for x in generated.split(";")) self.assertEqual(generated_list, expected_list) def test_csp_gen_2(self): - csp_dict = {'default-src': ('none',), 'script-src': ['none']} + csp_dict = {"default-src": ("none",), "script-src": ["none"]} expected = "default-src 'none'; script-src 'none'" csp = ContentSecurityPolicyMiddleware() generated = csp._csp_builder(csp_dict) - expected_list = sorted(x.strip() for x in expected.split(';')) - generated_list = sorted(x.strip() for x in generated.split(';')) + expected_list = sorted(x.strip() for x in expected.split(";")) + generated_list = sorted(x.strip() for x in generated.split(";")) self.assertEqual(generated_list, expected_list) def test_csp_gen_3(self): csp_dict = { - 'script-src': [ - 'self', - 'www.google-analytics.com', - 'ajax.googleapis.com', + "script-src": [ + "self", + "www.google-analytics.com", + "ajax.googleapis.com", ], } - expected = ( - "script-src " - "'self' www.google-analytics.com ajax.googleapis.com" - ) + expected = "script-src " "'self' www.google-analytics.com ajax.googleapis.com" csp = ContentSecurityPolicyMiddleware() generated = csp._csp_builder(csp_dict) @@ -932,40 +906,40 @@ def test_csp_gen_3(self): def test_csp_gen_err(self): # argument not passed as array, expect failure - csp_dict = {'default-src': 'self'} + csp_dict = {"default-src": "self"} csp = ContentSecurityPolicyMiddleware() self.assertRaises(MiddlewareNotUsed, csp._csp_builder, csp_dict) def test_csp_gen_err2(self): - csp_dict = {'invalid': 'self'} # invalid directive + csp_dict = {"invalid": "self"} # invalid directive csp = ContentSecurityPolicyMiddleware() self.assertRaises(MiddlewareNotUsed, csp._csp_builder, csp_dict) def test_csp_gen_err3(self): - csp_dict = {'sandbox': 'none'} # not a list or tuple, expect failure + csp_dict = {"sandbox": "none"} # not a list or tuple, expect failure csp = ContentSecurityPolicyMiddleware() self.assertRaises(MiddlewareNotUsed, csp._csp_builder, csp_dict) def test_csp_gen_err4(self): # Not an allowed directive, expect failure - csp_dict = {'sandbox': ('invalid', )} + csp_dict = {"sandbox": ("invalid",)} csp = ContentSecurityPolicyMiddleware() self.assertRaises(MiddlewareNotUsed, csp._csp_builder, csp_dict) def test_csp_gen_err5(self): # Not an allowed directive, expect failure - csp_dict = {'referrer': 'invalid'} + csp_dict = {"referrer": "invalid"} csp = ContentSecurityPolicyMiddleware() self.assertRaises(MiddlewareNotUsed, csp._csp_builder, csp_dict) def test_csp_gen_err6(self): # Not an allowed directive, expect failure - csp_dict = {'reflected-xss': 'invalid'} + csp_dict = {"reflected-xss": "invalid"} csp = ContentSecurityPolicyMiddleware() self.assertRaises(MiddlewareNotUsed, csp._csp_builder, csp_dict) @@ -973,29 +947,29 @@ def test_csp_gen_err6(self): def test_enforced_by_default(self): with self.settings(CSP_MODE=None): response = self.client.get(settings.LOGIN_URL) - self.assertIn('Content-Security-Policy', response) - self.assertNotIn('Content-Security-Policy-Report-Only', response) + self.assertIn("Content-Security-Policy", response) + self.assertNotIn("Content-Security-Policy-Report-Only", response) def test_enforced_when_on(self): - with self.settings(CSP_MODE='enforce'): + with self.settings(CSP_MODE="enforce"): response = self.client.get(settings.LOGIN_URL) - self.assertIn('Content-Security-Policy', response) - self.assertNotIn('Content-Security-Policy-Report-Only', response) + self.assertIn("Content-Security-Policy", response) + self.assertNotIn("Content-Security-Policy-Report-Only", response) def test_report_only_set(self): - with self.settings(CSP_MODE='report-only'): + with self.settings(CSP_MODE="report-only"): response = self.client.get(settings.LOGIN_URL) - self.assertNotIn('Content-Security-Policy', response) - self.assertIn('Content-Security-Policy-Report-Only', response) + self.assertNotIn("Content-Security-Policy", response) + self.assertIn("Content-Security-Policy-Report-Only", response) def test_both_enforce_and_report_only(self): - with self.settings(CSP_MODE='enforce-and-report-only'): + with self.settings(CSP_MODE="enforce-and-report-only"): response = self.client.get(settings.LOGIN_URL) - self.assertIn('Content-Security-Policy', response) - self.assertIn('Content-Security-Policy-Report-Only', response) + self.assertIn("Content-Security-Policy", response) + self.assertIn("Content-Security-Policy-Report-Only", response) def test_invalid_csp_mode(self): - with self.settings(CSP_MODE='invalid'): + with self.settings(CSP_MODE="invalid"): self.assertRaises( MiddlewareNotUsed, ContentSecurityPolicyMiddleware, @@ -1009,7 +983,7 @@ def test_no_csp_options_set(self): ) def test_both_csp_options_set(self): - with self.settings(CSP_DICT={'x': 'y'}, CSP_STRING='x y;'): + with self.settings(CSP_DICT={"x": "y"}, CSP_STRING="x y;"): self.assertRaises( MiddlewareNotUsed, ContentSecurityPolicyMiddleware, @@ -1017,31 +991,30 @@ def test_both_csp_options_set(self): def test_sets_from_csp_dict(self): with self.settings( - CSP_DICT={'default-src': ('self',)}, + CSP_DICT={"default-src": ("self",)}, CSP_STRING=None, ): - response = self.client.get('/accounts/login/') + response = self.client.get("/accounts/login/") self.assertEqual( - response['Content-Security-Policy'], + response["Content-Security-Policy"], "default-src 'self'", ) -@override_settings(MIDDLEWARE=('security.middleware.DoNotTrackMiddleware',)) +@override_settings(MIDDLEWARE=("security.middleware.DoNotTrackMiddleware",)) class DoNotTrackTests(TestCase): - def setUp(self): self.dnt = DoNotTrackMiddleware() self.request = HttpRequest() self.response = HttpResponse() def test_set_DNT_on(self): - self.request.META['HTTP_DNT'] = '1' + self.request.META["HTTP_DNT"] = "1" self.dnt.process_request(self.request) self.assertTrue(self.request.dnt) def test_set_DNT_off(self): - self.request.META['HTTP_DNT'] = 'off' + self.request.META["HTTP_DNT"] = "off" self.dnt.process_request(self.request) self.assertFalse(self.request.dnt) @@ -1050,84 +1023,86 @@ def test_default_DNT(self): self.assertFalse(self.request.dnt) def test_DNT_echo_on(self): - self.request.META['HTTP_DNT'] = '1' + self.request.META["HTTP_DNT"] = "1" self.dnt.process_response(self.request, self.response) - self.assertIn('DNT', self.response) - self.assertEqual(self.response['DNT'], '1') + self.assertIn("DNT", self.response) + self.assertEqual(self.response["DNT"], "1") def test_DNT_echo_off(self): - self.request.META['HTTP_DNT'] = 'off' + self.request.META["HTTP_DNT"] = "off" self.dnt.process_response(self.request, self.response) - self.assertEqual(self.response['DNT'], 'off') + self.assertEqual(self.response["DNT"], "off") def test_DNT_echo_default(self): self.dnt.process_response(self.request, self.response) - self.assertNotIn('DNT', self.response) + self.assertNotIn("DNT", self.response) -class ReferrerPolicyTests(TestCase): +class ReferrerPolicyTests(TestCase): def test_option_set(self): """ Verify the HTTP Referrer-Policy Header is set. """ - response = self.client.get('/accounts/login/') - self.assertNotEqual(response['Referrer-Policy'], None) + response = self.client.get("/accounts/login/") + self.assertNotEqual(response["Referrer-Policy"], None) def test_default_setting(self): with self.settings(REFERRER_POLICY=None): - response = self.client.get('/accounts/login/') - self.assertEqual(response['Referrer-Policy'], 'same-origin') + response = self.client.get("/accounts/login/") + self.assertEqual(response["Referrer-Policy"], "same-origin") def test_no_referrer_setting(self): - with self.settings(REFERRER_POLICY='no-referrer'): - response = self.client.get('/accounts/login/') - self.assertEqual(response['Referrer-Policy'], 'no-referrer') + with self.settings(REFERRER_POLICY="no-referrer"): + response = self.client.get("/accounts/login/") + self.assertEqual(response["Referrer-Policy"], "no-referrer") def test_no_referrer_when_downgrade_setting(self): - with self.settings(REFERRER_POLICY='no-referrer-when-downgrade'): - response = self.client.get('/accounts/login/') - self.assertEqual(response['Referrer-Policy'], 'no-referrer-when-downgrade') + with self.settings(REFERRER_POLICY="no-referrer-when-downgrade"): + response = self.client.get("/accounts/login/") + self.assertEqual(response["Referrer-Policy"], "no-referrer-when-downgrade") def test_origin_setting(self): - with self.settings(REFERRER_POLICY='origin'): - response = self.client.get('/accounts/login/') - self.assertEqual(response['Referrer-Policy'], 'origin') + with self.settings(REFERRER_POLICY="origin"): + response = self.client.get("/accounts/login/") + self.assertEqual(response["Referrer-Policy"], "origin") def test_origin_when_cross_origin_setting(self): - with self.settings(REFERRER_POLICY='origin-when-cross-origin'): - response = self.client.get('/accounts/login/') - self.assertEqual(response['Referrer-Policy'], 'origin-when-cross-origin') + with self.settings(REFERRER_POLICY="origin-when-cross-origin"): + response = self.client.get("/accounts/login/") + self.assertEqual(response["Referrer-Policy"], "origin-when-cross-origin") def test_same_origin_setting(self): - with self.settings(REFERRER_POLICY='same-origin'): - response = self.client.get('/accounts/login/') - self.assertEqual(response['Referrer-Policy'], 'same-origin') + with self.settings(REFERRER_POLICY="same-origin"): + response = self.client.get("/accounts/login/") + self.assertEqual(response["Referrer-Policy"], "same-origin") def test_strict_origin_setting(self): - with self.settings(REFERRER_POLICY='strict-origin'): - response = self.client.get('/accounts/login/') - self.assertEqual(response['Referrer-Policy'], 'strict-origin') + with self.settings(REFERRER_POLICY="strict-origin"): + response = self.client.get("/accounts/login/") + self.assertEqual(response["Referrer-Policy"], "strict-origin") def test_strict_origin_when_cross_origin_setting(self): - with self.settings(REFERRER_POLICY='strict-origin-when-cross-origin'): - response = self.client.get('/accounts/login/') - self.assertEqual(response['Referrer-Policy'], 'strict-origin-when-cross-origin') + with self.settings(REFERRER_POLICY="strict-origin-when-cross-origin"): + response = self.client.get("/accounts/login/") + self.assertEqual( + response["Referrer-Policy"], "strict-origin-when-cross-origin" + ) def test_unsafe_url_setting(self): - with self.settings(REFERRER_POLICY='unsafe-url'): - response = self.client.get('/accounts/login/') - self.assertEqual(response['Referrer-Policy'], 'unsafe-url') + with self.settings(REFERRER_POLICY="unsafe-url"): + response = self.client.get("/accounts/login/") + self.assertEqual(response["Referrer-Policy"], "unsafe-url") def test_off_setting(self): - with self.settings(REFERRER_POLICY='off'): - response = self.client.get('/accounts/login/') - self.assertEqual('Referrer-Policy' in response, False) + with self.settings(REFERRER_POLICY="off"): + response = self.client.get("/accounts/login/") + self.assertEqual("Referrer-Policy" in response, False) def test_improper_configuration_raises(self): referer_policy_middleware = ReferrerPolicyMiddleware() self.assertRaises( ImproperlyConfigured, referer_policy_middleware.load_setting, - 'REFERRER_POLICY', - 'invalid', + "REFERRER_POLICY", + "invalid", )