diff --git a/composer.json b/composer.json index 59dc36ed53..198d508cc3 100644 --- a/composer.json +++ b/composer.json @@ -8,15 +8,15 @@ "repositories": [ { "type": "package", - "package": { - "name": "facebook/php-sdk", - "version": "3.2.3", - "source": { - "url": "https://github.com/facebookarchive/facebook-php-sdk.git", - "type": "git", - "reference": "3.2.3" - } - } + "package": { + "name": "facebook/php-sdk", + "version": "3.2.3", + "source": { + "url": "https://github.com/facebookarchive/facebook-php-sdk.git", + "type": "git", + "reference": "3.2.3" + } + } }, { "type": "package", @@ -100,7 +100,6 @@ "monolog/monolog": "~1.3", "mrclay/minify": "~2.1.6", "neutron/process-manager": "2.0.x-dev@dev", - "neutron/recaptcha": "~0.1.0", "neutron/silex-filesystem-provider": "~1.0", "neutron/silex-imagine-provider": "~0.1.0", "neutron/temporary-filesystem": "~2.1", diff --git a/composer.lock b/composer.lock index 7e0a50b305..3f4ba3637d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5c6ceff0275f1bf8ebe67d19405bc456", + "content-hash": "a155d517a61963f9dd96d3f3a2c22d21", "packages": [ { "name": "alchemy-fr/tcpdf-clone", @@ -4572,50 +4572,6 @@ ], "time": "2014-02-13T20:27:33+00:00" }, - { - "name": "neutron/recaptcha", - "version": "0.1.3", - "source": { - "type": "git", - "url": "https://github.com/romainneutron/ReCaptcha.git", - "reference": "f9de705c5b8631ec74353bb9f0b5077d50328ab2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/romainneutron/ReCaptcha/zipball/f9de705c5b8631ec74353bb9f0b5077d50328ab2", - "reference": "f9de705c5b8631ec74353bb9f0b5077d50328ab2", - "shasum": "" - }, - "require": { - "guzzle/guzzle": "~3.0" - }, - "require-dev": { - "silex/silex": "1.0.x-dev@dev", - "symfony/http-foundation": "~2.0" - }, - "suggest": { - "symfony/http-foundation": "To bind symfony request to ReCaptcha" - }, - "type": "library", - "autoload": { - "psr-0": { - "Neutron": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Romain Neutron", - "email": "imprec@gmail.com", - "homepage": "http://www.lickmychip.com/" - } - ], - "description": "ReCaptcha Client", - "time": "2013-02-14T13:42:00+00:00" - }, { "name": "neutron/signal-handler", "version": "1.0.1", diff --git a/config/configuration.sample.yml b/config/configuration.sample.yml index 8061444b83..6f3aefb3d1 100644 --- a/config/configuration.sample.yml +++ b/config/configuration.sample.yml @@ -161,9 +161,6 @@ border-manager: authentication: auto-create: templates: { } - captcha: - enabled: true - trials-before-display: 9 providers: facebook: enabled: false diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index 4a9b79d3d5..3f0868b689 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -98,7 +98,6 @@ use Monolog\Handler\RotatingFileHandler; use Monolog\Handler\StreamHandler; use Monolog\Logger; -use Neutron\ReCaptcha\ReCaptchaServiceProvider; use Psr\Log\LoggerInterface; use Silex\Application as SilexApplication; use Silex\Application\TranslationTrait; @@ -203,7 +202,6 @@ public function __construct($environment = null) $this->setupGeonames(); $this->register(new NotificationDelivererServiceProvider()); $this->register(new RepositoriesServiceProvider()); - $this->register(new ManipulatorServiceProvider()); $this->register(new TechnicalDataServiceProvider()); $this->register(new MediaSubdefServiceProvider()); $this->register(new CaptionServiceProvider()); @@ -211,17 +209,14 @@ public function __construct($environment = null) $this->register(new PhraseaVersionServiceProvider()); $this->register(new RandomGeneratorServiceProvider()); - $this->register(new ReCaptchaServiceProvider()); $this->register(new SubdefServiceProvider()); $this->register(new ZippyServiceProvider()); - $this->setupRecaptacha(); if ($this['configuration.store']->isSetup()) { $this->register(new SearchEngineServiceProvider()); $this->register(new BorderManagerServiceProvider()); } - $this->register(new SerializerServiceProvider()); $this->register(new ServiceControllerServiceProvider()); $this->register(new SwiftmailerServiceProvider()); @@ -261,6 +256,7 @@ public function __construct($environment = null) $this->register(new LocaleServiceProvider()); $this->setupEventDispatcher(); + $this->setupRecaptacha(); $this->register(new DataboxServiceProvider()); $this->register(new QueueServiceProvider()); @@ -464,7 +460,7 @@ public function getUnlockAccountData() */ public function requireCaptcha() { - if ($this['conf']->get(['registry', 'webservices', 'captcha-enabled'])) { + if ($this['conf']->get(['registry', 'webservices', 'captchas-enabled'])) { $this['session']->set('require_captcha', true); } @@ -658,12 +654,12 @@ private function setupForm() private function setupRecaptacha() { $this['recaptcha.public-key'] = $this->share(function (Application $app) { - if ($app['conf']->get(['registry', 'webservices', 'captcha-enabled'])) { + if ($app['conf']->get(['registry', 'webservices', 'captchas-enabled'])) { return $app['conf']->get(['registry', 'webservices', 'recaptcha-public-key']); } }); $this['recaptcha.private-key'] = $this->share(function (Application $app) { - if ($app['conf']->get(['registry', 'webservices', 'captcha-enabled'])) { + if ($app['conf']->get(['registry', 'webservices', 'captchas-enabled'])) { return $app['conf']->get(['registry', 'webservices', 'recaptcha-private-key']); } }); diff --git a/lib/Alchemy/Phrasea/Authentication/Phrasea/FailureManager.php b/lib/Alchemy/Phrasea/Authentication/Phrasea/FailureManager.php index 105ce6032b..6430b60e66 100644 --- a/lib/Alchemy/Phrasea/Authentication/Phrasea/FailureManager.php +++ b/lib/Alchemy/Phrasea/Authentication/Phrasea/FailureManager.php @@ -16,7 +16,7 @@ use Alchemy\Phrasea\Model\Repositories\AuthFailureRepository; use Doctrine\ORM\EntityManager; use Alchemy\Phrasea\Model\Entities\AuthFailure; -use Neutron\ReCaptcha\ReCaptcha; +use ReCaptcha\ReCaptcha; use Symfony\Component\HttpFoundation\Request; class FailureManager @@ -111,18 +111,22 @@ public function resetLockedFailureByUsername($username) public function checkFailures($username, Request $request) { $failures = $this->repository->findLockedFailuresMatching($username, $request->getClientIp()); + $captchaResp = $request->get('g-recaptcha-response'); if (0 === count($failures)) { return $this; } - if ($this->trials < count($failures) && $this->captcha->isSetup()) { - $response = $this->captcha->bind($request); - - if (!$response->isValid()) { + if ($this->trials <= count($failures)) { + if ($captchaResp === null) { throw new RequireCaptchaException('Too many failures, require captcha'); } + $response = $this->captcha->verify($captchaResp, $request->getClientIp()); + if (!$response->isSuccess()) { + throw new RequireCaptchaException('Please fill captcha'); + } + foreach ($failures as $failure) { $failure->setLocked(false); } diff --git a/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php b/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php index 84259f9609..9f661b766f 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php @@ -335,7 +335,7 @@ private function getGlobalValuesInformation() 'googleAnalyticsId' => $conf->get(['registry', 'general', 'analytics']), 'i18nWebService' => $conf->get(['registry', 'webservices', 'geonames-server']), 'recaptacha' => [ - 'active' => $conf->get(['registry', 'webservices', 'captcha-enabled']), + 'active' => $conf->get(['registry', 'webservices', 'captchas-enabled']), 'publicKey' => $conf->get(['registry', 'webservices', 'recaptcha-public-key']), 'privateKey' => $conf->get(['registry', 'webservices', 'recaptcha-private-key']), ], diff --git a/lib/Alchemy/Phrasea/Controller/Root/LoginController.php b/lib/Alchemy/Phrasea/Controller/Root/LoginController.php index 3da853c680..e437aa18ec 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/LoginController.php +++ b/lib/Alchemy/Phrasea/Controller/Root/LoginController.php @@ -56,8 +56,9 @@ use Alchemy\Phrasea\Notification\Receiver; use DateTime; use Doctrine\ORM\EntityManagerInterface; -use Neutron\ReCaptcha\ReCaptcha; use RandomLib\Generator; +use ReCaptcha\ReCaptcha; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -94,7 +95,7 @@ public function getDefaultTemplateVariables(Request $request) $conf = $this->getConf(); $browser = $this->getBrowser(); - + return [ 'last_publication_items' => $items, 'instance_title' => $conf->get(['registry', 'general', 'title']), @@ -108,6 +109,7 @@ public function getDefaultTemplateVariables(Request $request) 'current_url' => $request->getUri(), 'flash_types' => $this->app->getAvailableFlashTypes(), 'recaptcha_display' => $this->app->isCaptchaRequired(), + 'recaptcha_enabled' => $conf->get(['registry', 'webservices', 'captchas-enabled']), 'unlock_usr_id' => $this->app->getUnlockAccountData(), 'guest_allowed' => $this->app->isGuestAllowed(), 'register_enable' => $this->getRegistrationManager()->isRegistrationEnabled(), @@ -187,6 +189,14 @@ public function doRegistration(Request $request) $data = $form->getData(); $provider = null; + + if(isset($requestData['g-recaptcha-response']) && $requestData['g-recaptcha-response'] == "") { + $this->app->addFlash('error', $this->app->trans('Please fill captcha')); + + $dateError = new FormError(""); + $form->get('captcha')->addError($dateError); + } + if ($data['provider-id']) { try { $provider = $this->findProvider($data['provider-id']); @@ -222,13 +232,6 @@ public function doRegistration(Request $request) try { if ($form->isValid()) { - $captcha = $this->getRecaptcha()->bind($request); - - $conf = $this->getConf(); - if ($conf->get(['registry', 'webservices', 'captcha-enabled']) && !$captcha->isValid()) { - throw new FormProcessingException($this->app->trans('Invalid captcha answer.')); - } - $registrationService = $this->getRegistrationService(); $providerId = isset($data['provider-id']) ? $data['provider-id'] : null; $selectedCollections = isset($data['collections']) ? $data['collections'] : null; @@ -428,6 +431,14 @@ public function forgotPassword(Request $request) try { if ('POST' === $request->getMethod()) { $form->handleRequest($request); + $requestData = $request->request->all(); + + if(isset($requestData['g-recaptcha-response']) && $requestData['g-recaptcha-response'] == "") { + $this->app->addFlash('error', $this->app->trans('Please fill captcha')); + + $dataError = new FormError(""); + $form->get('captcha')->addError($dataError); + } if ($form->isValid()) { $data = $form->getData(); @@ -832,7 +843,7 @@ private function doAuthentication(Request $request, FormInterface $form, $redire ); } catch (RequireCaptchaException $e) { $this->app->requireCaptcha(); - $this->app->addFlash('warning', $this->app->trans('Please fill the captcha')); + $this->app->addFlash('warning', $e->getMessage()); throw new AuthenticationException(call_user_func($redirector, $params)); } catch (AccountLockedException $e) { diff --git a/lib/Alchemy/Phrasea/Core/Configuration/RegistryFormManipulator.php b/lib/Alchemy/Phrasea/Core/Configuration/RegistryFormManipulator.php index 66d8808feb..f832bbbdc0 100644 --- a/lib/Alchemy/Phrasea/Core/Configuration/RegistryFormManipulator.php +++ b/lib/Alchemy/Phrasea/Core/Configuration/RegistryFormManipulator.php @@ -154,6 +154,7 @@ private function getDefaultData(array $config) 'captchas-enabled' => false, 'recaptcha-public-key' => '', 'recaptcha-private-key' => '', + 'trials-before-display' => 5, ], 'executables' => [ 'h264-streaming-enabled' => false, diff --git a/lib/Alchemy/Phrasea/Core/Provider/AuthenticationManagerServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/AuthenticationManagerServiceProvider.php index eb99d31b07..22c5945f79 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/AuthenticationManagerServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/AuthenticationManagerServiceProvider.php @@ -1,5 +1,4 @@ isSetup() && $app['conf']->get(['registry', 'webservices', 'recaptcha-private-key']) !== "") { + $app['recaptcha'] = $app->share(function (Application $app) { + return new ReCaptcha($app['conf']->get(['registry', 'webservices', 'recaptcha-private-key'])); + }); + } + $app['authentication.persistent-manager'] = $app->share(function (Application $app) { return new CookieManager($app['auth.password-encoder'], $app['repo.sessions'], $app['browser']); }); @@ -162,8 +166,7 @@ public function register(Application $app) }); $app['auth.native.failure-manager'] = $app->share(function (Application $app) { - $authConf = $app['conf']->get(['authentication', 'captcha']); - + $authConf = $app['conf']->get(['registry', 'webservices']); return new FailureManager($app['repo.auth-failures'], $app['orm.em'], $app['recaptcha'], isset($authConf['trials-before-display']) ? $authConf['trials-before-display'] : 9); }); @@ -172,9 +175,9 @@ public function register(Application $app) }); $app['auth.native'] = $app->share(function (Application $app) { - $authConf = $app['conf']->get('authentication'); + $authConf = $app['conf']->get(['registry', 'webservices']); - if ($authConf['captcha']['enabled']) { + if ($authConf['captchas-enabled']) { return new FailureHandledNativeAuthentication( $app['auth.password-checker'], $app['auth.native.failure-manager'] diff --git a/lib/Alchemy/Phrasea/Core/Version.php b/lib/Alchemy/Phrasea/Core/Version.php index 64c84591bf..80bf69bb3b 100644 --- a/lib/Alchemy/Phrasea/Core/Version.php +++ b/lib/Alchemy/Phrasea/Core/Version.php @@ -17,7 +17,7 @@ class Version * @var string */ - private $number = '4.1.7-rc3'; + private $number = '4.1.7-rc4'; /** * @var string diff --git a/lib/Alchemy/Phrasea/Form/Configuration/WebservicesFormType.php b/lib/Alchemy/Phrasea/Form/Configuration/WebservicesFormType.php index e55ed4946b..d27db8c0dd 100644 --- a/lib/Alchemy/Phrasea/Form/Configuration/WebservicesFormType.php +++ b/lib/Alchemy/Phrasea/Form/Configuration/WebservicesFormType.php @@ -17,6 +17,7 @@ use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Validator\Constraints as Assert; class WebservicesFormType extends AbstractType { @@ -50,6 +51,18 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->add('recaptcha-private-key', TextType::class, [ 'label' => 'Recaptcha private key', ]); + $builder->add('trials-before-display', 'integer', [ + 'label' => 'Trials before display captcha', + 'constraints' => array( + new Assert\NotBlank(), + new Assert\Type('integer'), + new Assert\Regex(array( + 'pattern' => '/^[0-9]\d*$/', + 'message' => 'Please use only positive numbers.' + ) + ), + ), + ]); } public function getName() diff --git a/lib/Alchemy/Phrasea/Form/Login/PhraseaAuthenticationForm.php b/lib/Alchemy/Phrasea/Form/Login/PhraseaAuthenticationForm.php index 2377d0816d..d918a8da4f 100644 --- a/lib/Alchemy/Phrasea/Form/Login/PhraseaAuthenticationForm.php +++ b/lib/Alchemy/Phrasea/Form/Login/PhraseaAuthenticationForm.php @@ -83,8 +83,10 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) if (isset($this->app['phraseanet.configuration']['auth-csrf-protection'])) { $csrf = (Boolean) $this->app['phraseanet.configuration']['auth-csrf-protection']; } + $resolver->setDefaults(array( 'csrf_protection' => $csrf, + 'allow_extra_fields' => true, )); } } diff --git a/lib/Alchemy/Phrasea/Form/Login/PhraseaForgotPasswordForm.php b/lib/Alchemy/Phrasea/Form/Login/PhraseaForgotPasswordForm.php index dddf5cab81..348fa416ab 100644 --- a/lib/Alchemy/Phrasea/Form/Login/PhraseaForgotPasswordForm.php +++ b/lib/Alchemy/Phrasea/Form/Login/PhraseaForgotPasswordForm.php @@ -14,6 +14,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\Validator\Constraints as Assert; class PhraseaForgotPasswordForm extends AbstractType @@ -24,9 +25,20 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'label' => 'E-mail', 'required' => true, 'constraints' => [ - new Assert\NotBlank(), - new Assert\Email() - ], + new Assert\NotBlank(), + new Assert\Email() + ], + ]); + + $builder->add('captcha', 'hidden', [ + 'error_bubbling' => false + ]); + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults([ + 'allow_extra_fields' => true, ]); } diff --git a/lib/Alchemy/Phrasea/Form/Login/PhraseaRegisterForm.php b/lib/Alchemy/Phrasea/Form/Login/PhraseaRegisterForm.php index 39b5e33def..1cd969cd59 100644 --- a/lib/Alchemy/Phrasea/Form/Login/PhraseaRegisterForm.php +++ b/lib/Alchemy/Phrasea/Form/Login/PhraseaRegisterForm.php @@ -23,6 +23,7 @@ use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; class PhraseaRegisterForm extends AbstractType { @@ -128,6 +129,16 @@ public function buildForm(FormBuilderInterface $builder, array $options) ); } } + $builder->add('captcha', 'hidden', [ + 'error_bubbling' => false + ]); + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults([ + 'allow_extra_fields' => true, + ]); } public function getName() diff --git a/lib/classes/patch/417RC4PHRAS1718.php b/lib/classes/patch/417RC4PHRAS1718.php new file mode 100644 index 0000000000..582b0c46cf --- /dev/null +++ b/lib/classes/patch/417RC4PHRAS1718.php @@ -0,0 +1,103 @@ +release; + } + + /** + * {@inheritdoc} + */ + public function getDoctrineMigrations() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function require_all_upgrades() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function concern() + { + return $this->concern; + } + + /** + * {@inheritdoc} + */ + public function apply(base $base, Application $app) + { + if ($base->get_base_type() === base::DATA_BOX) { + $this->patch_databox($base, $app); + } elseif ($base->get_base_type() === base::APPLICATION_BOX) { + $this->patch_appbox($base, $app); + } + + return true; + } + + private function patch_databox(databox $databox, Application $app) + { + } + + private function patch_appbox(base $appbox, Application $app) + { + /** @var PropertyAccess $conf */ + $conf = $app['conf']; + + // add captcha configuration + + if (!$conf->has(['registry', 'webservices', 'captchas-enabled'])) { + if ($conf->has(['registry', 'webservices', 'captcha-enabled'])) { + $conf->set(['registry', 'webservices', 'captchas-enabled'], $conf->get(['registry', 'webservices', 'captcha-enabled'])); + } else { + if ($conf->has(['authentication', 'captcha', 'enabled'])) { + $conf->set(['registry', 'webservices', 'captchas-enabled'], $conf->get(['authentication', 'captcha', 'enabled'])); + } else { + $conf->set(['registry', 'webservices', 'captchas-enabled'], false); + } + } + } + + if (!$conf->has(['registry', 'webservices', 'trials-before-display'])) { + if ($conf->has(['authentication', 'captcha', 'trials-before-display'])) { + $conf->set(['registry', 'webservices', 'trials-before-display'], $conf->get(['authentication', 'captcha', 'trials-before-display'])); + } else { + $conf->set(['registry', 'webservices', 'trials-before-display'], 5); + } + } + + if (!$conf->has(['registry', 'webservices', 'recaptcha-public-key'])) { + $conf->set(['registry', 'webservices', 'recaptcha-public-key'], ''); + } + + if (!$conf->has(['registry', 'webservices', 'recaptcha-private-key'])) { + $conf->set(['registry', 'webservices', 'recaptcha-private-key'], ''); + } + + // unused configuration section + $conf->remove(['authentication', 'captcha']); + $conf->remove(['registry', 'webservices', 'captcha-enabled']); + } +} diff --git a/lib/conf.d/configuration.yml b/lib/conf.d/configuration.yml index b7ea80c9f8..c0819df646 100644 --- a/lib/conf.d/configuration.yml +++ b/lib/conf.d/configuration.yml @@ -183,9 +183,6 @@ user_account: authentication: auto-create: templates: { } - captcha: - enabled: true - trials-before-display: 9 providers: facebook: enabled: false diff --git a/templates/web/common/macro_captcha.html.twig b/templates/web/common/macro_captcha.html.twig index f1168ed1bd..12d231c12d 100644 --- a/templates/web/common/macro_captcha.html.twig +++ b/templates/web/common/macro_captcha.html.twig @@ -1,69 +1,9 @@ {% macro captcha() %} - -