diff --git a/src/AbstractAuraForm.php b/src/AbstractAuraForm.php deleted file mode 100644 index d02dae7..0000000 --- a/src/AbstractAuraForm.php +++ /dev/null @@ -1,116 +0,0 @@ -builder = $builder; - $this->filter = $filter; - $this->options = $options; - $this->helper = $factory->newInstance(); - } - - public function __construct() - { - } - - /** - * @\Ray\Di\Di\PostConstruct - */ - public function postConstruct() - { - $this->init(); - if ($this->antiCsrf instanceof AntiCsrfInterface) { - $this->setAntiCsrf($this->antiCsrf); - } - } - - /** - * @var HelperLocator - */ - protected $helper; - - /** - * HTML - * - * @var string - */ - protected $string = '
'; - - /** - * @param AntiCsrfInterface $antiCsrf - */ - public function setCsrf(AntiCsrfInterface $antiCsrf) - { - $this->setAntiCsrf($antiCsrf); - } - - /** - * @inheritdoc - */ - public function input($input) - { - return $this->helper->input($this->get($input)); - } - - /** - * @inheritdoc - */ - public function error($input, $format = '%s', $layout = '%s') - { - $errorMessages = $this->getFilter()->getMessages($input); - array_filter($errorMessages, function (&$item) use ($format) { - $item = sprintf($format, $item); - }); - $errors = implode('', $errorMessages); - - return sprintf($layout, $errors); - } - - /** - * @param array $attr Attributes for the form tag. - * - * @return string - * - * @throws \Aura\Html\Exception\HelperNotFound - * @throws \Aura\Input\Exception\NoSuchInput - */ - public function form($attr = []) - { - $form = $this->helper->form($attr); - if (isset($this->inputs['__csrf_token'])) { - $form .= $this->helper->input($this->get('__csrf_token')); - } - - return $form; - } -} diff --git a/src/AbstractForm.php b/src/AbstractForm.php index 911c2e1..1294bad 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -11,11 +11,11 @@ use Aura\Html\HelperLocator; use Aura\Html\HelperLocatorFactory; use Aura\Input\AntiCsrfInterface; -use Aura\Input\Builder; use Aura\Input\BuilderInterface; -use Aura\Input\Form; +use Aura\Input\Fieldset; +use Ray\WebFormModule\Exception\CsrfViolationException; -abstract class AbstractForm extends Form implements FormInterface +abstract class AbstractForm extends Fieldset implements FormInterface { /** * @var SubjectFilter @@ -45,25 +45,22 @@ abstract class AbstractForm extends Form implements FormInterface * @\Ray\Di\Di\Inject */ public function setBaseDependencies( - BuilderInterface $builder = null, - FilterFactory $filterFactory = null, - HelperLocatorFactory $helperFactory = null + BuilderInterface $builder, + FilterFactory $filterFactory, + HelperLocatorFactory $helperFactory ) { - $this->builder = $builder ?: new Builder; - $this->filter = $filterFactory ? $filterFactory->newSubjectFilter() : (new FilterFactory)->newSubjectFilter(); - $this->helper = $helperFactory ? $helperFactory->newInstance() : (new HelperLocatorFactory)->newInstance(); + $this->builder = $builder; + $this->filter = $filterFactory->newSubjectFilter(); + $this->helper = $helperFactory->newInstance(); } public function __construct() { } - /** - * @param AntiCsrfInterface $antiCsrf - */ - public function setCsrf(AntiCsrfInterface $antiCsrf) + public function setAntiCsrf(AntiCsrfInterface $antiCsrf) { - $this->setAntiCsrf($antiCsrf); + $this->antiCsrf = $antiCsrf; } /** @@ -73,7 +70,7 @@ public function postConstruct() { $this->init(); if ($this->antiCsrf instanceof AntiCsrfInterface) { - $this->setAntiCsrf($this->antiCsrf); + $this->antiCsrf->setField($this); } } @@ -131,29 +128,39 @@ public function form($attr = []) */ public function apply(array $data) { + if ($this->antiCsrf && ! $this->antiCsrf->isValid($data)) { + throw new CsrfViolationException; + } $isValid = $this->filter->apply($data); return $isValid; } /** - * Gets the filter messages. - * - * @param string $name The input name to get the filter message for; if - * empty, gets all messages for all inputs. + * Returns all failure messages for all fields. * - * @return array The filter messages. + * @return array */ - public function getMessages($name = null) + public function getFailureMessages() { $messages = $this->filter->getFailures()->getMessages(); - if ($name && isset($messages[$name])) { - return $messages[$name]; - } return $messages; } + + /** + * + * Returns all the fields collection + * + * @return \ArrayIterator + * + */ + public function getIterator() + { + return new \ArrayIterator($this->inputs); + } + public function __clone() { $this->filter = clone $this->filter; diff --git a/src/AntiCsrf.php b/src/AntiCsrf.php index d6e6150..6537002 100644 --- a/src/AntiCsrf.php +++ b/src/AntiCsrf.php @@ -16,14 +16,31 @@ final class AntiCsrf implements AntiCsrfInterface const TOKEN_KEY = '__csrf_token'; + /** + * @var bool + */ + private $isCli; + + /** + * $_POST + * + * @var array + */ + private $post; + /** * @var Session */ private $session; - public function __construct(Session $session) + /** + * @param Session $session + * @param bool|null $isCli +s */ + public function __construct(Session $session, $isCli = null) { $this->session = $session; + $this->isCli = is_bool($isCli) ? $isCli : PHP_SAPI === 'cli'; } public function setField(Fieldset $fieldset) @@ -39,19 +56,19 @@ public function setField(Fieldset $fieldset) */ public function isValid(array $data) { - if (PHP_SAPI === 'cli') { + if ($this->isCli) { return true; } - if (isset($_POST[self::TOKEN_KEY])) { - $data[self::TOKEN_KEY] = $_POST[self::TOKEN_KEY]; - } return isset($data[self::TOKEN_KEY]) && $data[self::TOKEN_KEY] == $this->getToken(); } + /** + * @return string + */ private function getToken() { - $value = PHP_SAPI === 'cli' ? self::TEST_TOKEN : $this->session->getCsrfToken()->getValue(); + $value = $this->isCli ? self::TEST_TOKEN : $this->session->getCsrfToken()->getValue(); return $value; } diff --git a/src/AuraInputInterceptor.php b/src/AuraInputInterceptor.php index 67f1af9..a61e59d 100644 --- a/src/AuraInputInterceptor.php +++ b/src/AuraInputInterceptor.php @@ -13,7 +13,6 @@ use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\Exception\InvalidArgumentException; use Ray\WebFormModule\Exception\InvalidFormPropertyException; -use Ray\WebFormModule\Exception\LogicException; class AuraInputInterceptor implements MethodInterceptor { @@ -48,7 +47,7 @@ public function invoke(MethodInvocation $invocation) $formValidation = $this->reader->getMethodAnnotation($invocation->getMethod(), FormValidation::class); $form = $this->getFormProperty($formValidation, $object); $data = $object instanceof SubmitInterface ? $object->submit() : $this->getNamedArguments($invocation); - $isValid = $this->isValidForm($data, $form); + $isValid = $this->isValid($data, $form); if ($isValid === true) { // validation success return $invocation->proceed(); @@ -85,22 +84,11 @@ private function getNamedArguments(MethodInvocation $invocation) * * @throws \Aura\Input\Exception\CsrfViolation */ - public function isValidForm(array $submit, Form $form) + public function isValid(array $submit, AbstractForm $form) { - if ($form instanceof AbstractAuraForm) { - $form->fill($submit); - $isValid = $form->filter(); + $isValid = $form->apply($submit); - return $isValid; - } - - if ($form instanceof AbstractForm) { - $isValid = $form->apply($submit); - - return $isValid; - } - - throw new LogicException('invalid form type'); + return $isValid; } /** @@ -109,7 +97,7 @@ public function isValidForm(array $submit, Form $form) * @param FormValidation $formValidation * @param object $object * - * @return AbstractAuraForm + * @return AbstractForm */ private function getFormProperty(FormValidation $formValidation, $object) { @@ -119,7 +107,7 @@ private function getFormProperty(FormValidation $formValidation, $object) $prop = (new \ReflectionClass($object))->getProperty($formValidation->form); $prop->setAccessible(true); $form = $prop->getValue($object); - if (! $form instanceof FormInterface) { + if (! $form instanceof AbstractForm) { throw new InvalidFormPropertyException($formValidation->form); } diff --git a/src/AuraInputModule.php b/src/AuraInputModule.php index 59c3bbb..57846a5 100644 --- a/src/AuraInputModule.php +++ b/src/AuraInputModule.php @@ -6,6 +6,7 @@ */ namespace Ray\WebFormModule; +use Aura\Filter\FilterFactory; use Aura\Html\HelperLocatorFactory; use Aura\Input\AntiCsrfInterface; use Aura\Input\Builder; @@ -30,9 +31,10 @@ protected function configure() $this->bind(Reader::class)->to(AnnotationReader::class)->in(Scope::SINGLETON); $this->bind(BuilderInterface::class)->to(Builder::class); $this->bind(FilterInterface::class)->to(Filter::class); - $this->bind(HelperLocatorFactory::class); $this->bind(AntiCsrfInterface::class)->to(AntiCsrf::class)->in(Scope::SINGLETON); $this->bind(FailureHandlerInterface::class)->to(OnFailureMethodHandler::class); + $this->bind(HelperLocatorFactory::class); + $this->bind(FilterFactory::class); $this->bindInterceptor( $this->matcher->any(), $this->matcher->annotatedWith(FormValidation::class), diff --git a/src/Exception/CsrfViolationException.php b/src/Exception/CsrfViolationException.php new file mode 100644 index 0000000..5a396aa --- /dev/null +++ b/src/Exception/CsrfViolationException.php @@ -0,0 +1,13 @@ +getArguments(); diff --git a/src/SetAntiCsrfTrait.php b/src/SetAntiCsrfTrait.php index a18223d..e1b1af2 100644 --- a/src/SetAntiCsrfTrait.php +++ b/src/SetAntiCsrfTrait.php @@ -15,7 +15,7 @@ trait SetAntiCsrfTrait * * @\Ray\Di\Di\Inject */ - public function injectAntiCsrf(AntiCsrfInterface $antiCsrf) + public function setAntiCsrf(AntiCsrfInterface $antiCsrf) { $this->antiCsrf = $antiCsrf; } diff --git a/src/VndErrorHandler.php b/src/VndErrorHandler.php index 0be71d5..cf32f1d 100644 --- a/src/VndErrorHandler.php +++ b/src/VndErrorHandler.php @@ -28,7 +28,7 @@ public function __construct(Reader $reader) /** * {@inheritdoc} */ - public function handle(FormValidation $formValidation, MethodInvocation $invocation, Form $form) + public function handle(FormValidation $formValidation, MethodInvocation $invocation, AbstractForm $form) { unset($formValidation); $vndError = $this->reader->getMethodAnnotation($invocation->getMethod(), VndError::class); @@ -38,11 +38,11 @@ public function handle(FormValidation $formValidation, MethodInvocation $invocat throw $e; } - private function makeVndError(Form $form, VndError $vndError = null) + private function makeVndError(AbstractForm $form, VndError $vndError = null) { $body = ['message' => 'Validation failed']; $body['path'] = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : ''; - $body['validation_messages'] = $form->getMessages(); + $body['validation_messages'] = $form->getFailureMessages(); $body = $vndError ? $this->optionalAttribute($vndError) + $body : $body; return $body; diff --git a/tests/AbstractAuraFormTest.php b/tests/AbstractAuraFormTest.php index e3d5c26..e8f30b3 100644 --- a/tests/AbstractAuraFormTest.php +++ b/tests/AbstractAuraFormTest.php @@ -9,16 +9,14 @@ class AbstractAuraFormTest extends \PHPUnit_Framework_TestCase { /** - * @var AbstractAuraForm + * @var AbstractForm */ private $form; public function setUp() { parent::setUp(); - $this->form = new FakeForm; - $this->form->setBaseDependencies(new Builder, new Filter, new HelperLocatorFactory); - $this->form->postConstruct(); + $this->form = (new FormFactory)->newInstance(FakeForm::class); } public function testForm() @@ -29,7 +27,8 @@ public function testForm() public function testAntiCsrfForm() { - $this->form->setCsrf(new FakeAntiCsrf); + $this->form->setAntiCsrf(new FakeAntiCsrf); + $this->form->postConstruct(); $formHtml = $this->form->form(); $this->assertSame('
' . PHP_EOL, $formHtml); } @@ -43,7 +42,8 @@ public function testInput() public function testError() { $this->form->fill([]); - $isValid = $this->form->filter(); + $data = ['name' => '@invalid@']; + $isValid = $this->form->apply($data); $this->assertFalse($isValid); $error = $this->form->error('name'); $this->assertSame('Name must be alphabetic only.', $error); diff --git a/tests/AbstractFormTest.php b/tests/AbstractFormTest.php index 9266b28..c520f29 100644 --- a/tests/AbstractFormTest.php +++ b/tests/AbstractFormTest.php @@ -2,12 +2,15 @@ namespace Ray\WebFormModule; -use Aura\Filter\FilterFactory; -use Aura\Html\HelperLocatorFactory; -use Aura\Input\Builder; +use Aura\Session\CsrfTokenFactory; +use Aura\Session\Phpfunc; +use Aura\Session\Randval; +use Aura\Session\SegmentFactory; +use Aura\Session\Session; use Doctrine\Common\Annotations\AnnotationReader; use Ray\Aop\Arguments; use Ray\Aop\ReflectiveMethodInvocation; +use Ray\WebFormModule\Exception\CsrfViolationException; use Ray\WebFormModule\Exception\ValidationException; class AbstractFormTest extends \PHPUnit_Framework_TestCase @@ -29,9 +32,7 @@ public function setUp() public function getMethodInvocation(array $arguments) { // form - $fakeForm = new FakeMiniForm; - $fakeForm->setBaseDependencies(new Builder, new FilterFactory, new HelperLocatorFactory); - $fakeForm->postConstruct(); + $fakeForm = (new FormFactory)->newInstance(FakeMiniForm::class); // controller $controller = new FakeController; $controller->setForm($fakeForm); @@ -62,4 +63,39 @@ public function testSubmit() $invocation = $this->getMethodInvocation(['na']); $invocation->proceed(); } + + public function testErrorReturnEmpty() + { + $result = $this->form->error('name'); + $expected = ''; + $this->assertSame($expected, $result); + } + + public function testClone() + { + $form = clone $this->form; + (new \ReflectionProperty($form, 'filter'))->setAccessible(true); + (new \ReflectionProperty($this->form, 'filter'))->setAccessible(true); + $this->assertNotSame(spl_object_hash($form), spl_object_hash($this->form)); + } + + public function testGetItelator() + { + $itelator = $this->form->getIterator(); + $this->assertInstanceOf(\Iterator::class, $itelator); + } + + public function testAntiCsrfViolation() + { + $this->setExpectedException(CsrfViolationException::class); + $session = new Session( + new SegmentFactory, + new CsrfTokenFactory(new Randval(new Phpfunc)), + new FakePhpfunc, + [] + ); + $this->form->setAntiCsrf(new AntiCsrf($session, false)); + $this->form->apply([]); + } + } diff --git a/tests/AntiCsrfTest.php b/tests/AntiCsrfTest.php index 6c91994..2242dce 100644 --- a/tests/AntiCsrfTest.php +++ b/tests/AntiCsrfTest.php @@ -29,7 +29,7 @@ protected function setUp() { $this->phpfunc = new FakePhpfunc; $this->session = $this->newSession(); - $this->antiCsrf = new AntiCsrf($this->newSession([])); + $this->antiCsrf = new AntiCsrf($this->newSession([]), false, [AntiCsrf::TOKEN_KEY => AntiCsrf::TEST_TOKEN]); } protected function newSession(array $cookies = []) @@ -55,7 +55,7 @@ public function testSetField() public function testIsValid() { - $data = ['__csrf_token' => AntiCsrf::TEST_TOKEN]; + $data = [AntiCsrf::TOKEN_KEY => $this->session->getCsrfToken()->getValue()]; $this->assertTrue($this->antiCsrf->isValid($data)); } } diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index 7477b08..a046464 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -2,9 +2,6 @@ namespace Ray\WebFormModule; -use Aura\Html\HelperLocatorFactory; -use Aura\Input\Builder; -use Aura\Input\Filter; use Doctrine\Common\Annotations\AnnotationReader; use Ray\Aop\Arguments; use Ray\Aop\ReflectiveMethodInvocation; @@ -45,9 +42,8 @@ public function getMethodInvocation($method, array $submit, FailureHandlerInterf public function getController(array $submit) { $controller = new FakeController; - $fakeForm = new FakeForm; - $fakeForm->setBaseDependencies(new Builder, new Filter, new HelperLocatorFactory); - $fakeForm->postConstruct(); + /** @var $fakeForm FakeForm */ + $fakeForm = (new FormFactory)->newInstance(FakeForm::class); $fakeForm->setSubmit($submit); $controller->setForm($fakeForm); @@ -104,9 +100,9 @@ public function testInvalidFormPropertyException() { $this->setExpectedException(InvalidOnFailureMethod::class); $controller = new FakeInvalidController3; - $fakeForm = new FakeForm; - $fakeForm->setBaseDependencies(new Builder, new Filter, new HelperLocatorFactory); - $fakeForm->postConstruct(); + /** @var $fakeForm FakeForm */ + $fakeForm = (new FormFactory)->newInstance(FakeForm::class); + $fakeForm->setSubmit(['name' => '']); $controller->setForm($fakeForm); $this->proceed($controller); } @@ -118,7 +114,7 @@ public function testInvalidFormPropertyByInvalidInstance() $invocation = new ReflectiveMethodInvocation( $object, new \ReflectionMethod($object, 'createAction'), - new Arguments([]), + new Arguments(['name' => '']), [ new AuraInputInterceptor(new AnnotationReader, new OnFailureMethodHandler) ] diff --git a/tests/Fake/FakeControllerVndError.php b/tests/Fake/FakeControllerVndError.php index 9673ac9..e3cf8b9 100644 --- a/tests/Fake/FakeControllerVndError.php +++ b/tests/Fake/FakeControllerVndError.php @@ -31,7 +31,7 @@ public function setForm(FormInterface $form) * href={"_self"="/path/to/error", "help"="/path/to/help"} * ) */ - public function createAction() + public function createAction($name) { } } diff --git a/tests/Fake/FakeForm.php b/tests/Fake/FakeForm.php index 2c854b9..b25d5e5 100644 --- a/tests/Fake/FakeForm.php +++ b/tests/Fake/FakeForm.php @@ -2,9 +2,7 @@ namespace Ray\WebFormModule; -use Aura\Input\Filter; - -class FakeForm extends AbstractAuraForm +class FakeForm extends AbstractForm { use SetAntiCsrfTrait; @@ -27,15 +25,8 @@ public function init() ->setAttribs([ 'id' => 'name' ]); - /** @var $filter Filter */ - $filter = $this->getFilter(); - $filter->setRule( - 'name', - 'Name must be alphabetic only.', - function ($value) { - return ctype_alpha($value); - } - ); + $this->filter->validate('name')->is('alnum'); + $this->filter->useFieldMessage('name', 'Name must be alphabetic only.'); } /** diff --git a/tests/Fake/FakeInvalidController3.php b/tests/Fake/FakeInvalidController3.php index 60cff8e..8322619 100644 --- a/tests/Fake/FakeInvalidController3.php +++ b/tests/Fake/FakeInvalidController3.php @@ -16,7 +16,7 @@ public function setForm(FormInterface $form) /** * @FormValidation(onFailure="missing_method") */ - public function createAction() + public function createAction($name) { } } diff --git a/tests/Fake/FakeModule.php b/tests/Fake/FakeModule.php index eab7723..2e51456 100644 --- a/tests/Fake/FakeModule.php +++ b/tests/Fake/FakeModule.php @@ -10,7 +10,7 @@ class FakeModule extends AbstractModule protected function configure() { $this->bind(Phpfunc::class)->to(FakePhpFunc::class); - $this->install(new AuraInputModule()); + $this->install(new AuraInputModule); $this->bind(FormInterface::class)->annotatedWith('contact_form')->to(FakeForm::class); } } diff --git a/tests/VndErrorHandlerTest.php b/tests/VndErrorHandlerTest.php index 62a2c52..2906605 100644 --- a/tests/VndErrorHandlerTest.php +++ b/tests/VndErrorHandlerTest.php @@ -47,7 +47,7 @@ public function testVndErrorAnnotation() /** @var $controller FakeControllerVndError */ $controller = (new Injector(new FakeVndErrorModule))->getInstance(FakeControllerVndError::class); try { - $controller->createAction(); + $controller->createAction(''); } catch (ValidationException $e) { $vndError = (string) $e->error; $this->assertSame('{