Skip to content

Commit

Permalink
add in token generation and storage (bug #643482)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy McKay committed Apr 21, 2011
1 parent cba6af7 commit 9840be9
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 32 deletions.
38 changes: 37 additions & 1 deletion apps/amo/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import amo
from amo import urlresolvers, utils, helpers
from amo.utils import ImageCheck
from amo.utils import ImageCheck, Token
from versions.models import License


Expand Down Expand Up @@ -363,6 +363,42 @@ def test_junk(self):
assert img.is_image()


class TestToken(test_utils.TestCase):

def test_token_pop(self):
new = Token()
new.save()
assert Token.pop(new.token)
assert not Token.pop(new.token)

def test_token_valid(self):
new = Token()
new.save()
assert Token.valid(new.token)

def test_token_fails(self):
assert not Token.pop('some-random-token')

def test_token_ip(self):
new = Token(data='127.0.0.1')
new.save()
assert Token.valid(new.token, '127.0.0.1')

def test_token_no_ip_invalid(self):
new = Token()
assert not Token.valid(new.token, '255.255.255.0')

def test_token_bad_ip_invalid(self):
new = Token(data='127.0.0.1')
new.save()
assert not Token.pop(new.token, '255.255.255.0')
assert Token.pop(new.token, '127.0.0.1')

def test_token_well_formed(self):
new = Token('some badly formed token')
assert not new.well_formed()


def test_site_nav():
r = Mock()
r.APP = amo.FIREFOX
Expand Down
44 changes: 44 additions & 0 deletions apps/amo/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import unicodedata
import urllib
import urlparse
import uuid

from django import http
from django.conf import settings
Expand Down Expand Up @@ -447,3 +448,46 @@ def wrapper(*args, **kwargs):
return wrapper
return decorator


class Token:
_well_formed = re.compile('^[a-z0-9-]+$')

def __init__(self, token=None, data=True):
if token is None:
token = str(uuid.uuid4())
self.token = token
self.data = data

def cache_key(self):
assert self.token, 'No token value set.'
return '%s:token:%s' % (settings.CACHE_PREFIX, self.token)

def save(self, time=60):
cache.set(self.cache_key(), self.data, time)

def well_formed(self):
return self._well_formed.match(self.token)

@classmethod
def valid(cls, key, data=True):
"""Checks that the token is valid."""
token = cls(key)
if not token.well_formed():
return False
result = cache.get(token.cache_key())
if result is not None:
return result == data
return False

@classmethod
def pop(cls, key, data=True):
"""Checks that the token is valid and deletes it."""
token = cls(key)
if not token.well_formed():
return False
result = cache.get(token.cache_key())
if result is not None:
if result == data:
cache.delete(token.cache_key())
return True
return False
15 changes: 15 additions & 0 deletions apps/files/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import get_object_or_404

from amo.utils import Token
from access.acl import check_addon_ownership, action_allowed
from files.helpers import DiffHelper, FileViewer
from files.models import File
Expand Down Expand Up @@ -51,3 +52,17 @@ def wrapper(request, one_id, two_id, *args, **kw):

return func(request, DiffHelper(one, two), *args, **kw)
return wrapper


def file_view_token(func, **kwargs):
@functools.wraps(func)
def wrapper(request, file_id, key, *args, **kw):
viewer = FileViewer(get_object_or_404(File, pk=file_id))
token = request.GET.get('token')
if not token:
return http.HttpResponseForbidden()
if not Token.valid(token, [request.META.get('REMOTE_ADDR'),
viewer.file.id, key]):
return http.HttpResponseForbidden()
return func(request, viewer, key, *args, **kw)
return wrapper
32 changes: 15 additions & 17 deletions apps/files/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,22 +128,20 @@ def _get_files(self):
filename = smart_unicode(filename)
short = smart_unicode(path[len(self.dest) + 1:])
mime, encoding = mimetypes.guess_type(filename)
binary = self.is_binary(mime, filename)
# TODO: handling for st.a.m.o will be going in here
url = reverse('files.list', args=[self.file.id, short])
directory = os.path.isdir(path)
res[short] = {'full': path,
'filename': filename,
'truncated': self.truncate(filename),
'short': short,
'directory': directory,
'url': url,
'binary': binary,
args = [self.file.id, short]
res[short] = {'binary': self.is_binary(mime, filename),
'depth': short.count(os.sep),
'modified': os.stat(path)[stat.ST_MTIME],
'mimetype': mime or 'application/octet-stream',
'encoding': encoding,
'directory': directory,
'filename': filename,
'full': path,
'md5': get_md5(path) if not directory else '',
'mimetype': mime or 'application/octet-stream',
'modified': os.stat(path)[stat.ST_MTIME],
'short': short,
'truncated': self.truncate(filename),
'url': reverse('files.list', args=args),
'url_serve': reverse('files.redirect', args=args),
'parent': '/'.join(short.split(os.sep)[:-1])}
return res

Expand All @@ -170,12 +168,12 @@ def cleanup(self):
def is_extracted(self):
return self.file_one.is_extracted and self.file_two.is_extracted

def primary_files(self):
def get_files(self, file_obj):
"""
Get the files from the primary and remap any diffable ones
to the compare url as opposed to the other url.
"""
files = self.file_one.get_files()
files = file_obj.get_files()
for file in files.values():
file['url'] = reverse('files.compare',
args=[self.file_one.file.id,
Expand All @@ -186,8 +184,8 @@ def primary_files(self):

def select(self, key):
self.key = key
self.one = self.file_one.get_files().get(key)
self.two = self.file_two.get_files().get(key)
self.one = self.get_files(self.file_one).get(key)
self.two = self.get_files(self.file_two).get(key)

def is_different(self):
if self.one and self.two:
Expand Down
7 changes: 7 additions & 0 deletions apps/files/templates/files/file.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<dl>
<dt>{{ _('Version:') }}</dt><dd>{{ file.version }}</dd>
{# L10n: {0} is the filename #}
<dt>{{ _('File:') }}</dt><dd><a href="{{ selected['url_serve'] }}">{{ _('Download {0}').format(selected['filename']) }}</a></dd>
<dt>{{ _('MD5 hash:') }}</dt><dd>{{ selected['md5'] }}</dd>
<dt>{{ _('Mimetype:') }}</dt><dd>{{ selected['mimetype'] }}</dd>
</dl>
14 changes: 10 additions & 4 deletions apps/files/templates/files/viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ <h3>
<div>
{% if viewer %}
{% if selected['binary'] and not selected['directory'] %}
<p>{{ _('Binary file, hash:') }} {{ selected.md5 }}</p>
{% include "files/file.html" %}
{% else %}
{% if selected and content %}
<pre id="content" class="wrapped">{{ content }}</pre>
Expand All @@ -70,10 +70,16 @@ <h3>
{% if diff and diff.is_binary() %}
<p>
{% if diff.is_different() %}
{{ _('Files are different.') }}
{% else %}
{{ _('Files are the same.') }}
{{ _('Files are different.') }}<br/>
{% else %}
{{ _('Files are the same.') }}<br/>
{% endif %}
{% with selected = diff.one, file = diff.file_one.file %}
{% include "files/file.html" %}
{% endwith %}
{% with selected = diff.two, file = diff.file_two.file %}
{% include "files/file.html" %}
{% endwith %}
</p>
{% endif %}
{% if diff and not diff.is_binary() %}
Expand Down
6 changes: 3 additions & 3 deletions apps/files/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

from amo.urlresolvers import reverse
from files.helpers import FileViewer, DiffHelper
from files.utils import extract_zip

root = os.path.join(settings.ROOT, 'apps/files/fixtures/files')
dictionary = '%s/dictionary-test.xpi' % root
Expand Down Expand Up @@ -148,8 +147,9 @@ def test_files_extracted(self):
self.helper.extract()
eq_(self.helper.is_extracted, True)

def test_primary_files(self):
eq_(self.helper.file_one.get_files(), self.helper.primary_files())
def test_get_files(self):
eq_(self.helper.file_one.get_files(),
self.helper.get_files(self.helper.file_one))

def test_diffable(self):
self.helper.extract()
Expand Down
64 changes: 59 additions & 5 deletions apps/files/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import shutil
import tempfile
import urlparse

from django.conf import settings
from django.core.cache import cache
Expand All @@ -17,9 +18,10 @@
from files.models import File
from users.models import UserProfile


dictionary = 'apps/files/fixtures/files/dictionary-test.xpi'
unicode_filenames = 'apps/files/fixtures/files/unicode-filenames.xpi'
not_binary = 'install.js'
binary = 'dictionaries/ar.dic'


class FilesBase:
Expand Down Expand Up @@ -62,6 +64,12 @@ def tearDown(self):
settings.TMP_PATH = self.old_tmp
settings.ADDONS_PATH = self.old_addon

def files_redirect(self, file):
return reverse('files.redirect', args=[self.file.pk, file])

def files_serve(self, file):
return reverse('files.serve', args=[self.file.pk, file])

def test_view_access_anon(self):
self.client.logout()
self.check_urls(403)
Expand Down Expand Up @@ -133,15 +141,15 @@ def test_content_headers(self):

def test_file_header(self):
self.file_viewer.extract()
res = self.client.get(self.file_url('install.js'))
res = self.client.get(self.file_url(not_binary))
url = res.context['file_url']
eq_(url, reverse('editors.review', args=[self.version.pk]))

def test_file_header_anon(self):
self.client.logout()
self.file_viewer.extract()
self.addon.update(view_source=True)
res = self.client.get(self.file_url('install.js'))
res = self.client.get(self.file_url(not_binary))
url = res.context['file_url']
eq_(url, reverse('addons.detail', args=[self.addon.pk]))

Expand Down Expand Up @@ -169,7 +177,7 @@ def test_files_anon(self):

def test_files_file(self):
self.file_viewer.extract()
res = self.client.get(self.file_url('install.js'))
res = self.client.get(self.file_url(not_binary))
eq_(res.status_code, 200)
assert 'selected' in res.context

Expand Down Expand Up @@ -250,6 +258,44 @@ def test_unicode(self):
res = self.client.get(self.file_url(iri_to_uri(u'\u1109\u1161\u11a9')))
eq_(res.status_code, 200)

def test_serve_no_token(self):
self.file_viewer.extract()
res = self.client.get(self.files_serve(binary))
eq_(res.status_code, 403)

def test_serve_fake_token(self):
self.file_viewer.extract()
res = self.client.get(self.files_serve(binary) + '?token=aasd')
eq_(res.status_code, 403)

def test_serve_bad_token(self):
self.file_viewer.extract()
res = self.client.get(self.files_serve(binary) + '?token=a asd')
eq_(res.status_code, 403)

def test_serve_get_token(self):
self.file_viewer.extract()
res = self.client.get(self.files_redirect(binary))
eq_(res.status_code, 302)
url = res['Location']
assert url.startswith(settings.STATIC_URL)
assert urlparse.urlparse(url).query.startswith('token=')

def test_memcache_goes_bye_bye(self):
self.file_viewer.extract()
res = self.client.get(self.files_redirect(binary))
url = res['Location'][len(settings.STATIC_URL):]
cache.clear()
res = self.client.get(url)
eq_(res.status_code, 403)

def test_bounce(self):
self.file_viewer.extract()
res = self.client.get(self.files_redirect(binary), follow=True)
eq_(res.status_code, 200)
eq_(res['X-SENDFILE'],
self.file_viewer.get_files().get(binary)['full'])


class TestDiffViewer(FilesBase, test_utils.TestCase):
fixtures = ['base/addon_3615', 'base/users']
Expand Down Expand Up @@ -283,7 +329,15 @@ def test_tree_no_file(self):

def test_content_file(self):
self.file_viewer.extract()
res = self.client.get(self.file_url('install.js'))
res = self.client.get(self.file_url(not_binary))
doc = pq(res.content)
eq_(len(doc('#file-one')), 1)
eq_(len(doc('#file-two')), 1)

def test_binary_serve_links(self):
self.file_viewer.extract()
res = self.client.get(self.file_url(binary))
doc = pq(res.content)
node = doc('#content-wrapper a')
eq_(len(node), 2)
assert node[0].text.startswith('Download ar.dic')
3 changes: 3 additions & 0 deletions apps/files/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
file_patterns = patterns('',
url(r'^$', views.files_list, name='files.list'),
url(r'file/(?P<key>.*)$', views.files_list, name='files.list'),
url(r'file-redirect/(?P<key>.*)$', views.files_redirect,
name='files.redirect'),
url(r'file-serve/(?P<key>.*)$', views.files_serve, name='files.serve'),
url(r'status$', views.files_poll, name='files.poll'),
)

Expand Down
Loading

0 comments on commit 9840be9

Please sign in to comment.