Skip to content

Commit

Permalink
recaptcha inside a fieldset doesn't validate and allows any submission (
Browse files Browse the repository at this point in the history
#190)

* Put in tests to show recaptcha validation prevents submissions

* add robot tests to coverage

* fix flake8

* try improve caching

* show failing test recaptcha in a fieldset

* show failing test recaptcha in a fieldset

* fix fieldsets with recaptcha usecase

* flake 8

* more detailed comment

* flake8

* simply test

* more generic solution so IValidator adapters still work on fields

* flake8

* flake8

* put in comments and make clearer

* add in less specific adapter call for defaults also to pick up any fields with default adapters.

* flake8

* show test results quicker

* try to make the code clearer

* more reusable way of getting the superAdapter

* make more generic
  • Loading branch information
djay committed Oct 2, 2019
1 parent 459127c commit 5d18bce
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ script:
# Run code-analysis, except on Python 3.6, which mysteriously fails to find zc.buildout.
- python --version 2> /dev/stdout | grep 3.6 || bin/code-analysis
- bin/test --all $TEST_OPTIONS
- bin/createcoverage -t '--all $TEST_OPTIONS'
after_success:
- bin/createcoverage -t '--all $TEST_OPTIONS'
- pip install -q coveralls
- coveralls
notifications:
Expand Down
72 changes: 63 additions & 9 deletions src/collective/easyform/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
from collective.easyform.validators import IFieldValidator
from plone.schemaeditor.fields import FieldFactory
from plone.supermodel.exportimport import BaseHandler
from z3c.form import validator as z3c_validator
from z3c.form.interfaces import IGroup
from z3c.form.interfaces import IValidator
from z3c.form.interfaces import IValue
from zope.component import adapter
from zope.component import adapter, queryMultiAdapter
from zope.component import queryUtility
from zope.interface import implementer
from zope.interface import implementer, providedBy
from zope.interface import Interface
from zope.interface import Invalid
from zope.schema import Field
Expand All @@ -26,15 +25,62 @@
from zope.schema.interfaces import IField


def superAdapter(specific_interface, adapter, objects, name=u''):
""" find the next most specific adapter """

# We are adjusting view object class to provide IForm rather than IEasyFormForm or IGroup to make
# one of the objects less specific. This allows us to find anotehr adapter other than our one. This allows us to
# find any custom adapters for any fields that we have overridden
new_obj = []
found = False
for obj in objects:
interfaces = list(providedBy(obj).interfaces())
try:
index = interfaces.index(specific_interface)
found = True
except ValueError:
pass
else:
super_inferface = interfaces[index + 1]

@implementer(super_inferface)
class Wrapper(object):
def __init__(self, view):
self.__view__ = view

def __getattr__(self, item):
return getattr(self.__view__, item)

obj = Wrapper(obj) # Make one class less specific
new_obj.append(obj)
if not found:
return None

provides = providedBy(adapter).declared[0]

return queryMultiAdapter(new_obj, provides, name=name)


@implementer(IValidator)
@adapter(IEasyForm, Interface, IEasyFormForm, IField, Interface)
class FieldExtenderValidator(z3c_validator.SimpleFieldValidator):

class FieldExtenderValidator(object):
""" z3c.form validator class for easyform fields in the default fieldset"""

def __init__(self, context, request, view, field, widget):
self.context = context
self.request = request
self.view = view
self.field = field
self.widget = widget

def validate(self, value):
""" Validate field by TValidator """
super(FieldExtenderValidator, self).validate(value)
# By default this will call SimpleFieldValidator.validator but allows for a fields
# custom validation adaptor to also be called such as recaptcha
_, _, view_interface, _, _ = self.__class__.__component_adapts__
validator = superAdapter(view_interface, self, (self.context, self.request, self.view, self.field, self.widget))
if validator is not None:
validator.validate(value)

efield = IFieldExtender(self.field)
validators = getattr(efield, "validators", [])
Expand All @@ -59,7 +105,6 @@ def validate(self, value):
@implementer(IValidator)
@adapter(IEasyForm, Interface, IGroup, IField, Interface)
class GroupFieldExtenderValidator(FieldExtenderValidator):

""" z3c.form validator class for easyform fields in fieldset groups """

pass
Expand All @@ -80,10 +125,19 @@ def __init__(self, context, request, view, field, widget):

def get(self):
""" get default value of field from TDefault """
fdefault = self.field.default
efield = IFieldExtender(self.field)
TDefault = getattr(efield, "TDefault", None)
return get_expression(self.context, TDefault) if TDefault else fdefault
if TDefault:
return get_expression(self.context, TDefault)

# see if there is another default adapter for this field instead
_, _, view_interface, _, _ = self.__class__.__component_adapts__
adapter = superAdapter(view_interface, self, (self.context, self.request, self.view, self.field, self.widget), name='default')
if adapter is not None:
return adapter.get()
else:
# TODO: this should have already been done by z3c.form.widget.update() so shouldn't be needed
return self.field.default


@implementer(IValue)
Expand Down
2 changes: 1 addition & 1 deletion src/collective/easyform/fields.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
<adapter
for="collective.easyform.interfaces.IEasyForm
*
collective.easyform.interfaces.IEasyFormForm
*
collective.easyform.interfaces.IReCaptcha
*"
provides="z3c.form.interfaces.IValidator"
Expand Down
11 changes: 11 additions & 0 deletions src/collective/easyform/tests/fixtures/fieldset_recaptcha.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<model xmlns:easyform="http://namespaces.plone.org/supermodel/easyform" xmlns:form="http://namespaces.plone.org/supermodel/form" xmlns:i18n="http://xml.zope.org/namespaces/i18n" xmlns:indexer="http://namespaces.plone.org/supermodel/indexer" xmlns:lingua="http://namespaces.plone.org/supermodel/lingua" xmlns:marshal="http://namespaces.plone.org/supermodel/marshal" xmlns:security="http://namespaces.plone.org/supermodel/security" xmlns:users="http://namespaces.plone.org/supermodel/users" xmlns="http://namespaces.plone.org/supermodel/schema">
<schema>
<fieldset name="fs1" label="Fieldset 1">
<field name="verification" type="collective.easyform.fields.ReCaptcha">
<description/>
<required>False</required>
<title>Verification</title>
</field>
</fieldset>
</schema>
</model>
15 changes: 15 additions & 0 deletions src/collective/easyform/tests/testValidators.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ def test_talvalidator2(self):


class LoadFixtureBase(base.EasyFormTestCase):
""" test validator in form outside of fieldset
The test methods are reused in TestFieldsetValidator.
They use the same field, except that one has it in a fieldset.
"""

schema_fixture = "single_field.xml"

def afterSetUp(self):
Expand Down Expand Up @@ -370,13 +376,22 @@ def test_no_answer(self):
request.method = "POST"
form = EasyFormForm(self.ff1, request)()
self.assertIn('The code you entered was wrong, please enter the new one.', form)
self.assertNotIn('Thanks for your input.', form)

def test_wrong(self):
data = {"verification": "123"}
request = self.LoadRequestForm(**data)
request.method = "POST"
form = EasyFormForm(self.ff1, request)()
self.assertIn('The code you entered was wrong, please enter the new one.', form)
self.assertNotIn('Thanks for your input.', form)


class TestFieldsetRecaptchaValidator(TestSingleRecaptchaValidator):
""" make sure it works inside a fieldset too
"""

schema_fixture = "fieldset_recaptcha.xml"


class DummyUpload(FileUpload):
Expand Down

0 comments on commit 5d18bce

Please sign in to comment.