# HG changeset patch # User Daniele Nicolodi # Date 1320789948 -3600 # Node ID 215684074923b3934b8a8237ab6b68eb7a877d6c # Parent 83c2d2f07df4769775bf657b971c0db84ae28c0f Implement Atom feed security in a way compatible with feed readers. diff -r 83c2d2f07df4 -r 215684074923 src/ltpdarepo/__init__.py --- a/src/ltpdarepo/__init__.py Sun Aug 14 12:23:55 2011 +0200 +++ b/src/ltpdarepo/__init__.py Tue Nov 08 23:05:48 2011 +0100 @@ -174,8 +174,9 @@ from .views.browse import module app.register_blueprint(module, url_prefix='/browse') -from .views.feed import module +from .views.feed import module, url_for_atom_feed app.register_blueprint(module, url_prefix='/browse') +app.jinja_env.globals['url_for_atom_feed'] = url_for_atom_feed from .views.profile import module app.register_blueprint(module, url_prefix='/user') diff -r 83c2d2f07df4 -r 215684074923 src/ltpdarepo/templates/database.html --- a/src/ltpdarepo/templates/database.html Sun Aug 14 12:23:55 2011 +0200 +++ b/src/ltpdarepo/templates/database.html Tue Nov 08 23:05:48 2011 +0100 @@ -1,6 +1,6 @@ {% extends "layout.html" %} {% block head %} - + {% endblock %} {% block title %}{{ database.id }}{% endblock %} {% block body %} @@ -29,7 +29,7 @@ {% endif %}

Feed

Atom feed with the latest submisions to the database:

- + {% endblock %} diff -r 83c2d2f07df4 -r 215684074923 src/ltpdarepo/views/feed.py --- a/src/ltpdarepo/views/feed.py Sun Aug 14 12:23:55 2011 +0200 +++ b/src/ltpdarepo/views/feed.py Tue Nov 08 23:05:48 2011 +0100 @@ -1,43 +1,72 @@ -from flask import Blueprint, request, url_for, g +from flask import Blueprint, request, url_for, g, current_app, abort +from itsdangerous import BadSignature, Signer as BaseSigner from werkzeug.contrib.atom import AtomFeed from .browse import Objs from ltpdarepo.database import Database -from ltpdarepo.security import require, view app = Blueprint('feed', __name__) -@app.route('//atom.xml') -@require('user') -def atom(database): - with view('database', database): - db = Database.load(id=database) - if db is None: - # not found - abort(404) +class Signer(BaseSigner): + def __init__(self, secret=None, salt=None): + if secret is None: + secret = current_app.config['SECRET_KEY'] + if salt is None: + salt = request.host + super(Signer, self).__init__(secret, salt, sep='/') - feed = AtomFeed( - title=db.id, subtitle=db.description, - url=url_for('browse.database', database=database, _external=True), - feed_url=request.url, - generator=('LTPDA Repository', None, g.version)) + def unsign(self, token): + # translate bad signature exceptions into unauthorized http errors + try: + return super(Signer, self).unsign(token) + except BadSignature: + # unauthorized + abort(403) + + +def url_for_atom_feed(database, **kwargs): + signer = Signer() + token = signer.sign(database) + return url_for('feed.atom', token=token, **kwargs) + + +@app.route('//atom.xml') +def atom(token): + # this route cannot require authentication in the usual way + # because most feed readers do not support authentication. the + # trick is therefore to encode an authorization token into the url - # first n objects ordered per descending submission time - objs = Objs(database=database).orderby('submitted', 1).limit(64) - - for obj in objs.all(): - obj['url'] = url_for('browse.obj', database=database, - objid=obj['id'], _external=True) - feed.add(title='%s: %s' % (obj['name'], obj['title']), - content='%s %s' % (obj['description'], obj['analysis']), - content_type='text', - author=obj['author'], - url=obj['url'], - updated=obj['submitted']) + # check the token + signer = Signer() + database = signer.unsign(token) + + db = Database.load(id=database) + if db is None: + # not found + abort(404) + + feed = AtomFeed( + title=db.id, subtitle=db.description, + url=url_for('browse.database', database=database, _external=True), + feed_url=request.url, + generator=('LTPDA Repository', None, g.version)) - # return with the correct mime type - return feed.get_response() + # first n objects ordered per descending submission time + objs = Objs(database=database).orderby('submitted', 1).limit(64) + + for obj in objs.all(): + obj['url'] = url_for('browse.obj', database=database, + objid=obj['id'], _external=True) + feed.add(title='%s: %s' % (obj['name'], obj['title']), + content='%s %s' % (obj['description'], obj['analysis']), + content_type='text', + author=obj['author'], + url=obj['url'], + updated=obj['submitted']) + + # return with the correct mime type + return feed.get_response() module = app