Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
flask-admin
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Python-Dev
flask-admin
Commits
fce98687
Commit
fce98687
authored
Jan 14, 2015
by
Paul Brown
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix index_view delete button CSRF
parent
2b4bfe35
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
174 additions
and
16 deletions
+174
-16
base.py
flask_admin/model/base.py
+54
-8
list.html
flask_admin/templates/bootstrap2/admin/model/list.html
+5
-3
list.html
flask_admin/templates/bootstrap3/admin/model/list.html
+6
-4
test_model.py
flask_admin/tests/test_model.py
+109
-1
No files found.
flask_admin/model/base.py
View file @
fce98687
...
...
@@ -4,7 +4,8 @@ import re
from
flask
import
(
request
,
redirect
,
flash
,
abort
,
json
,
Response
,
get_flashed_messages
)
from
jinja2
import
contextfunction
from
wtforms.validators
import
ValidationError
from
wtforms.fields
import
HiddenField
from
wtforms.validators
import
ValidationError
,
Required
from
flask.ext.admin.babel
import
gettext
...
...
@@ -591,6 +592,7 @@ class BaseModelView(BaseView, ActionsMixin):
self
.
_create_form_class
=
self
.
get_create_form
()
self
.
_edit_form_class
=
self
.
get_edit_form
()
self
.
_delete_form_class
=
self
.
get_delete_form
()
# List View In-Line Editing
if
self
.
column_editable_list
:
...
...
@@ -888,6 +890,18 @@ class BaseModelView(BaseView, ActionsMixin):
"""
return
self
.
get_form
()
def
get_delete_form
(
self
):
"""
Create form class for model delete view.
Override to implement customized behavior.
"""
class
DeleteForm
(
self
.
form_base_class
):
id
=
HiddenField
(
validators
=
[
Required
()])
url
=
HiddenField
()
return
DeleteForm
def
create_form
(
self
,
obj
=
None
):
"""
Instantiate model creation form and return it.
...
...
@@ -904,6 +918,20 @@ class BaseModelView(BaseView, ActionsMixin):
"""
return
self
.
_edit_form_class
(
get_form_data
(),
obj
=
obj
)
def
delete_form
(
self
):
"""
Instantiate model delete form and return it.
Override to implement custom behavior.
"""
if
request
.
form
:
return
self
.
_delete_form_class
(
request
.
form
)
elif
request
.
args
:
# allow request.args for backward compatibility
return
self
.
_delete_form_class
(
request
.
args
)
else
:
return
self
.
_delete_form_class
()
def
list_form
(
self
,
obj
=
None
):
"""
Instantiate model editing form for list view and return it.
...
...
@@ -1294,6 +1322,11 @@ class BaseModelView(BaseView, ActionsMixin):
else
:
form
=
None
if
self
.
can_delete
:
delete_form
=
self
.
delete_form
()
else
:
delete_form
=
None
# Grab parameters from URL
view_args
=
self
.
_get_list_extra_args
()
...
...
@@ -1340,6 +1373,7 @@ class BaseModelView(BaseView, ActionsMixin):
self
.
list_template
,
data
=
data
,
form
=
form
,
delete_form
=
delete_form
,
# List
list_columns
=
self
.
_list_columns
,
...
...
@@ -1454,18 +1488,30 @@ class BaseModelView(BaseView, ActionsMixin):
"""
return_url
=
get_redirect_target
()
or
self
.
get_url
(
'.index_view'
)
# TODO: Use post
if
not
self
.
can_delete
:
return
redirect
(
return_url
)
id
=
get_mdict_item_or_list
(
request
.
args
,
'id'
)
if
id
is
None
:
return
redirect
(
return_url
)
form
=
self
.
delete_form
()
if
self
.
validate_form
(
form
):
id
=
form
.
id
.
data
# id is Required()
model
=
self
.
get_one
(
id
)
if
model
:
self
.
delete_model
(
model
)
if
model
is
None
:
return
redirect
(
return_url
)
# message is flashed from within delete_model if it fails
if
self
.
delete_model
(
model
):
flash
(
gettext
(
'Record was successfully deleted.'
))
return
redirect
(
return_url
)
else
:
# flash validation errors
for
field_name
,
errors
in
iteritems
(
form
.
errors
):
errors
=
field_name
+
u": "
+
u", "
.
join
(
errors
)
flash
(
gettext
(
'Failed to delete record.
%(error)
s'
,
error
=
str
(
errors
)),
'error'
)
return
redirect
(
return_url
)
...
...
flask_admin/templates/bootstrap2/admin/model/list.html
View file @
fce98687
...
...
@@ -107,9 +107,11 @@
</a>
{%- endif -%}
{%- if admin_view.can_delete -%}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete_view', id=get_pk_value(row), url=return_url) }}"
>
{% if csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete_view') }}"
>
<input
type=
"hidden"
name=
"id"
value=
"{{ get_pk_value(row) }}"
/>
<input
type=
"hidden"
name=
"url"
value=
"{{ return_url }}"
/>
{% if delete_form.csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ delete_form.csrf_token() }}"
/>
{% endif %}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete this record?') }}');"
title=
"{{ _gettext('Delete record') }}"
>
<i
class=
"icon-trash"
></i>
...
...
flask_admin/templates/bootstrap3/admin/model/list.html
View file @
fce98687
...
...
@@ -107,9 +107,11 @@
</a>
{%- endif -%}
{%- if admin_view.can_delete -%}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete_view', id=get_pk_value(row), url=return_url) }}"
>
{% if csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete_view') }}"
>
<input
type=
"hidden"
name=
"id"
value=
"{{ get_pk_value(row) }}"
/>
<input
type=
"hidden"
name=
"url"
value=
"{{ return_url }}"
/>
{% if delete_form.csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ delete_form.csrf_token() }}"
/>
{% endif %}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete this record?') }}');"
title=
"Delete record"
>
<span
class=
"glyphicon glyphicon-trash"
></span>
...
...
flask_admin/tests/test_model.py
View file @
fce98687
import
wtforms
from
nose.tools
import
eq_
,
ok_
from
flask
import
Flask
from
flask
import
Flask
,
session
from
werkzeug.wsgi
import
DispatcherMiddleware
from
werkzeug.test
import
Client
...
...
@@ -12,6 +14,14 @@ from flask.ext.admin._compat import iteritems, itervalues
from
flask.ext.admin.model
import
base
,
filters
def
wtforms2_and_up
(
func
):
"""Decorator for skipping test if wtforms <2
"""
if
int
(
wtforms
.
__version__
[
0
])
<
2
:
func
.
__test__
=
False
return
func
class
Model
(
object
):
def
__init__
(
self
,
id
=
None
,
c1
=
1
,
c2
=
2
,
c3
=
3
):
self
.
id
=
id
...
...
@@ -329,6 +339,104 @@ def test_form():
pass
@
wtforms2_and_up
def
test_csrf
():
from
datetime
import
timedelta
from
wtforms.csrf.session
import
SessionCSRF
from
wtforms.meta
import
DefaultMeta
# BaseForm w/ CSRF
class
SecureForm
(
form
.
BaseForm
):
class
Meta
(
DefaultMeta
):
csrf
=
True
csrf_class
=
SessionCSRF
csrf_secret
=
b
'EPj00jpfj8Gx1SjnyLxwBBSQfnQ9DJYe0Ym'
csrf_time_limit
=
timedelta
(
minutes
=
20
)
@
property
def
csrf_context
(
self
):
return
session
class
SecureModelView
(
MockModelView
):
form_base_class
=
SecureForm
def
scaffold_form
(
self
):
return
SecureForm
def
get_csrf_token
(
data
):
data
=
data
.
split
(
'name="csrf_token" type="hidden" value="'
)[
1
]
token
=
data
.
split
(
'">'
)[
0
]
return
token
app
,
admin
=
setup
()
view
=
SecureModelView
(
Model
,
endpoint
=
'secure'
)
admin
.
add_view
(
view
)
client
=
app
.
test_client
()
################
# create_view
################
rv
=
client
.
get
(
'/admin/secure/new/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
u'name="csrf_token"'
in
rv
.
data
.
decode
(
'utf-8'
))
csrf_token
=
get_csrf_token
(
rv
.
data
.
decode
(
'utf-8'
))
# Create without CSRF token
rv
=
client
.
post
(
'/admin/secure/new/'
,
data
=
dict
(
name
=
'test1'
))
eq_
(
rv
.
status_code
,
200
)
# Create with CSRF token
rv
=
client
.
post
(
'/admin/secure/new/'
,
data
=
dict
(
name
=
'test1'
,
csrf_token
=
csrf_token
))
eq_
(
rv
.
status_code
,
302
)
###############
# edit_view
###############
rv
=
client
.
get
(
'/admin/secure/edit/?url=
%2
Fadmin
%2
Fsecure
%2
F&id=1'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
u'name="csrf_token"'
in
rv
.
data
.
decode
(
'utf-8'
))
csrf_token
=
get_csrf_token
(
rv
.
data
.
decode
(
'utf-8'
))
# Edit without CSRF token
rv
=
client
.
post
(
'/admin/secure/edit/?url=
%2
Fadmin
%2
Fsecure
%2
F&id=1'
,
data
=
dict
(
name
=
'test1'
))
eq_
(
rv
.
status_code
,
200
)
# Edit with CSRF token
rv
=
client
.
post
(
'/admin/secure/edit/?url=
%2
Fadmin
%2
Fsecure
%2
F&id=1'
,
data
=
dict
(
name
=
'test1'
,
csrf_token
=
csrf_token
))
eq_
(
rv
.
status_code
,
302
)
################
# delete_view
################
rv
=
client
.
get
(
'/admin/secure/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
u'name="csrf_token"'
in
rv
.
data
.
decode
(
'utf-8'
))
csrf_token
=
get_csrf_token
(
rv
.
data
.
decode
(
'utf-8'
))
# Delete without CSRF token, test validation errors
rv
=
client
.
post
(
'/admin/secure/delete/'
,
data
=
dict
(
id
=
"1"
,
url
=
"/admin/secure/"
),
follow_redirects
=
True
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
u'Record was successfully deleted.'
not
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
u'Failed to delete record.'
in
rv
.
data
.
decode
(
'utf-8'
))
# Delete with CSRF token
rv
=
client
.
post
(
'/admin/secure/delete/'
,
data
=
dict
(
id
=
"1"
,
url
=
"/admin/secure/"
,
csrf_token
=
csrf_token
),
follow_redirects
=
True
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
u'Record was successfully deleted.'
in
rv
.
data
.
decode
(
'utf-8'
))
def
test_custom_form
():
app
,
admin
=
setup
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment