Skip to content

Commit

Permalink
Merge pull request #92 from sdelements/pr/59
Browse files Browse the repository at this point in the history
Add support for Referrer-Policy
  • Loading branch information
Gee19 authored Jun 3, 2021
2 parents 28ba05d + aabca6b commit 8590edc
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 54 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ or minimum configuration.
<td><b>DEPRECATED: </b>Will be removed in future releases.<br/>Adds the HTTP header attribute specifying compact P3P policy.
<td>Required.

<tr>
<td><a href="http://django-security.readthedocs.org/en/latest/#security.middleware.ReferrerPolicyMiddleware">ReferrerPolicyMiddleware</a>
<td>Specify when the browser will set a `Referer` header.
<td>Optional.

<tr>
<td><a href="http://django-security.readthedocs.org/en/latest/#security.middleware.SessionExpiryPolicyMiddleware">SessionExpiryPolicyMiddleware</a>
<td>Expire sessions on browser close, and on expiry times stored in the cookie itself.
Expand Down
57 changes: 55 additions & 2 deletions security/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from django.utils.deprecation import MiddlewareMixin
import django.views.static

from ua_parser.user_agent_parser import ParseUserAgent
from ua_parser import user_agent_parser


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -901,7 +901,7 @@ def process_response(self, request, response):
# choose headers based enforcement mode
is_ie = False
if 'HTTP_USER_AGENT' in request.META:
parsed_ua = ParseUserAgent(request.META['HTTP_USER_AGENT'])
parsed_ua = user_agent_parser.ParseUserAgent(request.META['HTTP_USER_AGENT'])
is_ie = parsed_ua['family'] == 'IE'

csp_header = 'Content-Security-Policy'
Expand Down Expand Up @@ -1315,3 +1315,56 @@ def process_request(self, request):
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
the `Referer` header. Use REFERRER_POLICY option in settings file
with the following values:
- ``no-referrer``
- ``no-referrer-when-downgrade``
- ``origin``
- ``origin-when-cross-origin``
- ``same-origin`` (*default*)
- ``strict-origin``
- ``strict-origin-when-cross-origin``
- ``unsafe-url``
- ``off``
Reference:
- `Referrer-Policy from Mozilla Developer Network
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy>`
"""

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' ]

DEFAULT = 'same-origin'

def load_setting(self, setting, value):
if not value:
self.option = self.DEFAULT
return

value = value.lower()

if value in self.OPTIONS:
self.option = value
return

raise ImproperlyConfigured(
self.__class__.__name__ + " invalid option for REFERRER_POLICY."
)

def process_response(self, request, response):
"""
Add Referrer-Policy to the reponse header.
"""
if self.option != 'off':
header = self.option
response['Referrer-Policy'] = header
return response
73 changes: 40 additions & 33 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from distutils.core import Command
from setuptools import setup

with open(os.path.join(os.path.dirname(__file__), 'README.md')) as f:
with open(os.path.join(os.path.dirname(__file__), "README.md")) as f:
readme = f.read()


Expand All @@ -20,37 +20,44 @@ def finalize_options(self):
pass

def run(self):
errno = subprocess.call([sys.executable, 'testing/manage.py', 'test'])
errno = subprocess.call([sys.executable, "testing/manage.py", "test"])
raise SystemExit(errno)

setup(name="django-security",
description='A collection of tools to help secure a Django project.',
long_description=readme,
long_description_content_type='text/markdown',
maintainer="SD Elements",
maintainer_email="[email protected]",
version="0.13.2",
packages=["security", "security.south_migrations",
"security.migrations", "security.auth_throttling"],
url='https://github.com/sdelements/django-security',
classifiers=[
'Framework :: Django',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.0',
'Environment :: Web Environment',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'License :: OSI Approved :: BSD License',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Security',
],
install_requires=[
'django>=1.11',
'ua_parser>=0.7.1',
'python-dateutil==2.8.1',
],
cmdclass={'test': Test})

setup(
name="django-security",
description="A collection of tools to help secure a Django project.",
long_description=readme,
long_description_content_type="text/markdown",
maintainer="SD Elements",
maintainer_email="[email protected]",
version="0.13.2",
packages=[
"security",
"security.south_migrations",
"security.migrations",
"security.auth_throttling",
],
url="https://github.com/sdelements/django-security",
classifiers=[
"Framework :: Django",
"Framework :: Django :: 1.11",
"Framework :: Django :: 2.2",
"Framework :: Django :: 3.0",
"Environment :: Web Environment",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"License :: OSI Approved :: BSD License",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Security",
],
install_requires=[
"django>=1.11",
"ua_parser>=0.7.1",
"python-dateutil==2.8.1",
],
cmdclass={"test": Test},
)
1 change: 1 addition & 0 deletions testing/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
'security.middleware.MandatoryPasswordChangeMiddleware',
'security.middleware.NoConfidentialCachingMiddleware',
'security.auth_throttling.Middleware',
'security.middleware.ReferrerPolicyMiddleware',
)

ROOT_URLCONF = 'testing.urls'
Expand Down
84 changes: 65 additions & 19 deletions testing/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from security.middleware import (
BaseMiddleware, ContentSecurityPolicyMiddleware, DoNotTrackMiddleware,
SessionExpiryPolicyMiddleware, MandatoryPasswordChangeMiddleware,
XssProtectMiddleware, XFrameOptionsMiddleware,
XssProtectMiddleware, XFrameOptionsMiddleware, ReferrerPolicyMiddleware
)
from security.models import PasswordExpiry
from security.password_expiry import never_expire_password
Expand Down Expand Up @@ -1064,24 +1064,70 @@ def test_DNT_echo_default(self):
self.dnt.process_response(self.request, self.response)
self.assertNotIn('DNT', self.response)

class ReferrerPolicyTests(TestCase):

@override_settings(MIDDLEWARE=(
'security.middleware.ClearSiteDataMiddleware',
))
class ClearSiteDataMiddlewareTests(TestCase):
def test_request_that_matches_the_whitelist_with_default_directives(self):
response = self.client.get('/home/')
self.assertEqual(response['Clear-Site-Data'], '"cookies", "storage"')
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)

def test_request_that_misses_the_whitelist(self):
response = self.client.get('/test1/')
self.assertNotIn("Clear-Site-Data", response)
def test_default_setting(self):
with self.settings(REFERRER_POLICY=None):
response = self.client.get('/accounts/login/')
self.assertEqual(response['Referrer-Policy'], 'same-origin')

@override_settings(CLEAR_SITE_DATA_DIRECTIVES=(
'cache', 'cookies', 'executionContexts', '*'
))
def test_request_that_matches_the_whitelist_with_custom_directives(self):
response = self.client.get('/home/')
self.assertEqual(
response['Clear-Site-Data'],
'"cache", "cookies", "executionContexts", "*"')
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')

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')

def test_origin_setting(self):
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')

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')

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')

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')

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')

def test_off_setting(self):
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',
)

0 comments on commit 8590edc

Please sign in to comment.