Skip to content

Commit

Permalink
Add CSRF token into meta tag
Browse files Browse the repository at this point in the history
  • Loading branch information
bobisjan committed Jun 2, 2015
1 parent 4ec4001 commit 5d8070c
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 4 deletions.
6 changes: 5 additions & 1 deletion DESCRIPTION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ Usage
index(r'^', 'my-app', adapter),
]
The provided ``regex`` is used to set router’s `rootURL`_ by `replacing`_ pregenerated `baseURL`_ environment configuration at index file. Note that `storeConfigInMeta`_ must be set to ``true``, otherwise an exception is raised. If ``base`` tag is present in index file, then value of ``href`` attribute will be replaced too.
The provided ``regex`` is used to set router’s `rootURL`_ by `replacing`_ pregenerated `baseURL`_ environment configuration at index file.

Note that `storeConfigInMeta`_ must be set to ``true``, otherwise an exception is raised. If ``base`` tag is present in index file, then value of ``href`` attribute will be replaced too.

If CSRF protection is enabled, then ``meta`` tag named ``X-CSRFToken`` with generated token will be provided.

All adapter’s keyword arguments will be passed into the `StrictRedis`_ object on initialization.

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ A Django app to serve [Ember](http://emberjs.com) index files deployed with [emb
]
```

_The provided `regex` is used to set router's [rootURL](http://emberjs.com/api/classes/Ember.Router.html#property_rootURL) by [replacing](https://github.com/bobisjan/django-ember-index/blob/master/ember_index/utils.py#L1) pregenerated [baseURL](https://github.com/ember-cli/ember-cli/blob/18d377b264859548f41aba6c3ea2015b90978068/blueprints/app/files/config/environment.js#L7) environment configuration at index file. Note that [storeConfigInMeta](https://github.com/ember-cli/ember-cli/blob/master/lib/broccoli/ember-app.js#L141) must be set to `true`, otherwise an exception is raised. If `base` tag is present in index file, then value of `href` attribute will be replaced too._
_The provided `regex` is used to set router's [rootURL](http://emberjs.com/api/classes/Ember.Router.html#property_rootURL) by [replacing](https://github.com/bobisjan/django-ember-index/blob/master/ember_index/utils.py#L1) pregenerated [baseURL](https://github.com/ember-cli/ember-cli/blob/18d377b264859548f41aba6c3ea2015b90978068/blueprints/app/files/config/environment.js#L7) environment configuration at index file._

_Note that [storeConfigInMeta](https://github.com/ember-cli/ember-cli/blob/master/lib/broccoli/ember-app.js#L141) must be set to `true`, otherwise an exception is raised. If `base` tag is present in index file, then value of `href` attribute will be replaced too._

_If CSRF protection is enabled, then `meta` tag named `X-CSRFToken` with generated token will be provided._

_All adapter's keyword arguments will be passed into the [StrictRedis](https://redis-py.readthedocs.org/en/latest/#redis.StrictRedis) object on initialization._

Expand Down
41 changes: 40 additions & 1 deletion ember_index/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.conf import settings
from django.http import HttpResponse, HttpResponseNotFound
from django.views.generic import View

Expand All @@ -16,6 +17,7 @@ class IndexView(View):
manifest (string): A name of Ember application.
adapter (object): An adapter for index provider.
regex (string): A regex from Ember application is served.
request: (django.http.HttpRequest): An instance of the current request.
'''

Expand All @@ -25,6 +27,8 @@ class IndexView(View):
adapter = None
regex = None

request = None

def get(self, request, revision='current'):
'''Handle `GET` request for index of Ember application.
Expand All @@ -38,6 +42,8 @@ def get(self, request, revision='current'):
If no index is found then an instance of `django.http.HttpResponseNotFound` is returned.
'''
self.request = request

index_key = self.index_key(revision)
index = self.adapter.index_for(index_key)

Expand Down Expand Up @@ -104,6 +110,8 @@ def process_index(self, index, revision):
By default it replaces `baseURL` in environment configuration.
If CSRF protection is enabled, then `meta` tag with CSRF token will be provided.
Keyword arguments:
index (string): An index for requested revision.
revision (string): A requested revision.
Expand All @@ -112,8 +120,39 @@ def process_index(self, index, revision):
A processed index.
'''
return replace_base_url(index, revision, self.path)
index = replace_base_url(index, revision, self.path)

if self.is_csrf_protection_enabled:
index = self.append_meta_with_csrf_token(index)

return index

@property
def path(self):
return path_for(self.regex)

@property
def is_csrf_protection_enabled(self):
return 'django.middleware.csrf.CsrfViewMiddleware' in settings.MIDDLEWARE_CLASSES

@property
def csrf_meta_name(self):
return 'X-CSRFToken' # HTTP_X_CSRFTOKEN

def append_meta_with_csrf_token(self, index):
'''Return index CSRF token in meta tag named `X-CSRFToken`.
Keyword arguments:
index (string): An index for requested revision.
Returns:
An index with CSRF token.
'''
from django.middleware.csrf import get_token

start = index.index('</head>')
meta = '<meta name="{0}" content="{1}" />'
meta = meta.format(self.csrf_meta_name, get_token(self.request))

return index[:start] + meta + index[start:]
21 changes: 20 additions & 1 deletion tests/acceptance/redis/get.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import re

from . import RedisTestCase


class GetIndexTestCase(RedisTestCase):

csrf_pattern = re.compile(r'<meta name="X-CSRFToken" content="\w{32}" />')

manifests = {
'my-app': {
'base_url': '/',
Expand Down Expand Up @@ -50,7 +54,17 @@ def test_should_redirect_to_current_index(self):
self.assertRedirect('/other/r/current/abc/def', 'other-app')
self.assertRedirect('/other/r/current/#/abc/def', 'other-app')

def assertGet(self, url, manifest, revision, current=False):
def test_should_get_without_csrf_token(self):
with self.settings(MIDDLEWARE_CLASSES=[]):
self.assertGet('/r/d696248/', 'my-app', 'd696248', csrf=False)
self.assertGet('/r/d696248/abc/def', 'my-app', 'd696248', csrf=False)
self.assertGet('/r/d696248/#/abc/def', 'my-app', 'd696248', csrf=False)

self.assertGet('/other/r/e696248/', 'other-app', 'e696248', csrf=False)
self.assertGet('/other/r/e696248/abc/def', 'other-app', 'e696248', csrf=False)
self.assertGet('/other/r/e696248/#/abc/def', 'other-app', 'e696248', csrf=False)

def assertGet(self, url, manifest, revision, current=False, csrf=True):
response = self.client.get(url)
content = response.content.decode('utf-8')

Expand All @@ -63,6 +77,7 @@ def assertGet(self, url, manifest, revision, current=False):
manifest = self.manifests[manifest]
self.assertBaseUrlInBaseTag(manifest, revision, content, current)
self.assertBaseUrlInMetaTag(manifest, revision, content, current)
self.assertCsrfTokenInMetaTag(content, csrf)

def assertBaseUrlInBaseTag(self, manifest, revision, content, current):
if not manifest['base_tag']:
Expand Down Expand Up @@ -91,6 +106,10 @@ def assertBaseUrlInMetaTag(self, manifest, revision, content, current):

self.assertTrue(base_url in content)

def assertCsrfTokenInMetaTag(self, content, required=True):
match = self.csrf_pattern.search(content)
self.assertEqual(required, bool(match))

def assertRedirect(self, url, manifest):
response = self.client.get(url)

Expand Down

0 comments on commit 5d8070c

Please sign in to comment.