# HG changeset patch # User Thierry Florac # Date 1571244105 -7200 # Node ID 87bcbf37ad6d266a83b8baebeef4b5c6182d350e # Parent 3f206017a2c0a5114681dff58a858f6f2844f80a Updated form's captcha and proxy management by adding default settings to forms manager diff -r 3f206017a2c0 -r 87bcbf37ad6d src/pyams_content/shared/form/__init__.py --- a/src/pyams_content/shared/form/__init__.py Mon Oct 07 14:04:31 2019 +0200 +++ b/src/pyams_content/shared/form/__init__.py Wed Oct 16 18:41:45 2019 +0200 @@ -19,10 +19,12 @@ from pyams_content.features.review.interfaces import IReviewTarget from pyams_content.shared.common import IWfSharedContentFactory, SharedContent, WfSharedContent, WfSharedContentChecker, \ register_content_type -from pyams_content.shared.form.interfaces import FORM_CONTENT_NAME, FORM_CONTENT_TYPE, IForm, IFormFieldContainer, \ - IFormFieldContainerTarget, IFormHandler, IWfForm, IWfFormFactory +from pyams_content.shared.form.interfaces import FORM_CONTENT_NAME, FORM_CONTENT_TYPE, IForm, \ + IFormFieldContainer, \ + IFormFieldContainerTarget, IFormHandler, IWfForm, IWfFormFactory, IFormsManager from pyams_utils.adapter import adapter_config from pyams_utils.registry import get_global_registry +from pyams_utils.traversing import get_parent __docformat__ = 'restructuredtext' @@ -45,10 +47,9 @@ submit_label = FieldProperty(IWfForm['submit_label']) submit_message = FieldProperty(IWfForm['submit_message']) _handler = FieldProperty(IWfForm['handler']) - use_captcha = FieldProperty(IWfForm['use_captcha']) + override_captcha = FieldProperty(IWfForm['override_captcha']) client_captcha_key = FieldProperty(IWfForm['client_captcha_key']) server_captcha_key = FieldProperty(IWfForm['server_captcha_key']) - captcha_proxy = FieldProperty(IWfForm['captcha_proxy']) rgpd_consent = FieldProperty(IWfForm['rgpd_consent']) rgpd_warning = FieldProperty(IWfForm['rgpd_warning']) rgpd_user_rights = FieldProperty(IWfForm['rgpd_user_rights']) @@ -86,6 +87,17 @@ registry = get_global_registry() return registry.queryUtility(IFormHandler, name=handler) + def get_captcha_settings(self): + if self.override_captcha: + return { + 'use_captcha': bool(self.client_captcha_key), + 'client_key': self.client_captcha_key, + 'server_key': self.server_captcha_key + } + else: + manager = get_parent(self, IFormsManager) + return manager.get_captcha_settings() + register_content_type(WfForm, shared_content=False) diff -r 3f206017a2c0 -r 87bcbf37ad6d src/pyams_content/shared/form/interfaces.py --- a/src/pyams_content/shared/form/interfaces.py Mon Oct 07 14:04:31 2019 +0200 +++ b/src/pyams_content/shared/form/interfaces.py Wed Oct 16 18:41:45 2019 +0200 @@ -13,8 +13,9 @@ from zope.annotation.interfaces import IAttributeAnnotatable from zope.container.constraints import containers, contains from zope.container.interfaces import IContained, IContainer -from zope.interface import Attribute, Interface -from zope.schema import Bool, Choice, TextLine +from zope.interface import Attribute, Interface, invariant +from zope.interface.interfaces import Invalid +from zope.schema import Bool, Choice, TextLine, Int, Password from pyams_content.component.paragraph import IBaseParagraph from pyams_content.shared.common.interfaces import ISharedContent, ISharedToolPortalContext, \ @@ -37,6 +38,67 @@ class IFormsManager(ISharedToolPortalContext): """Forms manager interface""" + use_captcha = Bool(title=_("Use captcha?"), + description=_("Set default captcha settings"), + required=True, + default=False) + + default_captcha_client_key = TextLine(title=_("Default captcha site key"), + description=_("This key is included into HTML code and " + "submitted with form data"), + required=False) + + default_captcha_server_key = TextLine(title=_("Default captcha secret key"), + description=_("This key is used to communicate with " + "Google's reCaptcha services"), + required=False) + + def get_captcha_settings(self): + """Get default captcha settings""" + + use_proxy = Bool(title=_("Use proxy server?"), + description=_("If a proxy server is required to access recaptcha services, " + "please set them here"), + required=True, + default=False) + + proxy_proto = Choice(title=_("Protocol"), + description=_("If your server is behind a proxy, please set it's " + "protocol here; HTTPS support is required for reCaptcha"), + required=False, + values=('http', 'https'), + default='http') + + proxy_host = TextLine(title=_("Host name"), + description=_("If your server is behind a proxy, please set it's " + "address here; captcha verification requires HTTPS " + "support..."), + required=False) + + proxy_port = Int(title=_("Port number"), + description=_("If your server is behind a proxy, plase set it's port " + "number here"), + required=False, + default=8080) + + proxy_username = TextLine(title=_("Username"), + description=_("If your proxy server requires authentication, " + "please set username here"), + required=False) + + proxy_password = Password(title=_("Password"), + description=_("If your proxy server requires authentication, " + "please set password here"), + required=False) + + proxy_only_from = TextLine(title=_("Use proxy only from"), + description=_("If proxy usage is restricted to several domains " + "names, you can set them here, separated by comas"), + required=False) + + def get_proxy_url(self, request): + """Get proxy server URL""" + class IFormsManagerFactory(Interface): """Forms manager factory interface""" @@ -175,10 +237,10 @@ description=_("Select how form data is transmitted"), vocabulary='PyAMS form handlers') - use_captcha = Bool(title=_("Use captcha?"), - description=_("If 'yes', a captcha will be added automatically to the form"), - required=False, - default=True) + override_captcha = Bool(title=_("Override captcha settings?"), + description=_("If 'yes', you can define custom captcha keys here"), + required=False, + default=True) client_captcha_key = TextLine(title=_("Site key"), description=_("This key is included into HTML code and submitted " @@ -190,11 +252,8 @@ "reCaptcha services"), required=False) - captcha_proxy = TextLine(title=_("Recaptcha proxy"), - description=_("If your server is behind a proxy, please set it's " - "address here; captcha verification requires HTTPS " - "support..."), - required=False) + def get_captcha_settings(self): + """Get form captcha settings""" rgpd_consent = Bool(title=_("Required RGPD consent?"), description=_("If 'yes', an RGPD compliance warning will be displayed " diff -r 3f206017a2c0 -r 87bcbf37ad6d src/pyams_content/shared/form/manager.py --- a/src/pyams_content/shared/form/manager.py Mon Oct 07 14:04:31 2019 +0200 +++ b/src/pyams_content/shared/form/manager.py Wed Oct 16 18:41:45 2019 +0200 @@ -14,6 +14,7 @@ from zope.component.interfaces import ISite from zope.interface import implementer from zope.lifecycleevent.interfaces import IObjectAddedEvent +from zope.schema.fieldproperty import FieldProperty from pyams_content.component.paragraph import IParagraphFactorySettings from pyams_content.shared.common.interfaces import ISharedContentFactory @@ -36,6 +37,46 @@ shared_content_type = FORM_CONTENT_TYPE shared_content_menu = False + use_captcha = FieldProperty(IFormsManager['use_captcha']) + default_captcha_client_key = FieldProperty(IFormsManager['default_captcha_client_key']) + default_captcha_server_key = FieldProperty(IFormsManager['default_captcha_server_key']) + use_proxy = FieldProperty(IFormsManager['use_proxy']) + proxy_proto = FieldProperty(IFormsManager['proxy_proto']) + proxy_host = FieldProperty(IFormsManager['proxy_host']) + proxy_port = FieldProperty(IFormsManager['proxy_port']) + proxy_username = FieldProperty(IFormsManager['proxy_username']) + proxy_password = FieldProperty(IFormsManager['proxy_password']) + proxy_only_from = FieldProperty(IFormsManager['proxy_only_from']) + + def get_captcha_settings(self): + result = { + 'use_captcha': False, + 'client_key': None, + 'server_key': None + } + if self.use_captcha: + result.update({ + 'use_captcha': True, + 'client_key': self.default_captcha_client_key, + 'server_key': self.default_captcha_server_key + }) + return result + + def get_proxy_url(self, request): + if self.use_proxy: + # check selected domains names + if self.proxy_only_from: + domains = map(str.strip, self.proxy_only_from.split(',')) + if request.host not in domains: + return None + return '{}://{}{}:{}/'.format(self.proxy_proto, + '{}{}{}@'.format(self.proxy_username, + ':' if self.proxy_password else '', + self.proxy_password or '') + if self.proxy_username else '', + self.proxy_host, + self.proxy_port) + @utility_config(provides=IFormsManagerFactory) class FormsManagerFactory(object): diff -r 3f206017a2c0 -r 87bcbf37ad6d src/pyams_content/shared/form/zmi/manager.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/shared/form/zmi/manager.py Wed Oct 16 18:41:45 2019 +0200 @@ -0,0 +1,91 @@ +# +# Copyright (c) 2008-2019 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +from pyramid.events import subscriber +from z3c.form import field +from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget +from z3c.form.interfaces import IDataExtractedEvent +from zope.interface import Invalid + +from pyams_content.shared.common.zmi.manager import SharedToolPropertiesEditForm +from pyams_content.shared.form.interfaces import IFormsManager +from pyams_form.group import NamedWidgetsGroup +from pyams_form.interfaces.form import IInnerSubForm +from pyams_skin.layer import IPyAMSLayer +from pyams_utils.adapter import adapter_config +from pyams_zmi.form import InnerAdminEditForm + + +__docformat__ = 'restructuredtext' + +from pyams_content import _ + + +@adapter_config(name='captcha-settings', + context=(IFormsManager, IPyAMSLayer, SharedToolPropertiesEditForm), + provides=IInnerSubForm) +class FormManagerCaptchaSettingsEditForm(InnerAdminEditForm): + """Form manager captcha settings edit form""" + + prefix = 'captcha_properties.' + + legend = _("Captcha settings") + fieldset_class = 'bordered no-x-margin margin-y-10' + + fields = field.Fields(IFormsManager).select('use_captcha', + 'default_captcha_client_key', + 'default_captcha_server_key', + 'use_proxy', + 'proxy_proto', + 'proxy_host', + 'proxy_port', + 'proxy_username', + 'proxy_password', + 'proxy_only_from') + fields['use_captcha'].widgetFactory = SingleCheckBoxFieldWidget + fields['use_proxy'].widgetFactory = SingleCheckBoxFieldWidget + + weight = 1 + + def updateGroups(self): + self.add_group(NamedWidgetsGroup(self, 'captcha', self.widgets, + ('use_captcha', 'default_captcha_client_key', + 'default_captcha_server_key'), + fieldset_class='inner bordered', + legend=_("Use captcha"), + css_class='inner', + switch=True, + checkbox_switch=True, + checkbox_field=IFormsManager['use_captcha'])) + self.add_group(NamedWidgetsGroup(self, 'proxy', self.widgets, + ('use_proxy', 'proxy_proto', 'proxy_host', + 'proxy_port', 'proxy_username', 'proxy_password', + 'proxy_only_from'), + fieldset_class='inner bordered', + legend=_("Use proxy server"), + css_class='inner', + switch=True, + checkbox_switch=True, + checkbox_field=IFormsManager['use_proxy'])) + super(FormManagerCaptchaSettingsEditForm, self).updateGroups() + + +@subscriber(IDataExtractedEvent, form_selector=FormManagerCaptchaSettingsEditForm) +def check_form_captcha_data(event): + """Check captcha form input data""" + data = event.data + if data.get('use_captcha') and not (data.get('default_captcha_client_key') and + data.get('default_captcha_server_key')): + event.form.widgets.errors += (Invalid(_("You must define client and server key to " + "activate a captcha")), ) + if data.get('use_proxy') and not data.get('proxy_host'): + event.form.widgets.errors += (Invalid(_("You must define hostname to use a proxy")), ) diff -r 3f206017a2c0 -r 87bcbf37ad6d src/pyams_content/shared/form/zmi/properties.py --- a/src/pyams_content/shared/form/zmi/properties.py Mon Oct 07 14:04:31 2019 +0200 +++ b/src/pyams_content/shared/form/zmi/properties.py Wed Oct 16 18:41:45 2019 +0200 @@ -58,10 +58,10 @@ fields = field.Fields(IWfForm).select('form_header', 'user_title', 'auth_only', 'submit_label', 'submit_message', 'handler', - 'use_captcha', 'client_captcha_key', - 'server_captcha_key', 'captcha_proxy', + 'override_captcha', 'client_captcha_key', + 'server_captcha_key', 'rgpd_consent', 'rgpd_warning', 'rgpd_user_rights') - fields['use_captcha'].widgetFactory = SingleCheckBoxFieldWidget + fields['override_captcha'].widgetFactory = SingleCheckBoxFieldWidget fields['rgpd_consent'].widgetFactory = SingleCheckBoxFieldWidget weight = 1 @@ -96,14 +96,14 @@ ('form_header', 'user_title', 'auth_only', 'submit_label', 'submit_message', 'handler'))) self.add_group(NamedWidgetsGroup(self, 'captcha', self.widgets, - ('use_captcha', 'client_captcha_key', - 'server_captcha_key', 'captcha_proxy'), + ('override_captcha', 'client_captcha_key', + 'server_captcha_key'), fieldset_class='inner bordered', - legend=_("Add captcha"), + legend=_("Override default captcha settings"), css_class='inner', switch=True, checkbox_switch=True, - checkbox_field=IWfForm['use_captcha'])) + checkbox_field=IWfForm['override_captcha'])) self.add_group(NamedWidgetsGroup(self, 'rgpd', self.widgets, ('rgpd_consent', 'rgpd_warning', 'rgpd_user_rights'), fieldset_class='inner bordered',