Commit 1183f6ce by Toby Lawrence

Add the ability to exclude files from URL canonicalization.

To be able to more easily deal with same-origin issues, due to some course assets being loaded in an iframe, we're adding the ability to specify file extensions that should be excluded from URL canonicalization.  The default value is simply 'html', which means we won't rewrite asset links that point to files ending in .html.  Thus, they'll be loaded from the same origin as the parent page, and voila, no SOP issues.
parent f43e1961
......@@ -5,7 +5,7 @@ from django.contrib.staticfiles.storage import staticfiles_storage
from django.contrib.staticfiles import finders
from django.conf import settings
from static_replace.models import AssetBaseUrlConfig
from static_replace.models import AssetBaseUrlConfig, AssetExcludedExtensionsConfig
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum
from xmodule.contentstore.content import StaticContent
......@@ -182,7 +182,8 @@ def replace_static_urls(text, data_directory=None, course_id=None, static_asset_
# if not, then assume it's courseware specific content and then look in the
# Mongo-backed database
base_url = AssetBaseUrlConfig.get_base_url()
url = StaticContent.get_canonicalized_asset_path(course_id, rest, base_url)
excluded_exts = AssetExcludedExtensionsConfig.get_excluded_extensions()
url = StaticContent.get_canonicalized_asset_path(course_id, rest, base_url, excluded_exts)
if AssetLocator.CANONICAL_NAMESPACE in url:
url = url.replace('block@', 'block/', 1)
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('static_replace', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='AssetExcludedExtensionsConfig',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
('excluded_extensions', models.TextField(default=b'html', help_text=b'The file extensions to exclude from canonicalization. No leading period required. Values should be space separated i.e. "html svg css"')),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
],
),
]
......@@ -27,3 +27,27 @@ class AssetBaseUrlConfig(ConfigurationModel):
def __unicode__(self):
return unicode(repr(self))
class AssetExcludedExtensionsConfig(ConfigurationModel):
"""Configuration for the the excluded file extensions when canonicalizing static asset paths."""
class Meta(object):
app_label = 'static_replace'
excluded_extensions = TextField(
default='html',
help_text='The file extensions to exclude from canonicalization. No leading period required. ' +
'Values should be space separated i.e. "html svg css"'
)
@classmethod
def get_excluded_extensions(cls):
"""Gets the excluded file extensions when canonicalizing static asset paths"""
return cls.current().excluded_extensions.split()
def __repr__(self):
return '<AssetExcludedExtensionsConfig(extensions={})>'.format(self.get_excluded_extensions().split())
def __unicode__(self):
return unicode(repr(self))
......@@ -168,7 +168,7 @@ class StaticContent(object):
return StaticContent.compute_location(course_key, path)
@staticmethod
def get_canonicalized_asset_path(course_key, path, base_url):
def get_canonicalized_asset_path(course_key, path, base_url, excluded_exts):
"""
Returns a fully-qualified path to a piece of static content.
......@@ -199,17 +199,24 @@ class StaticContent(object):
# If we can't find the item, just treat it as if it's locked.
serve_from_cdn = False
# See if this is an allowed file extension to serve. Some files aren't served through the
# CDN in order to avoid same-origin policy/CORS-related issues.
for excluded_ext in excluded_exts:
if relative_path.lower().endswith(excluded_ext.lower()):
serve_from_cdn = False
break
# Update any query parameter values that have asset paths in them. This is for assets that
# require their own after-the-fact values, like a Flash file that needs the path of a config
# file passed to it e.g. /static/visualization.swf?configFile=/static/visualization.xml
query_params = parse_qsl(query_string)
updated_query_params = []
for query_name, query_value in query_params:
if query_value.startswith("/static/"):
new_query_value = StaticContent.get_canonicalized_asset_path(course_key, query_value, base_url)
updated_query_params.append((query_name, new_query_value))
for query_name, query_val in query_params:
if query_val.startswith("/static/"):
new_val = StaticContent.get_canonicalized_asset_path(course_key, query_val, base_url, excluded_exts)
updated_query_params.append((query_name, new_val))
else:
updated_query_params.append((query_name, query_value))
updated_query_params.append((query_name, query_val))
serialized_asset_key = StaticContent.serialize_asset_key_with_slash(asset_key)
base_url = base_url if serve_from_cdn else ''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment