Require explicit tagging on views taking query parameters

Require each view to declare which query parameters it wants, and filter
out any other parameters.

We have very few views that actually take query parameters, and random
additional query patterns will have no effect on the view. However, they
will break frontend caching (in making them look like different pages).

This will be extended into an implementation in the caching frontends as
well, btu it's needed in the backend to ensure that local testing will
have tbe same effect as the caches.
This commit is contained in:
Magnus Hagander
2021-02-22 10:43:59 +01:00
parent b23309c95d
commit 0724c08e40
4 changed files with 45 additions and 2 deletions

View File

@ -4,7 +4,7 @@ import django.contrib.auth.views as authviews
from django.http import HttpResponseRedirect, Http404, HttpResponse
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from pgweb.util.decorators import login_required, script_sources, frame_sources, content_sources
from pgweb.util.decorators import login_required, script_sources, frame_sources, content_sources, queryparams
from django.views.decorators.csrf import csrf_exempt
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
@ -535,6 +535,7 @@ def signup_complete(request):
@script_sources('https://www.gstatic.com/recaptcha/')
@frame_sources('https://www.google.com/')
@transaction.atomic
@queryparams('do_abort')
def signup_oauth(request):
if 'oauth_email' not in request.session \
or 'oauth_firstname' not in request.session \
@ -618,6 +619,7 @@ def signup_oauth(request):
####
# Community authentication endpoint
####
@queryparams('d', 'su')
def communityauth(request, siteid):
# Get whatever site the user is trying to log in to.
site = get_object_or_404(CommunityAuthSite, pk=siteid)
@ -742,6 +744,7 @@ def communityauth_logout(request, siteid):
@login_required
@queryparams('next')
def communityauth_consent(request, siteid):
org = get_object_or_404(CommunityAuthSite, id=siteid).org
if request.method == 'POST':
@ -776,6 +779,7 @@ def _encrypt_site_response(site, s):
)
@queryparams('s', 'e', 'n', 'u')
def communityauth_search(request, siteid):
# Perform a search for users. The response will be encrypted with the site
# key to prevent abuse, therefor we need the site.

View File

@ -3,7 +3,7 @@ from django.http import HttpResponseRedirect, Http404
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from pgweb.util.decorators import cache
from pgweb.util.decorators import cache, queryparams
import urllib.parse
import requests
@ -47,6 +47,7 @@ def generate_pagelinks(pagenum, totalpages, querystring):
@csrf_exempt
@queryparams('a', 'd', 'l', 'ln', 'm', 'p', 'q', 's', 'u')
@cache(minutes=30)
def search(request):
# Perform a general web search

View File

@ -24,6 +24,17 @@ def cache(days=0, hours=0, minutes=0, seconds=0):
return _cache
def queryparams(*args):
"""
Allow specified query parameters when calling function.
NOTE! Must be the "outermost" decorator!!!
"""
def _queryparams(fn):
fn.queryparams = args
return fn
return _queryparams
def allow_frames(fn):
def _allow_frames(request, *_args, **_kwargs):
resp = fn(request, *_args, **_kwargs)

View File

@ -1,4 +1,5 @@
from django.conf import settings
from django.http import QueryDict
from pgweb.util.templateloader import initialize_template_collection, get_all_templates
@ -76,3 +77,29 @@ class PgMiddleware(object):
response['X-XSS-Protection'] = "1; mode=block"
return response
def process_view(self, request, view_func, view_args, view_kwargs):
# Filter out any query parameters that are not explicitly allowed. We do the same thing in Varnish,
# and it's better to also do it in django if they show up here, so issues because of it are caught
# in local testing where there is no Varnish.
if not request.GET:
# If there are no parameters, just skip this whole process
return None
if request.path.startswith('/admin/'):
# django-admin uses it a lot and it's not for us to change
return None
allowed = getattr(view_func, 'queryparams', None)
if allowed:
# Filter the QueryDict for only the allowed parameters
result = request.GET.copy()
for k in request.GET.keys():
if k not in allowed:
del result[k]
result.mutable = False
request.GET = result
else:
request.GET = QueryDict()
return None