From a341fd8ebed30f0f7ece7bd50e3c4b5229947e3f Mon Sep 17 00:00:00 2001 From: smilerz Date: Sun, 28 Mar 2021 18:39:20 -0500 Subject: [PATCH] added CORS middleware --- cookbook/admin.py | 3 +- cookbook/serializer.py | 2 +- cookbook/static/js/bookmarklet.js | 42 +++++++++++++++------------- cookbook/templates/url_import.html | 2 +- cookbook/templatetags/custom_tags.py | 17 +++++++---- cookbook/views/api.py | 4 +-- cookbook/views/data.py | 4 +-- recipes/settings.py | 19 +++++++++++-- requirements.txt | 5 ++-- 9 files changed, 62 insertions(+), 36 deletions(-) diff --git a/cookbook/admin.py b/cookbook/admin.py index ed145623..d4cc533d 100644 --- a/cookbook/admin.py +++ b/cookbook/admin.py @@ -7,7 +7,7 @@ from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword, RecipeBook, RecipeBookEntry, RecipeImport, ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, Sync, SyncLog, Unit, UserPreference, - ViewLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation, + ViewLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation, ImportLog, TelegramBot, BookmarkletImport) @@ -229,6 +229,7 @@ class BookmarkletImportAdmin(admin.ModelAdmin): admin.site.register(BookmarkletImport, BookmarkletImportAdmin) + class TelegramBotAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'created_by',) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index d9ae6a50..9da066cf 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -476,7 +476,7 @@ class BookmarkletImportSerializer(serializers.ModelSerializer): class Meta: model = BookmarkletImport fields = ('id', 'url', 'html', 'created_by', 'created_at') - read_only_fields = ('created_by',) + read_only_fields = ('created_by', 'space') # Export/Import Serializers diff --git a/cookbook/static/js/bookmarklet.js b/cookbook/static/js/bookmarklet.js index 0d2e4f1b..f8a94725 100644 --- a/cookbook/static/js/bookmarklet.js +++ b/cookbook/static/js/bookmarklet.js @@ -18,28 +18,30 @@ } function initBookmarklet() { (window.bookmarkletTandoor = function() { - var recipe = document.documentElement.innerHTML - var form = document.createElement("form"); - var windowName = "ImportRecipe" - form.setAttribute("method", "post"); - form.setAttribute("action", localStorage.getItem('importURL')); - form.setAttribute("target",'importRecipe'); - var params = { 'recipe' : recipe,'url': window.location}; + let recipe = document.documentElement.innerHTML + let windowName = "ImportRecipe" + let url = localStorage.getItem('importURL') + let redirect = localStorage.getItem('redirectURL') + let token = localStorage.getItem('token') + let params = { 'html' : recipe,'url': window.location.href}; + console.log(window.location.href) - for (var i in params) { - if (params.hasOwnProperty(i)) { - var input = document.createElement('input'); - input.type = 'hidden'; - input.name = i; - input.value = params[i]; - form.appendChild(input); + const xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.setRequestHeader('Authorization', 'Token ' + token); + + // listen for `onload` event + xhr.onload = () => { + // process response + if (xhr.readyState == 4 && xhr.status == 201) { + // parse JSON data + console.log(JSON.parse(xhr.response)); + } else { + console.error('Error!'); } - } - document.body.appendChild(form); - window.open('', windowName); - form.target = windowName; - form.submit(); - document.body.removeChild(form); + }; + xhr.send(JSON.stringify(params)); } )(); } diff --git a/cookbook/templates/url_import.html b/cookbook/templates/url_import.html index 63a819bc..6dd3ec33 100644 --- a/cookbook/templates/url_import.html +++ b/cookbook/templates/url_import.html @@ -26,7 +26,7 @@

{% trans 'Import' %}

{% trans 'Bookmark Me!' %} diff --git a/cookbook/templatetags/custom_tags.py b/cookbook/templatetags/custom_tags.py index ab53919b..12e525bf 100644 --- a/cookbook/templatetags/custom_tags.py +++ b/cookbook/templatetags/custom_tags.py @@ -10,6 +10,7 @@ from django.db.models import Avg from django.templatetags.static import static from django.urls import NoReverseMatch, reverse from recipes import settings +from rest_framework.authtoken.models import Token from gettext import gettext as _ register = template.Library() @@ -111,20 +112,26 @@ def is_debug(): @register.simple_tag -def bookmarklet(host, secure): - if secure: +def bookmarklet(request): + if request.is_secure(): prefix = "https://" else: prefix = "http://" + server = prefix + request.get_host() + # TODO is it safe to store the token in clear text in a bookmark? + if (api_token := Token.objects.filter(user=request.user).first()) is None: + api_token = Token.objects.create(user=request.user) bookmark = "javascript: \ (function(){ \ if(window.bookmarkletTandoor!==undefined){ \ bookmarkletTandoor(); \ } else { \ - localStorage.setItem('importURL', '" + prefix + host + reverse('api_bookmarklet') + "'); \ + localStorage.setItem('importURL', '" + server + reverse('api:bookmarkletimport-list') + "'); \ + localStorage.setItem('redirectURL', '" + server + reverse('data_import_url') + "'); \ + localStorage.setItem('token', '" + api_token.__str__() + "'); \ document.body.appendChild(document.createElement(\'script\')).src=\'" \ - + prefix + host + static('js/bookmarklet.js') + "? \ + + server + static('js/bookmarklet.js') + "? \ r=\'+Math.floor(Math.random()*999999999);}})();" - return re.sub(r"[\n\t]*", "", bookmark) + return re.sub(r"[\n\t\s]*", "", bookmark) diff --git a/cookbook/views/api.py b/cookbook/views/api.py index b7020dc5..f56d216a 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -51,8 +51,8 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer, StorageSerializer, SyncLogSerializer, SyncSerializer, UnitSerializer, UserNameSerializer, UserPreferenceSerializer, - ViewLogSerializer, CookLogSerializer, - RecipeBookEntrySerializer, RecipeOverviewSerializer, + ViewLogSerializer, CookLogSerializer, + RecipeBookEntrySerializer, RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer, BookmarkletImportSerializer) from recipes.settings import DEMO from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode diff --git a/cookbook/views/data.py b/cookbook/views/data.py index 1820ac7c..77252b1a 100644 --- a/cookbook/views/data.py +++ b/cookbook/views/data.py @@ -94,8 +94,8 @@ def batch_edit(request): 'Batch edit done. %(count)d recipe was updated.', 'Batch edit done. %(count)d Recipes where updated.', count) % { - 'count': count, - } + 'count': count, + } messages.add_message(request, messages.SUCCESS, msg) return redirect('data_batch_edit') diff --git a/recipes/settings.py b/recipes/settings.py index 85d56381..f2f9aa8b 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -14,6 +14,7 @@ import os import random import string +from corsheaders.defaults import default_headers from django.contrib import messages from django.utils.translation import gettext_lazy as _ from dotenv import load_dotenv @@ -76,6 +77,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'django.contrib.postgres', 'django_tables2', + 'corsheaders', 'django_filters', 'crispy_forms', 'emoji_picker', @@ -97,6 +99,7 @@ SOCIALACCOUNT_PROVIDERS = ast.literal_eval( os.getenv('SOCIALACCOUNT_PROVIDERS') if os.getenv('SOCIALACCOUNT_PROVIDERS') else '{}') MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -249,5 +252,17 @@ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' TEST_RUNNER = "cookbook.helper.CustomTestRunner.CustomTestRunner" -# future versions of django will make undeclared default django.db.models.BigAutoField which will force migrations on all models -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' + +# settings for cross site origin (CORS) +# all origins allowed to support bookmarklet +# all of this may or may not work with nginx or other web servers +# TODO make this user configureable - enable or disable bookmarklets +# TODO since token auth is enabled - this all should be https by default +CORS_ORIGIN_ALLOW_ALL = True + +# django rest_framework requires authentication header +#CORS_ALLOW_HEADERS = list(default_headers) + ['authentication',] + +# enable CORS only for bookmarklet api and only for posts, get and options +CORS_URLS_REGEX = r'^/api/bookmarklet-import.*$' +CORS_ALLOW_METHODS = ['GET', 'OPTIONS', 'POST'] diff --git a/requirements.txt b/requirements.txt index 93bea96f..3e49f29a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,5 +33,6 @@ django-js-reverse==0.9.1 django-allauth==0.44.0 recipe-scrapers==12.2.1 django-scopes==1.2.0 -pytest==6.2.3 -pytest-django==4.2.0 +pytest==6.2.2 +pytest-django==4.1.0 +django-cors-headers==3.7.0