Skip to content

Commit

Permalink
Merge branch 'hotfix/2.0.0b2' into testing
Browse files Browse the repository at this point in the history
  • Loading branch information
disko committed Apr 3, 2018
2 parents 4ba683e + 9c99ba3 commit 0a565eb
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 43 deletions.
5 changes: 5 additions & 0 deletions CHANGES.txt
@@ -1,6 +1,11 @@
Change History
==============

2.0.0b2 - 2018-04-03
--------------------

- Fix CSRF issue in ``@@share`` view (#551).

2.0.0b1 - 2018-03-19
--------------------

Expand Down
1 change: 1 addition & 0 deletions kotti/templates/edit/share.pt
Expand Up @@ -39,6 +39,7 @@

<form action="${request.url}" method="post" tal:condition="entries"
id="form-share-assign">
<input type="hidden" name="csrf_token" value="${csrf_token}">

<fieldset>
<legend i18n:translate="">Assign local roles</legend>
Expand Down
8 changes: 4 additions & 4 deletions kotti/templates/login.pt
Expand Up @@ -17,9 +17,9 @@
</div>
<div class="panel-body">
<div class="control-group">
<label class="control-label" for="form-login"
<label class="control-label" for="form-login-login"
i18n:translate="">Username or email</label>
<input type="text" name="login" id="form-login"
<input type="text" name="login" id="form-login-login"
class="form-control" value="${login}" />
</div>
<div class="control-group">
Expand Down Expand Up @@ -55,9 +55,9 @@
below to receive an email with a link to reset your password.
</div>
<div class="control-group">
<label class="control-label" for="form-login"
<label class="control-label" for="form-forgot-password-login"
i18n:translate="">Username or email</label>
<input type="text" name="login" id="form-login"
<input type="text" name="login" id="form-forgot-password-login"
class="form-control" value="${login}" />
</div>
</div>
Expand Down
113 changes: 85 additions & 28 deletions kotti/tests/test_functional.py
Expand Up @@ -562,22 +562,83 @@ def test_content_management(self, webtest):
assert '<i class="glyphicon glyphicon-folder-open"></i>' in resp.text
assert '<i class="glyphicon glyphicon-folder-close"></i>' not in resp.text # noqa

# Navigation
resp = resp.click("Navigate")
resp = resp.click("Second Child", index=0)
assert resp.request.path == '/second-child-1/'

@pytest.mark.user('admin')
def test_workflow_actions(self, webtest):

app = webtest.app

# Add a document
resp = app.get('/')
resp = resp.click('Document', index=0)
form = resp.forms['deform']
form['title'] = "Child One"
resp = form.submit('save').maybe_follow()
assert "Item was added" in resp.text
assert resp.request.path == '/child-one/'

# Add more documents
resp = app.get('/')
resp = resp.click('Document', index=0)
form = resp.forms['deform']
form['title'] = "Second Child"
resp = form.submit('save').maybe_follow()
assert resp.request.path == '/second-child/'

resp = resp.click('Document', index=0)
form = resp.forms['deform']
form['title'] = "Third Child"
resp = form.submit('save').maybe_follow()
assert resp.request.path == '/second-child/third-child/'

resp = app.get('/second-child/third-child/')
resp = resp.click('Document', index=0)
form = resp.forms['deform']
form['title'] = "Grandchild 1"
resp = form.submit('save').maybe_follow()
assert resp.request.path == '/second-child/third-child/grandchild-1/'

resp = app.get('/second-child/third-child/')
resp = resp.click('Document', index=0)
form = resp.forms['deform']
form['title'] = "Grandchild 2"
resp = form.submit('save').maybe_follow()
assert resp.request.path == '/second-child/third-child/grandchild-2/'

resp = app.get('/second-child/third-child/')
resp = resp.click('Document', index=0)
form = resp.forms['deform']
form['title'] = "Grandchild 3"
resp = form.submit('save').maybe_follow()
assert resp.request.path == '/second-child/third-child/grandchild-3/'

resp = app.get('/second-child/third-child/')
resp = resp.click('Document', index=0)
form = resp.forms['deform']
form['title'] = "Child One"
resp = form.submit('save').maybe_follow()
assert resp.request.path == '/second-child/third-child/child-one/'

# Contents view change state actions (workflow)
resp = resp.click("Second Child")
resp = resp.click("Second Child", index=0)
resp = resp.click("Contents")
assert '/second-child-1/third-child/@@workflow-change?new_state=public' in resp.text # noqa
assert '/second-child/third-child/@@workflow-change?new_state=public' in resp.text # noqa

resp = app.get('/second-child-1/third-child/@@workflow-change?new_state=public').maybe_follow() # noqa
assert '/second-child-1/third-child/@@workflow-change?new_state=private' in resp.text # noqa
resp = app.get('/second-child/third-child/@@workflow-change?new_state=public').maybe_follow() # noqa
assert '/second-child/third-child/@@workflow-change?new_state=private' in resp.text # noqa

resp = app.get('/second-child-1/third-child/@@contents')
resp = app.get('/second-child/third-child/@@contents')
form = self._select_children(resp, 0, 1, 2)
resp = form.submit('change_state').maybe_follow()
assert 'Change workflow state' in resp.text

resp = resp.forms['form-change-state'].submit('cancel').maybe_follow()
assert 'No changes were made.' in resp.text
assert resp.request.path == '/second-child-1/third-child/@@contents'
assert resp.request.path == '/second-child/third-child/@@contents'

form = self._select_children(resp, 0, 1, 2)
resp = form.submit('change_state').maybe_follow()
Expand All @@ -592,42 +653,38 @@ def test_content_management(self, webtest):
form['to-state'] = 'public'
resp = form.submit('change_state').maybe_follow()
assert 'Your changes have been saved.' in resp.text
assert '/second-child-1/third-child/grandchild-1/@@workflow-change?new_state=private' in resp.text # noqa
assert '/second-child-1/third-child/grandchild-2/@@workflow-change?new_state=private' in resp.text # noqa
assert '/second-child-1/third-child/grandchild-3/@@workflow-change?new_state=private' in resp.text # noqa
assert '/second-child/third-child/grandchild-1/@@workflow-change?new_state=private' in resp.text # noqa
assert '/second-child/third-child/grandchild-2/@@workflow-change?new_state=private' in resp.text # noqa
assert '/second-child/third-child/grandchild-3/@@workflow-change?new_state=private' in resp.text # noqa

resp = resp.click('My Third Child')
assert '/second-child-1/third-child/child-one/@@workflow-change?new_state=public' in resp.text # noqa
resp = resp.click('Child One', index=1)
assert '/second-child/third-child/child-one/@@workflow-change?new_state=public' in resp.text # noqa

app.get('/second-child-1/third-child/child-one/@@workflow-change?new_state=public') # noqa
resp = app.get('/second-child-1/third-child/@@contents')
assert '/second-child-1/third-child/child-one/@@workflow-change?new_state=private' in resp.text # noqa
app.get('/second-child/third-child/child-one/@@workflow-change?new_state=public') # noqa
resp = app.get('/second-child/third-child/@@contents')
assert '/second-child/third-child/child-one/@@workflow-change?new_state=private' in resp.text # noqa

resp = resp.click('First Child', index=1)
resp = resp.click('Child One', index=1)
resp = resp.click('Document', index=0)
form = resp.forms['deform']
form['title'] = 'Sub child'
resp = form.submit('save').maybe_follow()
assert '/second-child-1/third-child/child-one/sub-child/@@workflow-change?new_state=public' in resp.text # noqa
assert '/second-child/third-child/child-one/sub-child/@@workflow-change?new_state=public' in resp.text # noqa

resp = resp.click("Second Child", index=0)
resp = resp.click("Contents")
form = self._select_children(resp, 0, 1, 2)
resp = resp.click('Second Child', index=0)
resp = resp.click('Contents')

form = self._select_children(resp, 0)
resp = form.submit('change_state').maybe_follow()
form = resp.forms['form-change-state']
form['include-children'] = 'include-children'
form['to-state'] = 'public'
form['to-state'] = 'private'
resp = form.submit('change_state').maybe_follow()
assert 'Your changes have been saved.' in resp.text
assert '/second-child-1/third-child/@@workflow-change?new_state=private' in resp.text # noqa
assert '/second-child/third-child/@@workflow-change?new_state=public' in resp.text # noqa

resp = app.get('/second-child-1/third-child/child-one/sub-child/')
assert '/second-child-1/third-child/child-one/sub-child/@@workflow-change?new_state=private' in resp.text # noqa

# Navigation
resp = resp.click("Navigate")
resp = resp.click("Second Child", index=0)
assert resp.request.path == '/second-child-1/'
resp = app.get('/second-child/third-child/child-one/sub-child/')
assert '/second-child/third-child/child-one/sub-child/@@workflow-change?new_state=public' in resp.text # noqa

def test_user_management(self, webtest, settings, dummy_mailer):
from kotti import get_settings
Expand Down
18 changes: 18 additions & 0 deletions kotti/tests/test_node_views.py
@@ -1,4 +1,5 @@
from pyramid.exceptions import Forbidden
from pyramid.httpexceptions import HTTPBadRequest
from pytest import raises
from webob.multidict import MultiDict

Expand Down Expand Up @@ -432,6 +433,7 @@ def test_apply(self, extra_principals, root):
request = DummyRequest()

request.params['apply'] = ''
request.params['csrf_token'] = request.session.get_csrf_token()
share_node(root, request)
assert (request.session.pop_flash('info') == ['No changes were made.'])
assert list_groups('bob', root) == []
Expand Down Expand Up @@ -460,3 +462,19 @@ def test_apply(self, extra_principals, root):
set(list_groups('bob', root)) ==
{'role:owner', 'role:editor', 'role:special'}
)

def test_csrf(self, extra_principals, root, dummy_request):
""" Test if a CSRF token is present and checked on submission """
from kotti.views.users import share_node

result = share_node(root, dummy_request)
assert 'csrf_token' in result

dummy_request.params['apply'] = ''

with raises(HTTPBadRequest):
share_node(root, dummy_request)

dummy_request.params['csrf_token'] = dummy_request.session.get_csrf_token() # noqa
result = share_node(root, dummy_request)
assert result['csrf_token'] == dummy_request.session.get_csrf_token()
6 changes: 5 additions & 1 deletion kotti/views/users.py
Expand Up @@ -9,7 +9,7 @@
from deform.widget import CheckedPasswordWidget
from deform.widget import SequenceWidget
from pyramid.exceptions import Forbidden
from pyramid.httpexceptions import HTTPFound
from pyramid.httpexceptions import HTTPFound, HTTPBadRequest
from pyramid.view import view_config
from pyramid_deform import FormView
from six import string_types
Expand Down Expand Up @@ -109,6 +109,9 @@ def search_principals(request, context=None, ignore=None, extra=()):
renderer='kotti:templates/edit/share.pt')
def share_node(context, request):
# Allow roles_form_handler to do processing on 'apply':
if 'apply' in request.POST:
if request.params.get('csrf_token') != request.session.get_csrf_token():
raise HTTPBadRequest('Invalid CSRF token')
changed = roles_form_handler(
context, request, SHARING_ROLES, list_groups_raw)
if changed:
Expand All @@ -132,6 +135,7 @@ def with_roles(entry):
return {
'entries': entries,
'available_roles': available_roles,
'csrf_token': request.session.get_csrf_token()
}


Expand Down
16 changes: 8 additions & 8 deletions requirements.txt
@@ -1,8 +1,8 @@
alembic==0.9.8
alembic==0.9.9
appdirs==1.4.3
Babel==2.5.3
Beaker==1.9.0
bleach==2.1.2
bleach==2.1.3
bleach-whitelist==0.0.8
Chameleon==3.2
colander==1.4
Expand All @@ -13,11 +13,11 @@ filedepot==0.5.2
FormEncode==2.0.0a1
html2text==2018.1.9
html5lib==1.0.1
hupper==1.0
hupper==1.1
iso8601==0.1.11 # rq.filter: !=0.1.12
js.angular==1.1.4
js.bootstrap==3.3.4
js.deform==2.0.5.dev0
#js.deform==2.0.5.dev0
js.fineuploader==5.14.0
js.html5shiv==3.7.3
js.jquery==1.9.1 # rq.filter: <2.0
Expand All @@ -39,7 +39,7 @@ MarkupSafe==1.0
PasteDeploy==1.5.2
peppercorn==0.5
plaster==1.0
plaster-pastedeploy==0.4.2
plaster-pastedeploy==0.5
polib==1.1.0
py-bcrypt==0.4
pyramid==1.9.1
Expand All @@ -59,9 +59,9 @@ repoze.zcml==1.0b1
rfc6266-parser==0.0.5.post2
shutilwhich==1.1.0
six==1.11.0
SQLAlchemy==1.2.3
SQLAlchemy-Utils==0.33.0
transaction==2.1.2
SQLAlchemy==1.2.6
SQLAlchemy-Utils==0.33.2
transaction==2.2.1
translationstring==1.3
Unidecode==1.0.22
usersettings==1.0.7
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Expand Up @@ -4,7 +4,7 @@
from setuptools import find_packages
from setuptools import setup

version = '2.0.0b1'
version = '2.0.0b2'
description = "A high-level, Pythonic web application framework based on " \
"Pyramid and SQLAlchemy. It includes an extensible Content " \
"Management System called the Kotti CMS."
Expand All @@ -31,7 +31,7 @@
'iso8601==0.1.11', # rq.filter: !=0.1.12
'js.angular',
'js.bootstrap>=3.0.0',
'js.deform>=2.0a2-2',
'js.deform>=2.0.3',
'js.fineuploader',
'js.html5shiv',
'js.jquery<2.0.0.dev', # rq.filter: <2.0
Expand Down Expand Up @@ -84,6 +84,7 @@
'check-manifest',
'pipdeptree',
'pyramid_debugtoolbar',
'kotti-tinymce>=0.7.0',
]

docs_require = [
Expand Down

0 comments on commit 0a565eb

Please sign in to comment.