mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-13 22:45:05 +02:00
Add test that login is required for almost every URL
This commit is contained in:
parent
2e7644bdc3
commit
85a8b108de
1 changed files with 158 additions and 0 deletions
158
src/registrar/tests/test_url_auth.py
Normal file
158
src/registrar/tests/test_url_auth.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
"""Test that almost all URLs require authentication.
|
||||
|
||||
This uses deep Django URLConf pattern magic and was shamelessly lifted from
|
||||
https://github.com/18F/tock/blob/main/tock/tock/tests/test_url_auth.py
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse, URLPattern
|
||||
from django.urls.resolvers import URLResolver
|
||||
|
||||
import registrar.config.urls
|
||||
|
||||
from .common import less_console_noise
|
||||
|
||||
# When a URLconf pattern contains named capture groups, we'll use this
|
||||
# dictionary to retrieve a sample value for it, which will be included
|
||||
# in the sample URLs we generate, when attempting to perform a GET
|
||||
# request on the view.
|
||||
SAMPLE_KWARGS = {
|
||||
"app_label": "registrar",
|
||||
"pk": "1",
|
||||
"id": "1",
|
||||
"content_type_id": "2",
|
||||
"object_id": "3",
|
||||
"domain": "whitehouse.gov",
|
||||
}
|
||||
|
||||
# Our test suite will ignore some namespaces.
|
||||
IGNORE_NAMESPACES = [
|
||||
# The Django Debug Toolbar (DJDT) ends up in the URL config but it's always
|
||||
# disabled in production, so don't worry about it.
|
||||
"djdt"
|
||||
]
|
||||
|
||||
# In general, we don't want to have any unnamed views, because that makes it
|
||||
# impossible to generate sample URLs that point at them. We'll make exceptions
|
||||
# for some namespaces that we don't have control over, though.
|
||||
NAMESPACES_WITH_UNNAMED_VIEWS = ["admin", None]
|
||||
|
||||
|
||||
def iter_patterns(urlconf, patterns=None, namespace=None):
|
||||
"""
|
||||
Iterate through all patterns in the given Django URLconf. Yields
|
||||
`(viewname, route)` tuples, where `viewname` is the fully-qualified view name
|
||||
(including its namespace, if any), and `route` is a regular expression that
|
||||
corresponds to the part of the pattern that contains any capturing groups.
|
||||
"""
|
||||
if patterns is None:
|
||||
patterns = urlconf.urlpatterns
|
||||
for pattern in patterns:
|
||||
# Resolve if it's a route or an include
|
||||
if isinstance(pattern, URLPattern):
|
||||
viewname = pattern.name
|
||||
if viewname is None and namespace not in NAMESPACES_WITH_UNNAMED_VIEWS:
|
||||
raise AssertionError(
|
||||
f"namespace {namespace} cannot contain unnamed views"
|
||||
)
|
||||
if namespace and viewname is not None:
|
||||
viewname = f"{namespace}:{viewname}"
|
||||
yield (viewname, pattern.pattern)
|
||||
elif isinstance(pattern, URLResolver):
|
||||
if len(pattern.default_kwargs.keys()) > 0:
|
||||
raise AssertionError("resolvers are not expected to have kwargs")
|
||||
if pattern.namespace and namespace is not None:
|
||||
raise AssertionError("nested namespaces are not currently supported")
|
||||
if pattern.namespace in IGNORE_NAMESPACES:
|
||||
continue
|
||||
yield from iter_patterns(
|
||||
urlconf, pattern.url_patterns, namespace or pattern.namespace
|
||||
)
|
||||
else:
|
||||
raise AssertionError("unknown pattern class")
|
||||
|
||||
|
||||
def iter_sample_urls(urlconf):
|
||||
"""
|
||||
Yields sample URLs for all entries in the given Django URLconf.
|
||||
This gets pretty deep into the muck of RoutePattern
|
||||
https://docs.djangoproject.com/en/2.1/_modules/django/urls/resolvers/
|
||||
"""
|
||||
|
||||
for viewname, route in iter_patterns(urlconf):
|
||||
if not viewname:
|
||||
continue
|
||||
if viewname == "auth_user_password_change":
|
||||
print(route)
|
||||
break
|
||||
named_groups = route.regex.groupindex.keys()
|
||||
kwargs = {}
|
||||
args = ()
|
||||
|
||||
for kwarg in named_groups:
|
||||
if kwarg not in SAMPLE_KWARGS:
|
||||
raise AssertionError(
|
||||
f'Sample value for {kwarg} in pattern "{route}" not found'
|
||||
)
|
||||
kwargs[kwarg] = SAMPLE_KWARGS[kwarg]
|
||||
|
||||
url = reverse(viewname, args=args, kwargs=kwargs)
|
||||
yield (viewname, url)
|
||||
|
||||
|
||||
class TestURLAuth(TestCase):
|
||||
"""
|
||||
Tests to ensure that most URLs in a Django URLconf are protected by
|
||||
authentication.
|
||||
"""
|
||||
|
||||
# We won't test that the following URLs are protected by auth.
|
||||
# Note that the trailing slash is wobbly depending on how the URL was defined.
|
||||
IGNORE_URLS = [
|
||||
# These are the OIDC auth endpoints that always need
|
||||
# to be public.
|
||||
"/openid/login/",
|
||||
"/openid/logout/",
|
||||
"/openid/callback",
|
||||
"/openid/callback/logout/",
|
||||
]
|
||||
|
||||
def assertURLIsProtectedByAuth(self, url):
|
||||
"""
|
||||
Make a GET request to the given URL, and ensure that it either redirects
|
||||
to login or denies access outright.
|
||||
"""
|
||||
|
||||
try:
|
||||
with less_console_noise():
|
||||
response = self.client.get(url)
|
||||
except Exception as e:
|
||||
# It'll be helpful to provide information on what URL was being
|
||||
# accessed at the time the exception occurred. Python 3 will
|
||||
# also include a full traceback of the original exception, so
|
||||
# we don't need to worry about hiding the original cause.
|
||||
raise AssertionError(f'Accessing {url} raised "{e}"', e)
|
||||
|
||||
code = response.status_code
|
||||
if code == 302:
|
||||
redirect = response["location"]
|
||||
self.assertRegex(
|
||||
redirect,
|
||||
r"^\/openid\/login",
|
||||
f"GET {url} should redirect to login or deny access, but instead "
|
||||
f"it redirects to {redirect}",
|
||||
)
|
||||
elif code == 401 or code == 403:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError(
|
||||
f"GET {url} returned HTTP {code}, but should redirect to login or "
|
||||
"deny access",
|
||||
)
|
||||
|
||||
def test_login_required_all_urls(self):
|
||||
"""All URLs redirect to the login view."""
|
||||
for viewname, url in iter_sample_urls(registrar.config.urls):
|
||||
if url not in self.IGNORE_URLS:
|
||||
with self.subTest(viewname=viewname):
|
||||
self.assertURLIsProtectedByAuth(url)
|
Loading…
Add table
Add a link
Reference in a new issue