Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

recaptcha inside a fieldset doesn't validate and allows any submission #190

Merged
merged 23 commits into from
Oct 2, 2019
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
53 changes: 44 additions & 9 deletions src/collective/easyform/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
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 IGroup, IForm
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 Interface
Expand All @@ -26,15 +25,43 @@
from zope.schema.interfaces import IField


def LessSpecificInterfaceWrapper(view, interface):
""" rewrap an adapter so it its class implements a different interface """

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

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

return Wrapper(view)


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

class FieldExtenderValidator(object):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems nicer to keep the SimpleFieldValidator as base. You can at least skip the __init__ then, because it is the same.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got rid of it to reduce the confusion as to why super wasn't being called.

""" 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you still want the super call. The base class seems to do some useful things.
I fear that we could break other use cases when we do not use the base class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The StrictSimpleFieldValidator is by default registered for all fields so still gets called in the multi adapter call. This however allows someone else to prevent that happening if they want. Also it means this code isn't run twice. BTW. The tests fail without StrictSimpleFieldValidator validator being called.
I'll remove StrictSimpleFieldValidator as a subclass and put in a comment to make it more clear

view = LessSpecificInterfaceWrapper(self.view, IForm)
# view now doesn't implement IEasyFormForm so we can call another less specific validation adapter
# that might exist for this field. The above line prevents a loop.
# By default this will call SimpleFieldValidator.validator but allows for special fields
# custom validation to also be called

validator = queryMultiAdapter((self.context, self.request, view, self.field, self.widget), IValidator)
if validator is not None:
validator.validate(value)

efield = IFieldExtender(self.field)
validators = getattr(efield, "validators", [])
Expand All @@ -59,7 +86,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 +106,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 = LessSpecificInterfaceWrapper(self.view, IForm)
adapter = queryMultiAdapter((self.context, self.request, view, self.field, self.widget), IValue, 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