From 1d4be082cf61d4835d72347be019c145259ebb15 Mon Sep 17 00:00:00 2001 From: Danielle McLean Date: Tue, 3 Jul 2018 09:19:50 +1000 Subject: [PATCH 1/2] Refactor the 'find an entry based on a URL' behaviour into a utility function --- entries/from_url.py | 35 +++++++++++++++++++++++++++++++++++ micropub/views/delete.py | 28 ++++------------------------ 2 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 entries/from_url.py diff --git a/entries/from_url.py b/entries/from_url.py new file mode 100644 index 0000000..5572963 --- /dev/null +++ b/entries/from_url.py @@ -0,0 +1,35 @@ +from typing import Union +from urllib.parse import urlparse + +from django.contrib.sites.models import Site +from django.http import HttpResponse +from django.urls import resolve, Resolver404 +from micropub.views import error + +from .models import Entry + + +def from_url(url: str) -> Union[Entry, HttpResponse]: + domain = Site.objects.get_current().domain + if not url: + return error.bad_req('url parameter required') + if '//' not in url: + url = '//' + url + parts = urlparse(url, scheme='https') + if parts.scheme not in ('http', 'https') or parts.netloc != domain: + return error.bad_req('url does not point to this site') + + try: + match = resolve(parts.path) + except Resolver404: + return error.bad_req('url does not point to a valid page on this site') + + if match.view_name != 'entries:entry': + return error.bad_req('url does not point to an entry on this site') + + try: + entry = Entry.objects.get(pk=match.kwargs['id']) + except Entry.DoesNotExist: + return error.bad_req('url does not point to an existing entry') + + return entry diff --git a/micropub/views/delete.py b/micropub/views/delete.py index 5daa696..9182c1e 100644 --- a/micropub/views/delete.py +++ b/micropub/views/delete.py @@ -1,10 +1,8 @@ from django.http import HttpResponse -from django.urls import resolve, Resolver404 -from urllib.parse import urlparse from ronkyuu import webmention +from entries.from_url import from_url from entries.jobs import ping_hub, send_mentions -from entries.models import Entry from . import error @@ -18,27 +16,9 @@ def delete(request): if request.content_type not in normalise: return error.unsupported_type(request.content_type) url = normalise[request.content_type](request) - if not url: - return error.bad_req('url parameter required') - - if '//' not in url: - url = '//' + url - url = urlparse(url, scheme='https') - - if url.scheme not in ('http', 'https') or url.netloc != request.site.domain: - return error.bad_req('url does not point to this site') - try: - match = resolve(url.path) - except Resolver404: - return error.bad_req('url does not point to a valid page on this site') - - if match.view_name != 'entries:entry': - return error.bad_req('url does not point to an entry on this site') - - try: - entry = Entry.objects.get(pk=match.kwargs['id']) - except Entry.DoesNotExist: - return error.bad_req('url does not point to an existing entry') + entry = from_url(url) + if isinstance(entry, HttpResponse): + return entry if entry.author != request.token.user: return error.forbid('entry belongs to another user') From 7d17a92793c0c9a191f785bafce6986232957f49 Mon Sep 17 00:00:00 2001 From: Danielle McLean Date: Tue, 3 Jul 2018 09:41:00 +1000 Subject: [PATCH 2/2] Introduce a middleware that allows for HttpResponses to be thrown from inner utility functions, to avoid boilerplate in views --- entries/from_url.py | 14 +++++++------- lemoncurry/middleware.py | 14 ++++++++++++++ lemoncurry/settings/base.py | 1 + micropub/views/delete.py | 2 -- 4 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 lemoncurry/middleware.py diff --git a/entries/from_url.py b/entries/from_url.py index 5572963..b19475f 100644 --- a/entries/from_url.py +++ b/entries/from_url.py @@ -1,35 +1,35 @@ -from typing import Union from urllib.parse import urlparse from django.contrib.sites.models import Site from django.http import HttpResponse from django.urls import resolve, Resolver404 from micropub.views import error +from lemoncurry.middleware import ResponseException from .models import Entry -def from_url(url: str) -> Union[Entry, HttpResponse]: +def from_url(url: str) -> Entry: domain = Site.objects.get_current().domain if not url: - return error.bad_req('url parameter required') + raise ResponseException(error.bad_req('url parameter required')) if '//' not in url: url = '//' + url parts = urlparse(url, scheme='https') if parts.scheme not in ('http', 'https') or parts.netloc != domain: - return error.bad_req('url does not point to this site') + raise ResponseException(error.bad_req('url does not point to this site')) try: match = resolve(parts.path) except Resolver404: - return error.bad_req('url does not point to a valid page on this site') + raise ResponseException(error.bad_req('url does not point to a valid page on this site')) if match.view_name != 'entries:entry': - return error.bad_req('url does not point to an entry on this site') + raise ResponseException(error.bad_req('url does not point to an entry on this site')) try: entry = Entry.objects.get(pk=match.kwargs['id']) except Entry.DoesNotExist: - return error.bad_req('url does not point to an existing entry') + raise ResponseException(error.bad_req('url does not point to an existing entry')) return entry diff --git a/lemoncurry/middleware.py b/lemoncurry/middleware.py new file mode 100644 index 0000000..02221bc --- /dev/null +++ b/lemoncurry/middleware.py @@ -0,0 +1,14 @@ +from django.http import HttpRequest, HttpResponse +from django.utils.deprecation import MiddlewareMixin + + +class ResponseException(Exception): + def __init__(self, response: HttpResponse) -> None: + self.response = response + + +class ResponseExceptionMiddleware(MiddlewareMixin): + def process_exception(self, request: HttpRequest, exception: Exception) -> HttpResponse: + if isinstance(exception, ResponseException): + return exception.response + raise exception diff --git a/lemoncurry/settings/base.py b/lemoncurry/settings/base.py index 38ccee7..efc25ae 100644 --- a/lemoncurry/settings/base.py +++ b/lemoncurry/settings/base.py @@ -118,6 +118,7 @@ MIDDLEWARE = [ 'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.sites.middleware.CurrentSiteMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'lemoncurry.middleware.ResponseExceptionMiddleware', ] ROOT_URLCONF = 'lemoncurry.urls' diff --git a/micropub/views/delete.py b/micropub/views/delete.py index 9182c1e..4fc1f3e 100644 --- a/micropub/views/delete.py +++ b/micropub/views/delete.py @@ -17,8 +17,6 @@ def delete(request): return error.unsupported_type(request.content_type) url = normalise[request.content_type](request) entry = from_url(url) - if isinstance(entry, HttpResponse): - return entry if entry.author != request.token.user: return error.forbid('entry belongs to another user')