How to validate Google reCaptcha v2 using phalcon/volt forms? - php

How do you validate the new Google reCaptcha using volt and phalcon techniques?
(Just wanted to share what I did to make it work, see answer below, hope it helps...)

What you will need
A Validator (RecaptchaValidator in this case)
Form implementation
Controller implementation (actually no changes needed here, but for completeness...)
View implementation
(optional) Config entries for your recaptcha keys and url (nicer/cleaner this way)
(optional) A recaptcha element for automatic rendering (if you prefer the render method)
The Validator
The validator is the most crucial part of this, all other things are rather intuitive ...
use \Phalcon\Validation\Validator;
use \Phalcon\Validation\ValidatorInterface;
use \Phalcon\Validation\Message;
class RecaptchaValidator extends Validator implements ValidatorInterface
{
public function validate(\Phalcon\Validation $validation, $attribute)
{
if (!$this->isValid($validation)) {
$message = $this->getOption('message');
if ($message) { // Add the custom message defined in the "Form" class
$validation->appendMessage(new Message($message, $attribute, 'Recaptcha'));
}
return false;
}
return true;
}
/********************************************
* isValid - Return Values
* =======================
* true .... Ok
* false ... Not Ok
* null .... Error
*/
public function isValid($validation)
{
try {
$config = $validation->config->recaptcha; // not needed if you don't use a config
$value = $validation->getValue('g-recaptcha-response');
$ip = $validation->request->getClientAddress();
$url = $config->verifyUrl; // or 'https://www.google.com/recaptcha/api/siteverify'; without config
$data = ['secret' => $config->secretKey, // or your secret key directly without using the config
'response' => $value,
'remoteip' => $ip,
];
// Prepare POST request
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data),
],
];
// Make POST request and evaluate the response
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
return json_decode($result)->success;
}
catch (Exception $e) {
return null;
}
}
}
Form (class) implementation
class SignupForm extends Form
{
public function initialize($entity = null, $options = null)
{
// Name (just as an example of other form fields)
$name = new Text('name');
$name->setLabel('Username');
$name->addValidators(array(
new PresenceOf(array(
'message' => 'Please enter your name'
))
));
$this->add($name);
// Google Recaptcha v2
$recaptcha = new Check('recaptcha');
$recaptcha->addValidator(new RecaptchaValidator([
'message' => 'Please confirm that you are human'
]));
$this->add($recaptcha);
// Other form fields...
}
Controller implementation
Even though the controller is the same as with every other form, for completeness sake here an example...
class SessionController extends \Phalcon\Mvc\Controller
{
public function signupAction()
{
$form = new SignupForm();
if ($this->request->isPost()) {
if ($form->isValid($this->request->getPost()) != false)
{
// Add user to database, do other checks, etc.
// ...
}
}
$this->view->form = $form;
}
}
View implementation
For the view you can either just put the html there or let it be rendered by the engine. If you want it rendered instead (with e.g. {{ form.render('recaptcha') }}), you will have to also create an Recaptcha Element instead of using one of the defaults (see last point in this answer for that).
...
{{ form('class':'signupForm') }}
<fieldset>
<div>{{ form.label('name') }}</div>
{{ form.render('name') }}
{{ form.messages('name') }}
<!-- other elements here -->
<!-- ... -->
<div class="g-recaptcha" data-sitekey="{{ this.config.recaptcha.publicKey }}"></div>
{{ form.messages('recaptcha') }}
If you don't wanna use a config for your public key (next section), just set the value of the data-sitekey to your personal (Google reCaptcha) public key.
Also don't forget to include the script (<script src='https://www.google.com/recaptcha/api.js'></script>) somewhere (e.g. in your html head section).
(Optional) Config
If you wanna use the config to store your recaptcha keys, also add the following to your config/config.php ...
// config/config.php
return new \Phalcon\Config([
'application' => [
'controllersDir' => __DIR__ . '/../../app/controllers/',
'modelsDir' => __DIR__ . '/../../app/models/',
'formsDir' => __DIR__ . '/../../app/forms/',
'viewsDir' => __DIR__ . '/../../app/views/',
'pluginsDir' => __DIR__ . '/../../app/plugins/',
'libraryDir' => __DIR__ . '/../../app/library/',
'cacheDir' => __DIR__ . '/../../app/cache/',
'baseUri' => '/',
],
// other configs here
// ...
'recaptcha' => [
'publicKey' => 'your public key',
'secretKey' => 'your private key',
'verifyUrl' => 'https://www.google.com/recaptcha/api/siteverify',
],
]);
To be able to access the config in your view, you might also need to add $di->set('config', $config); to your dependency injector (typically within config/services.php).
(Optional) Recaptcha Element
If you want your recaptcha to be rendered for you (instead of you putting the div directly in the view), you will need a separate \Phalcon\Forms\Element\ ...
class Recaptcha extends \Phalcon\Forms\Element
{
public function render($attributes = null) {
return '<div class="g-recaptcha" data-sitekey="'
.$this->config->recaptcha->publicKey
.'"></div>';
}
}
You will also have to change your Form accordingly:
// ...
$recaptcha = new Recaptcha('recaptcha');
$recaptcha->addValidator(new RecaptchaValidator([
'message' => '...'
]));
// ...
And finally also your View:
{{ form.render('recaptcha') }}

Related

Redirect back with Symfony and twig

In my Symfony project I have two views. Details view of specific entity and a button to lead me to a new view with with some other data by date param.
The problem with the code I have is of generating 'Return to previous' page button from second method back to first..
Code:
/**
* #Route("/details/{id}", name="first_method")
*/
public function firstMethod(Detail $id)
{
$workspace = $this->entityManager
->getRepository(Detail::class)
->find($id);
$build['detail'] = $detail;
$form = $this->createFormBuilder()
->add('date', DateTimeType::class, [
'data' => new \DateTime(),
'widget' => 'single_text'
])
->add(
'save',
SubmitType::class,
[
'attr' => ['class' => 'btn-submit']
]
)
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$date = $data['date']
return $this->redirectToRoute('second_method', [
'date' => $date
]);
}
return $this->render('first-method.html.twig', [
'detail' => $detail,
]);
}
And there is that 'second' method:
/**
* #Route("/second-method/{date}", name="second_method")
*/
public function secondMethod($date)
{
return $this->render('second-method.html.twig', [
'someData' => $someData,
'date' => $date,
]);
}
I have a button on the second_method twig view which needs to return me back to method_one page.
How can accomplish that as the parameter $id is probably needed in second method but can not find a way to provide it. Maybe there is some other way? Can someone help?
I think in this first way it shoud me like:
{{ path('first_method', {'detailId':detail.id}) }}
As hinted by #msg, you should provide a second parameter to your second page. You will then need the #Entity annotation for Detail conversion and the #ParamConverter annotation for the date conversion :
/**
* #Route("/second_method/{id_detail}/{date}", name="second_method")
* #Entity("detail", expr="repository.find(id_detail)")
* #ParamConverter("date", options={"format": "!Y-m-d"})
*/
public function (Detail $detail, \DateTime $date)
// ...
The parameter conversion will perform a first sanity check and will return a 404 if the entity doesn't exist.
You will also be able to check in your controller that $detail is coherent with the date in the URL. Remember : never trust user input (this includes URLs).
And you can provide this new parameter when your redirect to your second controller :
return $this->redirectToRoute("second_method", [
"date" => $date,
"id_detail" => $workspace->getId() // You can also pass the whole object and fetch the ID in the template
]);
Then in your template :
{{ path('first_method', {'id': id_detail}) }}
Details
For ParamConverters you can find more details here : https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
I personnaly prefer the #Entity annotation for conversion of an Entity since you are sure of how the conversion is done : I find it better for when your project grows and your entities have more and more parameters. Eventhough the #ParamConverter would work in your case.
Alternative without changing the second_method route parameters, relying only on the referer.
Add the helper method to the controller:
/**
* Returns the referer url to redirect back if the request came from first_method
* Otherwise returns FALSE.
*/
private function getBackUrl(Request $request, UrlMatcherInterface $matcher)
{
$referer = $request->headers->get('referer');
if (null === $referer) {
return false;
}
$url = parse_url($referer);
if ($url['host'] != $request->getHost()) {
return false;
}
// Remove the baseurl if we're in a subdir or there's no Rewrite
$path = str_replace($request->getBaseUrl(), '', $url['path']);
try {
$route = $matcher->match($path);
} catch (ResourceNotFoundException $e) {
// Doesn't match any known local route
return false;
}
// A route has been determined, check if it matches the requirement
if ('first_method' == $route['_route']) {
return $referer;
}
return false;
}
Change the method to (The Route annotation doesn't change, just the method signature):
public function secondMethod($date, Request $request, UrlMatcherInterface $matcher)
{
return $this->render('second-method.html.twig', [
'someData' => $someData,
'date' => $date,
'back_url' => $this->getBackUrl($request, $matcher),
]);
}
In your template:
{# Show the button only if the request came from first_method (otherwise will be false) #}
{% if back_url %}
<a class="btn" href="{{ back_url }}">Return</a>
{% endif %}

Unable to update file details entries in mysql db using Laminas and Doctrine

I'm able to add file details in database but not able to update it.
I am able to add file details entries, but when i try to update only the file that i am updating is moved to the storage folder. My update manager doesn't show any errors and doesn't update the file details in database.
this is my file form
protected function addElements()
{
// Add "name" field
$this->add([
'type' => 'file',
'name' => 'image',
'attributes' => [
'id' => 'image'
],
'options' => [
'label' => 'ImageFile',
],
]);
// Add the Submit button
$this->add([
'type' => 'submit',
'name' => 'submit',
'attributes' => [
'value' => 'Add Image File',
'id' => 'submit',
],
]);
// Add the CSRF field
$this->add([
'type' => 'csrf',
'name' => 'csrf',
'options' => [
'csrf_options' => [
'timeout' => 600
]
],
]);
}
public function addInputFilter()
{
$inputFilter = new InputFilter\InputFilter();
// File Input
$fileInput = new InputFilter\FileInput('image');
$fileInput->setRequired(true);
$inputFilter->add($fileInput);
$this->setInputFilter($inputFilter);
}
}
this is the update image manager
public function updateImage($name, $size)
{
$images = new Images();
$images->setName($name);
$images->setSize($size);
// Apply changes to database.
$this->entityManager->flush();
}
and this is my controller
public function editAction()
{
$id = (int)$this->params()->fromRoute('id', -1);
if ($id<1) {
$this->getResponse()->setStatusCode(404);
return;
}
$image = $this->entityManager->getRepository(Images::class)
->find($id);
if ($image == null) {
$this->getResponse()->setStatusCode(404);
return;
}
// Create form
$form = new ImageUploadForm('update', $this->entityManager);
$request = $this->getRequest();
if ($this->getRequest()->isPost()) {
$data = array_merge_recursive(
$request->getPost()->toArray(),
$request->getFiles()->toArray()
);
$form->setData($data);
if($form->isValid()) {
$data = $form->getData();
$imgtmp = $data["image"]["tmp_name"];
$name = $data["image"]["name"];
$size = $data["image"]["size"];
$filepath = $this->_dir.$name;
move_uploaded_file($imgtmp, $filepath);
$this->achimotaImagesManager->updateImage($name, $size);
var_dump($name, $size);
return $this->redirect()->toRoute('images', ['action'=>'index']);
}
}
return new ViewModel([
'form' => $form,
]);
}
Do not create a new object
If you update an Images entity (consider naming it Image if it is one image), you should not create a new one. Hand over the $image you need to update:
public function updateImage($image, $name, $size){
$image->setName($name);
$image->setSize($size);
...
}
Persist before flush
You need to persist the entity before you flush.
$this->entityManager->persist($image);
$this->entityManager->flush();
Organize the code nicer
Do not inject entity manager in your controller. Inject rather a service through a factory, which handles all features of your Image entity. (ImageService.php)
Do not inject entity manager into your ImageService neither. Create a ImageMapper service, inject that into your ImageService. Create all Doctrine-related features in this Mapper. This has this advantage: Doctrin specific functionality is only in your Mapper files. Should you need to use another solution to store data, you only need to replace the Mapper files, providing the Service with the same interface.
Controller
public function editAction()
{
...
$this->serviceImage->update($image,$name,$size);
...
}
Service - ImageService.php
public function update($image,$name,$size)
{
$image->setName($name);
$image->setSize($size);
$this->mapperImage->save($image);
}
Mapper - ImageMapper.php
public function save($image)
{
$this->managerEntity->persist($image);
$this->managerEntity->flush();
}
Consider adding rich comments and typehints to the arguments and return value of the functions.
Moreover
The form should not be created in your controller. Put that code in your ImageService too. And consider inject form into the service. (Make sure you define the form for the factory in the getFormElementConfig()! This is more advance stuff, if you do not test with phpunit, you might not bother creating form as a service, hovever it leads to a very organized codebase.)
var_dump($name, $size) has no place in your controller. (If this is for debug purposes, it is OK, but use rather something like XDebug with a compatible IDE - PHPStorm is far the best one.)
This line is not so easy to understand: $filepath = $this->_dir.$name; Maybe:
$filePath = _dir . $name;
Naming convention: look for camelCase.

Phalcon - validation in form

I created a form with validation rules. Everything is fine, form is visible and works.
Problem is with validators. Only first validator works in addValidators([ ....])
My form class source code:
public function initialize()
{
$title = new Text('title');
$title->setLabel('Title of article');
$title->setFilters([
'striptags', 'trim'
]);
$title->addValidators([
new PresenceOf([
'message' => 'Title can not be empty'
]),
new StringLength([
'min' => 5,
'messageMinimum' => 'Title is too short. Should has more than 5 letters'
]),
new MYArticleAddCheckTitleValidator([
'message' => 'aaaaaaaaaaaaaaa'
])
]);
$this->add($title);
..........
Validator PresenceOf works fine. validation flash message is visible.
Validator StringLength does not work. It looks like form doesn't know about it
Validator MYArticleAddCheckTitleValidator (my own validator class) - the same as StringLength.
Phalcon version 2.0.4 on windows.
Any proposition, or suggestions ?
Thanks a lot.
Using Phalcon\Flash\Direct
The best way to get around this problem is to use flash messages, with the class Phalcon\Flash\Direct. You can find the documentation here.
This strategy is used by the phalcon vokuro project which I suggest you to look at.
The key of this solution is the message() function inside your form class.
Form class
<?php
namespace Your\App\Forms;
use Phalcon\Forms\Form;
use Phalcon\Forms\Element\Text;
use Phalcon\Validation\Message;
use Phalcon\Validation\Validator\PresenceOf;
use Phalcon\Validation\Validator\StringLength;
use Your\App\Validation\MYArticleAddCheckTitleValidator;
class YourForm extends Form {
public function initialize()
{
$title = new Text('title');
$title->setLabel('Title of article');
$title->setFilters([
'striptags', 'trim'
]);
$title->addValidators([
new PresenceOf([
'message' => 'Title can not be empty'
]),
new StringLength([
'min' => 5,
'messageMinimum' => 'Title is too short. Should has more than 5 letters'
]),
new MYArticleAddCheckTitleValidator([
'message' => 'aaaaaaaaaaaaaaa'
])
]);
$this->add($title);
}
/**
* Prints messages for a specific element. Call it in the view
*/
public function messages($name)
{
if ($this->hasMessagesFor($name)) {
foreach ($this->getMessagesFor($name) as $message) {
$this->flash->error($message);
}
}
}
}
Controller
<?php
namespace Your\App;
use Your\App\YourForm;
class YourController extends ControllerBase
{
public function indexAction()
{
$form = new YourForm();
$this->view->form = $form;
if($this->request->hasQuery('title')){
if ($form->isValid($this->request->getQuery()) != false) {
// Code when form is valid
}
}
}
}
View
If you follow the suggested schema should be located in /app/views/your/index.html
<form method="GET" action="">
<?= $form->label('title') ?>
<?= $form->render('title')?>
<?= $form->messages('title') //show messages here ?>
</form>
If you have more than one form, it is useful to register the flash service with the DI.
When you define your services (could be in the index.php in the root folder or services.php in the /app/config/ folder) you define your flash service:
<?php
use Phalcon\DI\FactoryDefault;
$di = new FactoryDefault();
// Register the flash service with custom CSS classes
$di->set('flash', function () {
$flash = new Phalcon\Flash\Direct(
array(
'error' => 'your-error-class',
'success' => 'your-success-class',
'notice' => 'your-notice-class',
'warning' => 'your-warning-class'
)
);
return $flash;
});

Laravel PHP 4: 'Put' method generates 'MethodNotAllowedHttpException'

I am trying to modify a form used for editing and updating data. However when I try submitting the 'edit' form, I keep getting a 'MethodNotAllowedHttpException'. I'm not sure if this is because I am using the 'PUT' method incorrectly or my 'EditAlbumsController.php' file is not defined correctly.
edit-album.blade.php:
{{ Form::model($album, array('method' => 'PUT', 'route' => array('edit_album', $album->album_id))) }}
/* Form code here */
{{ Form::close() }}
routes.php:
Route::get('gallery/album/{id}/edit', array('as'=>'edit_album', 'uses'=>'EditAlbumsController#update'));
EditAlbumsController.php:
class EditAlbumsController extends AlbumsController {
public function __construct()
{
parent::__construct();
}
public function update($id)
{
$input = \Input::except('_method');
$validation = new Validators\Album($input);
if ($validation->passes())
{
$album = Album::find($id);
$album->album_name = $input['album_name'];
/* Additional database fields go here */
$album->touch();
return $album->save();
return \Redirect::route('gallery.album.show', array('id' => $id));
}
else
{
return \Redirect::route('gallery.album.edit', array('id' => $id))
->withInput()
->withErrors($validation->errors)
->with('message', \Lang::get('gallery::gallery.errors'));
}
}
Any help is greatly appreciated!
You need to define the PUT route (you are incorrectly using GET)
Route::put('gallery/album/{id}/edit', array('as'=>'edit_album', 'uses'=>'EditAlbumsController#update'));

not passing validation - Symfony2 Form without a Class

After a thorough search im not seeing the problem.
I have a form im submitting, which isnt tied to an object, its just an emailer form. I want to validate the data. according to the docs the alternative way is to do this.
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
$builder
->add('firstName', 'text', array(
'constraints' => new Length(array('min' => 3)),
))
->add('lastName', 'text', array(
'constraints' => array(
new NotBlank(),
new Length(array('min' => 3)),
),
))
;
now thats done, but i CANNOT call $form->isValid on this in any shape or form (pun intended), because even though it passes the constraint violations, something seemingly invisible is still causing it to return invalid.
i feel like i might need to extract the form from the post first, and pass that through isValid() but i cant be sure.
heres my method code
/**
* #Route("/share", name="email_share")
* #Method({"POST"})
* #return \Symfony\Component\HttpFoundation\Response
*/
public function shareAction( Request $request, array $lessons){
if(!$lessons || !is_array($lessons))
{
throw new HttpException(404, "Whoops! shareAction didnt get the lessons");
}
//set up our form defaults here
$defaultData = array('comment' => 'Type your comment here');
//build the form here
$form = $this->createFormBuilder($defaultData)
->setAction($this->generateUrl('email_share'))
->add("emails", 'email', array(
'label' => "Recipient's email address (separate with a comma)",
'constraints' => array(
new Length(array('min' => 6, 'max' => 2040)),
new NotBlank(),
),
))
->add('comment', 'textarea', array(
'label' => "Leave a comment",
))
->add('name', 'text', array(
'label' => "Your name",
'constraints' => array(
new Length(array('min' => 3), 'max' => 254)),
new NotBlank(),
),
))
->add('email', 'email', array(
'label' => "Your email address",
'constraints' => array(
new Length(array('min' => 6, 'max' => 254)),
new NotBlank(),
),
))
->add('copy', 'checkbox', array(
'label' => "Send me a copy",
'required' => false,
))
->add('cancel', 'submit', array(
'label' => "Cancel",
))
->add('save', 'submit', array(
'label' => "Email Resources",
))
->getForm();
if ($this->getRequest()->isMethod('POST')) {
//data is already validated by constraints added when the form was created since we are not attaching this particular form to any object
$form->handleRequest($request);
//alternatively (makes no differene from the former)
//$form->submit($request->request->get($form->getName()));
if ($form->isValid())
{
//have YET to see this
echo 'valid';
exit;
}
else{
//echo 'not fuckin valie, WHY?';
//exit;
// get a ConstraintViolationList
$errors = $this->get('validator')->validate( $form );
$result = '';
//nothing returns here when form is valid against constraints, its just empty
echo $errors;
// iterate on it
foreach( $errors as $error )
{
$error->getPropertyPath() : the field that caused the error
$error->getMessage() : the error message
}
}
$data = $form->getData();
return $this->emailUser($data);
}
return $this->render('ResourceBundle:Default:resources.html.twig', array(
'form' => $form->createView(),
));
}
This is how im posting the data
function postForm($form, callback) {
/*
* Get all form values
*/
var values = {};
$.each($form.serializeArray(), function (i, field) {
values[field.name] = field.value;
});
/*
* Throw the form values to the server!
*/
$.ajax({
type: 'POST',
url: '/share',
data: values,
success: function (data) {
callback(data);
}
});
}
$(document).ready(function () {
//bind an event to submit on 'email resources' button
$('div#share form').submit(function (e) {
//disable symfonys default submit button hehaviour
e.preventDefault();
postForm($(this), function (response) {
//replace html here
// Is this where im going wrong? Do i need to replace the form here?
});
});
});
EDIT: Here is the pertinent portion of the main template code that calls the action in the first place
<div id="share" class="hidden" >
<h2>Share Resources</h2>
{% render url('email_share') %}
</div>
here is the form template code thats rendered in the shareAction (in its entirety currently)
{{ form(form) }}
was
{% if form | default %}
{{ form(form) }}
{% endif %}
{% if mail_response | default %}
{{ dump(mail_response) }}
{% endif %}
The hidden token input portion of the form
<input id="form__token" class="form-control" type="hidden" value="8QWLo8xaPZFCKHBJbuc6CGNIcfmpWyT-yFdWScrsiJs" name="form[_token]">
The two underscores worry me a bit (form__token)
EDIT2:
The problem is in the CSRF token somewhere. it could be the form input name, the token itself is already expired, or something else.
I pretty much narrowed it down by constructing my own form module like this
//set up our form defaults here
$defaultData = array('comment' => 'Type your comment here');
$session = new Session();
$secret = '123xyz';
$vendorDir = realpath(__DIR__ . '/../vendor');
$vendorFormDir = $vendorDir . '/symfony/form/Symfony/Component/Form';
$vendorValidatorDir =
$vendorDir . '/symfony/validator/Symfony/Component/Validator';
// create the validator - details will vary
$validator = Validation::createValidator();
$formFactory = Forms::createFormFactoryBuilder()
->addExtension(new HttpFoundationExtension())
//->addExtension(new CsrfExtension(new SessionCsrfProvider($session, $secret)))
->addExtension(new ValidatorExtension($validator))
->getFormFactory();
//build the form here
$form = $formFactory->createBuilder('form', $defaultData)
->setAction($this->generateUrl('email_share'))
->setMethod('POST')
->add("emails", 'email', array(
//......
//same as above for the rest......
The form FINALLY passes validation like this, and when i uncomment the line
->addExtension(new CsrfExtension(new SessionCsrfProvider($session, $secret)))
i get the same error as i did before, that the CSRF token is invalid.
To me, this is pretty much pointing to somewhere in this module, or im not calling something, or extending something right, or the javascript is returning a form that is older than the CSRF module is expecting, or the hidden token form input has a name that is other than that what the CSRF module is looking for. I dont know enough about symfony's internals to diagnose this, this is why i come here for help. Does anybody see a potential issue?
EDIT:3 i feel like i shouldnt be using isValid(), as mentioned, i am not passing an object, im passing an array. see this URL http://symfony.com/doc/current/book/validation.html#validating-values-and-arrays. Im trying to figure out how to properly check against the constraints, and im thinking isValid() is NOT the way to go after all, or else im missing something fundamental.. I just cant figure if i only check against constraint errors, how can i use the CSRFprotection still, or is that only for objects or something?? Do i need to pass this in manually since im not using an object?
EDIT 4:
It looks like i might have uncovered the crux of the problem, yet i cant figure out how to solve it yet.
on this file Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider
i put some output to track the token, and it appears that the token is regenerating for the comparison, which seems like IT SHOULD NOT be the case I would expect that it would compare the passed down token to one in memory, but in fact, its being generated twice, once for the form, then once again for the comparison.
At first i suspected it was possible that the browser and the ajax are running from two different sessions, and a mismatch can be caused by this because i was using SessionCsrfProvider(), but after switching to ->addExtension(new CsrfExtension(new DefaultCsrfProvider($secret))) i had the same problem.
Is this a bug, am i going crazy, or am i missing something as simple as the form id in the building of the form or something?
heres the code, and the results i found from that code.
//Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider
public function isCsrfTokenValid($intention, $token)
{
echo '<pre>Warning, Symfony\Component\Form\Extension\Csrf\CsrfProvider\isCsrfTokenValid';
echo'<br>, here is out token handed down to compare<br>';
var_dump($token);
echo '<br>the new generated token thats being compared to is<br>';
var_dump($this->generateCsrfToken($intention));
echo '</pre>';
return $token === $this->generateCsrfToken($intention);
}
returns
//the form
array(6) { ["emails"]=> string(19) "email#email.net"
["comment"]=> string(2) "yo" ["name"]=> string(5) "me"
["email"]=> string(19) "email#email.net" ["copy"]=>
string(1) "1" ["_token"]=> string(40)
"a11e10eb323f7a4d19577e6d07e68be951ceb569" }
Warning,
Symfony\Component\Form\Extension\Csrf\CsrfProvider\isCsrfTokenValid ,
here is out token handed down to compare string(40)
"a11e10eb323f7a4d19577e6d07e68be951ceb569"
the new generated token thats being compared to is string(40)
"e83cdf94b15e63e822520b62402eb66e0b1f03d3"
The CSRF token is invalid. Please try to resubmit the form.
Blockquote
EDIT 5:
the problem has been traced to here, look at this code in the DefaultCsrfProvider
public function generateCsrfToken($intention)
{
return sha1($this->secret.$intention.$this->getSessionId());
}
public function isCsrfTokenValid($intention, $token)
{
return $token === $this->generateCsrfToken($intention);
}
The token can never be valid during an ajax call, unless a param is set in the generateCsrfToken() token method to allow passing of the session, to which you would want to pass that via ajax, like this
public function generateCsrfToken($intention, $session)
{
if(!$session)
{
$session = $this->getSessionId()
}
return sha1($this->secret.$intention.$session);
}
which i would think would completely reduce teh security of the whole idea of the CSRF in the first place.
is there another provide i can use for ajax calls in within the symfony framework? If were depending on the sesion, this pretty much leaves out both the SessionCsrfProvider class and the DefaultCsrfProvider class to process this, unless im missing something very obvious... should i just grab, pass, then reset the session on the ajax call????
Ok, after i figured this out thus far, i just found this post Symfony CSRF and Ajax ill see if i can make heads or tails from it.
To see errors you should render that form. In your code when form is invalid the method returns $this->emailUser($data); but it should render the form. Try this:
if ($this->getRequest()->isMethod('POST')) {
//data is already validated by constraints added when the form was created since we are not attaching this particular form to any object
$form->handleRequest($request);
//alternatively (makes no differene from the former)
//$form->submit($request->request->get($form->getName()));
if ($form->isValid())
{
//have YET to see this
echo 'valid';
//this is the place to process data
//$data = $form->getData();
//return $this->emailUser($data);
exit;
}
}
return $this->render('ResourceBundle:Default:resources.html.twig', array(
'form' => $form->createView(),
));
That should render invalid form again on submit and show error
You can also try this
if ($form->isValid())
{
//have YET to see this
echo 'valid';
exit;
}else{
echo '<pre>';
\Doctrine\Common\Util\Debug::dump($form->getErrorsAsString(), 9);
echo '</pre>';
}
To show errors in debug style if form is invalid.

Categories