added CORS middleware

This commit is contained in:
smilerz 2021-03-28 18:39:20 -05:00
parent 9a62b6e4e7
commit a341fd8ebe
9 changed files with 62 additions and 36 deletions

View File

@ -7,7 +7,7 @@ from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword,
RecipeBook, RecipeBookEntry, RecipeImport, ShareLink, RecipeBook, RecipeBookEntry, RecipeImport, ShareLink,
ShoppingList, ShoppingListEntry, ShoppingListRecipe, ShoppingList, ShoppingListEntry, ShoppingListRecipe,
Space, Step, Storage, Sync, SyncLog, Unit, UserPreference, Space, Step, Storage, Sync, SyncLog, Unit, UserPreference,
ViewLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation, ViewLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation,
ImportLog, TelegramBot, BookmarkletImport) ImportLog, TelegramBot, BookmarkletImport)
@ -229,6 +229,7 @@ class BookmarkletImportAdmin(admin.ModelAdmin):
admin.site.register(BookmarkletImport, BookmarkletImportAdmin) admin.site.register(BookmarkletImport, BookmarkletImportAdmin)
class TelegramBotAdmin(admin.ModelAdmin): class TelegramBotAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'created_by',) list_display = ('id', 'name', 'created_by',)

View File

@ -476,7 +476,7 @@ class BookmarkletImportSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = BookmarkletImport model = BookmarkletImport
fields = ('id', 'url', 'html', 'created_by', 'created_at') fields = ('id', 'url', 'html', 'created_by', 'created_at')
read_only_fields = ('created_by',) read_only_fields = ('created_by', 'space')
# Export/Import Serializers # Export/Import Serializers

View File

@ -18,28 +18,30 @@
} }
function initBookmarklet() { function initBookmarklet() {
(window.bookmarkletTandoor = function() { (window.bookmarkletTandoor = function() {
var recipe = document.documentElement.innerHTML let recipe = document.documentElement.innerHTML
var form = document.createElement("form"); let windowName = "ImportRecipe"
var windowName = "ImportRecipe" let url = localStorage.getItem('importURL')
form.setAttribute("method", "post"); let redirect = localStorage.getItem('redirectURL')
form.setAttribute("action", localStorage.getItem('importURL')); let token = localStorage.getItem('token')
form.setAttribute("target",'importRecipe'); let params = { 'html' : recipe,'url': window.location.href};
var params = { 'recipe' : recipe,'url': window.location}; console.log(window.location.href)
for (var i in params) { const xhr = new XMLHttpRequest();
if (params.hasOwnProperty(i)) { xhr.open('POST', url, true);
var input = document.createElement('input'); xhr.setRequestHeader('Content-Type', 'application/json');
input.type = 'hidden'; xhr.setRequestHeader('Authorization', 'Token ' + token);
input.name = i;
input.value = params[i]; // listen for `onload` event
form.appendChild(input); 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); xhr.send(JSON.stringify(params));
window.open('', windowName);
form.target = windowName;
form.submit();
document.body.removeChild(form);
} }
)(); )();
} }

View File

@ -26,7 +26,7 @@
<h2> {% trans 'Import' %}</h2> <h2> {% trans 'Import' %}</h2>
<a class="btn btn-outline-info btn-sm" <a class="btn btn-outline-info btn-sm"
style="height:50%" style="height:50%"
href="{% bookmarklet request.get_host request.is_secure %}" href="{% bookmarklet request %}"
title="{% trans 'Drag me to your bookmarks to import recipes from anywhere' %}"> title="{% trans 'Drag me to your bookmarks to import recipes from anywhere' %}">
<img src="{% static 'assets/favicon-16x16.png' %}">{% trans 'Bookmark Me!' %} </a> <img src="{% static 'assets/favicon-16x16.png' %}">{% trans 'Bookmark Me!' %} </a>
</div> </div>

View File

@ -10,6 +10,7 @@ from django.db.models import Avg
from django.templatetags.static import static from django.templatetags.static import static
from django.urls import NoReverseMatch, reverse from django.urls import NoReverseMatch, reverse
from recipes import settings from recipes import settings
from rest_framework.authtoken.models import Token
from gettext import gettext as _ from gettext import gettext as _
register = template.Library() register = template.Library()
@ -111,20 +112,26 @@ def is_debug():
@register.simple_tag @register.simple_tag
def bookmarklet(host, secure): def bookmarklet(request):
if secure: if request.is_secure():
prefix = "https://" prefix = "https://"
else: else:
prefix = "http://" 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: \ bookmark = "javascript: \
(function(){ \ (function(){ \
if(window.bookmarkletTandoor!==undefined){ \ if(window.bookmarkletTandoor!==undefined){ \
bookmarkletTandoor(); \ bookmarkletTandoor(); \
} else { \ } 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=\'" \ document.body.appendChild(document.createElement(\'script\')).src=\'" \
+ prefix + host + static('js/bookmarklet.js') + "? \ + server + static('js/bookmarklet.js') + "? \
r=\'+Math.floor(Math.random()*999999999);}})();" r=\'+Math.floor(Math.random()*999999999);}})();"
return re.sub(r"[\n\t]*", "", bookmark) return re.sub(r"[\n\t\s]*", "", bookmark)

View File

@ -51,8 +51,8 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
StorageSerializer, SyncLogSerializer, StorageSerializer, SyncLogSerializer,
SyncSerializer, UnitSerializer, SyncSerializer, UnitSerializer,
UserNameSerializer, UserPreferenceSerializer, UserNameSerializer, UserPreferenceSerializer,
ViewLogSerializer, CookLogSerializer, ViewLogSerializer, CookLogSerializer,
RecipeBookEntrySerializer, RecipeOverviewSerializer, RecipeBookEntrySerializer, RecipeOverviewSerializer,
SupermarketSerializer, ImportLogSerializer, BookmarkletImportSerializer) SupermarketSerializer, ImportLogSerializer, BookmarkletImportSerializer)
from recipes.settings import DEMO from recipes.settings import DEMO
from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode

View File

@ -94,8 +94,8 @@ def batch_edit(request):
'Batch edit done. %(count)d recipe was updated.', 'Batch edit done. %(count)d recipe was updated.',
'Batch edit done. %(count)d Recipes where updated.', 'Batch edit done. %(count)d Recipes where updated.',
count) % { count) % {
'count': count, 'count': count,
} }
messages.add_message(request, messages.SUCCESS, msg) messages.add_message(request, messages.SUCCESS, msg)
return redirect('data_batch_edit') return redirect('data_batch_edit')

View File

@ -14,6 +14,7 @@ import os
import random import random
import string import string
from corsheaders.defaults import default_headers
from django.contrib import messages from django.contrib import messages
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dotenv import load_dotenv from dotenv import load_dotenv
@ -76,6 +77,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.postgres', 'django.contrib.postgres',
'django_tables2', 'django_tables2',
'corsheaders',
'django_filters', 'django_filters',
'crispy_forms', 'crispy_forms',
'emoji_picker', 'emoji_picker',
@ -97,6 +99,7 @@ SOCIALACCOUNT_PROVIDERS = ast.literal_eval(
os.getenv('SOCIALACCOUNT_PROVIDERS') if os.getenv('SOCIALACCOUNT_PROVIDERS') else '{}') os.getenv('SOCIALACCOUNT_PROVIDERS') if os.getenv('SOCIALACCOUNT_PROVIDERS') else '{}')
MIDDLEWARE = [ MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
@ -249,5 +252,17 @@ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
TEST_RUNNER = "cookbook.helper.CustomTestRunner.CustomTestRunner" 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']

View File

@ -33,5 +33,6 @@ django-js-reverse==0.9.1
django-allauth==0.44.0 django-allauth==0.44.0
recipe-scrapers==12.2.1 recipe-scrapers==12.2.1
django-scopes==1.2.0 django-scopes==1.2.0
pytest==6.2.3 pytest==6.2.2
pytest-django==4.2.0 pytest-django==4.1.0
django-cors-headers==3.7.0