Commit 49850f94 authored by Serge S. Koval's avatar Serge S. Koval

Fixed #442. Do not allow redirects to external sites from create/edit/delete model views

parent 6cf9b937
from re import sub
from urlparse import urlparse, urljoin
from jinja2 import contextfunction
from flask import g, request
from wtforms.validators import DataRequired, InputRequired
from ._compat import string_types
......@@ -96,3 +98,17 @@ def prettify_class_name(name):
String to split
"""
return sub(r'(?<=.)([A-Z])', r' \1', name)
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return (test_url.scheme in ('http', 'https') and
ref_url.netloc == test_url.netloc)
def get_redirect_target(param_name='url'):
target = request.values.get(param_name)
if target and is_safe_url(target):
return target
......@@ -10,7 +10,7 @@ from flask.ext.admin.base import BaseView, expose
from flask.ext.admin.form import BaseForm, FormOpts, rules
from flask.ext.admin.model import filters, typefmt
from flask.ext.admin.actions import ActionsMixin
from flask.ext.admin.helpers import get_form_data, validate_form_on_submit
from flask.ext.admin.helpers import get_form_data, validate_form_on_submit, get_redirect_target
from flask.ext.admin.tools import rec_getattr
from flask.ext.admin._backwards import ObsoleteAttr
from flask.ext.admin._compat import iteritems, as_unicode
......@@ -1200,7 +1200,7 @@ class BaseModelView(BaseView, ActionsMixin):
"""
Create model view
"""
return_url = request.args.get('url') or url_for('.index_view')
return_url = get_redirect_target() or url_for('.index_view')
if not self.can_create:
return redirect(return_url)
......@@ -1228,7 +1228,7 @@ class BaseModelView(BaseView, ActionsMixin):
"""
Edit model view
"""
return_url = request.args.get('url') or url_for('.index_view')
return_url = get_redirect_target() or url_for('.index_view')
if not self.can_edit:
return redirect(return_url)
......@@ -1266,7 +1266,7 @@ class BaseModelView(BaseView, ActionsMixin):
"""
Delete model view. Only POST method is allowed.
"""
return_url = request.args.get('url') or url_for('.index_view')
return_url = get_redirect_target() or url_for('.index_view')
# TODO: Use post
if not self.can_delete:
......
......@@ -840,3 +840,26 @@ def test_ajax_fk_multi():
ok_(mdl is not None)
ok_(mdl.model1 is not None)
eq_(len(mdl.model1), 1)
def test_safe_redirect():
app, db, admin = setup()
Model1, _ = create_models(db)
db.create_all()
view = CustomModelView(Model1, db.session)
admin.add_view(view)
client = app.test_client()
rv = client.post('/admin/model1view/new/?url=http://localhost/admin/model2view/',
data=dict(test1='test1large', test2='test2'))
eq_(rv.status_code, 302)
eq_(rv.location, 'http://localhost/admin/model2view/')
rv = client.post('/admin/model1view/new/?url=http://google.com/evil/',
data=dict(test1='test1large', test2='test2'))
eq_(rv.status_code, 302)
eq_(rv.location, 'http://localhost/admin/model1view/')
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