Functional testing form with CSRF enabled in Symfony - forms

What is the best way of creating functional tests to test forms with CSRF protection enabled in Symfony?
Currently I have to add the following code before each form submittion:
$form = new sfGuardFormSignin();
$token = $form->getCSRFToken();
$token_name = $form->getCSRFFieldName();
Then I add the $token and $token_name to form parameters like this:
call('/login', 'POST', array (
'signin' =>
array (
'username' => $username,
'password' => $password,
$token_name => $token,
)))
The option suggested in the documentation:
'_with_csrf' => true,
Doesn't work at all.
Is there more simple way to avoid adding token to each form tested manually? Or is there a way to turn off csrf checking when running tests?
The way I've described above is ok when you have to test 1-2 forms but if project contains tens unique forms it becomes a pain.

Of course, you can't use _with_csrf option if you call directly the url.
You must pass from the form page, clicking on the submit button.
Like so:
click('signin', array('signin' => array('username' => $username, 'password' => $password), array('_with_csrf' => true)))
The string 'signin' must be adapted to your form. You can also use a more label-independent string, like 'form#myform input[type="submit"]' instead of 'signin', adapting the id of your form.
As already suggested, you can disapble CSRF for login, it's really useful for forms that modifies data.

I personally don't use functional tests that extensively (probably to my own detriment), but you could always switch the CSRF protection off in your form class for testing purposes.
public function configure ()
$this->disableLocalCSRFProtection();

You can disable csrf protection for all forms just by adding additional compiler pass:
class CsrfProtectionCompilerPass implements CompilerPassInterface
{
/**
* {#inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$env = $container->getParameter('kernel.environment');
if ($env == 'test') {
$container->setParameter('form.type_extension.csrf.enabled', false);
}
}
}
Or You can disable form extension completely by adding to config:
framework:
csrf_protection: false
btw, last solutions works only if You don't have explicitly set form option csrf_protection

I would turn off CSRF for testing environment.

You should get a CSRF token by showing the page including the form.
$browser->get('/login');
$dom = new DOMDocument('1.0', $browser->getResponse()->getCharset());
$dom->loadHTML($browser->getResponse()->getContent());
$domCssSelector = new sfDomCssSelector($dom);
$token = $domCssSelector->matchSingle('input[name="_csrf_token"]')->getNode()->getAttribute('value');

Related

YII : How to redirect in afterAction under component controller class "CController"

I need your help to resolve my issue. I'm stuck here from approx 3-4 hours.
I made custom roles and permissions to every user. I have executed the code under component controller class's function afterAction. But if user don't have the access of the clicked action then it should be redirect to error page. When i use redirect function it says Cannot modify header information - headers already sent. I will be highly thankful if anyone can help me out. Here is my code
if (isset(yii::app()->user->id)) {
$controller = yii::app()->controller->id;
$action = yii::app()->controller->action->id;
$noAuthControllerAction = array();
$noAuthControllerAction[] = 'site/index';
$controllerAction = $controller . '/' . $action;
if (!in_array($controllerAction, $noAuthControllerAction)) {
$isAllowed = $this->isAllowed($controller, $action);
if (!$isAllowed) {
$this->redirect(array('site/denied'));
}
}
}
parent::afterAction($action);
Always use accessRules() in your controller for roles and permissions for more information see Yii Documentation for authentication and authorization
In your controller
A basic role-based access control looks like this :
array('allow', // allow authenticated owner users to perform the following actions.
'actions' => array('sales', 'export', 'invoice', 'payment'),
'users' => array('#'),
'roles' => array('owner'),
),
A custom expression role-based access control looks like this : (This is what you need)
array('deny', // deny authenticated owner users to perform the following actions if store is not yet selected.
'actions' => array('sales', 'export', 'invoice', 'payment'),
'users' => array('#'),
'roles' => array('owner'),
'deniedCallback' => function() {
Yii::app()->controller->redirect(array('/store/location'));
},
'expression' => '!Yii::app()->user->isStoreSelected()',
),
'expression' is your rule, and if rule is not met then 'deniedCallback' will redirect you to desired 'controller/action' in this case '/store/location'.
Also don't use
$this->redirect(array('site/denied')) for error handling, instead use
throw new CHttpException(401,'Access denied.');
This is the right way to handle errors in Yii. If you want to customize your error page please refer to Error Handling
afterAction runs after action is rendered. This is the cause of your error.
Use beforeAction event for that. Do you know RBAC? RBAC can help you.
http://www.yiiframework.com/doc/guide/1.1/en/topics.auth#role-based-access-control
You could use accessControl for limit action uses to roles.

Zf2 multicheckbox at least one element is always required

I can't validate a zf2 form with multicheckbox because at least one checkbox is always required.
I found a lot of reference to this issue (for example here - https://github.com/zendframework/zf2/issues/4845), but i didn't found a solution for this.
Does anybody know how to solve this problem ?
UPDATE: I use a doctrine 2 objectmulticheckbox which is extended from zf2 multichechbox. As is commented below the override of getInputFilterSpecification method, will solve the problem with form validation, but the values will still remain in database (values populated by objectmulticheckbox).
I found a seemingly easier way to get around this issue by setting the input filter 'required' to false inside the controller, after the form is instantiated.
<?php
$form = new CampaignForm($multiCheckboxOptions); // Setting up checkbox in form class
$form->getInputFilter()->get('my_multi_checkbox')->setRequired(false);
?>
You can override the getInputFilterSpecification function on your form to set the field to not be required. For example:
public function getInputFilterSpecification() {
return array(
[...]
'the-multi-checkbox-field' => array(
'required' => false,
),
[...]
);
}
Ok i did a little hack to solve this problem.
So I added this code in the action controller:
$form->bind($client);
/** #var $request Request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
/** #var $client Client */
$client = $form->getData();
// hack because of - https://github.com/zendframework/zf2/issues/4694
if($request->getPost('reportSettings') === null){
$client->setReportSettings(null); // set null to remove all associations with this client
}
And also as it is described in the first answere, in form should be rewritten getInputFilterSpecification method, for field that shouldn't be required.

zf2 restful not reach update method

I made a restful controller that if I send the id the get method receives it. But when I update a form I expect the update method to process but I cant get to the right config for this and after 1 day with this issue I decided to right it down here.
Here the code involved
route in module config:
'activities' => array(
'type' => 'segment',
'options' => array(
'route' => '/activities[/:id][/:action][.:formatter]',
'defaults' => array(
'controller' => 'activities'
),
'constraints' => array(
'formatter' => '[a-zA-Z0-9_-]*',
'id' => '[0-9_-]*'
),
),
),
Head of controller:
namespace Clock\Controller;
use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\Mvc\MvcEvent;
use Zend\View\Model\ViewModel;
use Zend\Form\Annotation\AnnotationBuilder;
use Zend\Form;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Clock\Entity\Activity;
use \Clock\Entity\Project;
Wich contains the get method:
public function get($id)
{
$entity = $this->getRepository()->find($id);
$form = $this->buildForm(new Activity());
#$form->setAttribute('action', $this->url()->fromRoute("activities", array('action' => 'update')));
$form->setAttribute('action', "/activities/$id/update");
$form->bind($entity);
return array(
"activities" => $entity,
"form" => $form
);
}
That feeds this view:
<h3>Edit activity</h3>
<div>
<?php echo $this->form()->openTag($form);?>
<?php echo $this->formSelect($form->get("project"));?><br>
<?php echo $this->formInput($form->get("duration"));?><br>
<?php echo $this->formInput($form->get("description"));?><br>
<input type="submit" value="save changes" />
<?php echo $this->form()->closeTag($form);?>
</div>
After sending it, I expect update method in activities to take control, but I get:
A 404 error occurred
Page not found.
The requested controller was unable to dispatch the request.
Controller:
activities
EDIT:#DrBeza
This is what i get, that i think (not a master in routes) is right:
Zend\Mvc\Router\Http\RouteMatch Object
(
[length:protected] => 21
[params:protected] => Array
(
[controller] => activities
[id] => 30
[action] => update
)
[matchedRouteName:protected] => activities
)
--
That's it.
Any help?
Quick Fix
The RouteMatch object tries to dispatch ActivitiesController::updateAction but you have defined ActivitiesController::update
That's due to you using a Restful Controller. the Controller::update-Method is specifically tied to PUT-Requests. You need to define an extra method to handle updates via POST-Requests.
I suggest you define ActivitiesController::updateAction, make clear in the docblock it is meant to handle POST-Update requests and refactor both ::updateAction and ::update to share as much common helper-methods as possible for a fast solution.
Common URI Structur information
As a nice information to have when you start developing RESTful applications/APIs:
The ruby community suggests the following url-structure for your resources:
# These are restful
/resource GET (lists) | POST (creates)
/resource/:id PUT (updates) | DELETE (deletes)
# these are just helpers, not restful, and may accept POST too.
/resource/new GET (shows the create-form), POST
/resource/:id/edit GET (shows the update-form), POST
Detailed Problem Analysis
A restful update will be sent by an consumer via PUT, but browsers sending HTML-forms may only send GET or POST requests. You should never use GET to create something. So you have to use POST in a forms-context.
Looking at the problem from an architectural perspective a multitude of possibilities emerge, depending on how big your application is.
For a small application, tight integration (formhandling and API handling in the controller) apply best.
Getting bigger you may want to split up API-Controllers (only restful actions) from Helper-Controllers (form, website handling) which talk to your API-Controllers
Being big (multitude of API-Users) you will want to have dedicated API Servers and dedicated Website Servers (independent applications!). In this case your website will consume the API serverside (thats what twitter is doing). API Servers and Website Servers still may share libraries (for filtering, utilities).
Code Sample
As an educational example I made an gist to show how such a controller could look like in principle. This controller is a) untested b) not production ready and c) only marginally configurable.
For your special interest here two excerpts about updating:
/* the restful method, defined in AbstractRestfulController */
public function update($id, $data)
{
$response = $this->getResponse();
if ( ! $this->getService()->has($id) )
{
return $this->notFoundAction();
}
$form = $this->getEditForm();
$form->setData($data);
if ( ! $form->isValid() )
{
$response->setStatusCode(self::FORM_INVALID_STATUSCODE);
return [ 'errors' => $form->getMessages() ];
}
$data = $form->getData(); // you want the filtered & validated data from the form, not the raw data from the request.
$status = $this->getService()->update($id, $data);
if ( ! $status )
{
$response->setStatusCode(self::SERVERSIDE_ERROR_STATUSCODE);
return [ 'errors' => [self::SERVERSIDE_ERROR_MESSAGE] ];
}
// if everything went smooth, we just return the new representation of the entity.
return $this->get($id);
}
and the editAction which satisfies browser-requests:
public function editAction()
{
/*
* basically the same as the newAction
* differences:
* - first fetch the data from the service
* - prepopulate the form
*/
$id = $this->params('id', false);
$dataExists = $this->getService()->has($id);
if ( ! $dataExists )
{
$this->flashMessenger()->addErrorMessage("No entity with {$id} is known");
return $this->notFoundAction();
}
$request = $this->getRequest();
$form = $this->getEditForm();
$data = $this->getService()->get($id);
if ( ! $request->isPost() )
{
$form->populateValues($data);
return ['form' => $form];
}
$this->update($id, $request->getPost()->toArray());
$response = $this->getResponse();
if ( ! $response->isSuccess() )
{
return [ 'form' => $form ];
}
$this->flashMessenger()->addSuccessMessage('Entity changed successfully');
return $this->redirect()->toRoute($this->routeIdentifiers['entity-changed']);
}
That error message suggests the dispatch process is unable to find the requested controller action and therefore using notFoundAction().
I would check the route matched and make sure the values are as expected. You can do this by adding the following into your module's onBootstrap() method:
$e->getApplication()->getEventManager()->attach('route', function($event) {
var_dump($event->getRouteMatch());
exit;
});

Need help with routing in Mojolicious

I have the "Pages" controller with the "show" method and "Auths" controller with the "check" method which returns 1 if user is authenticated.
I have "default" page ("/profile").
I need to redirect to / if the user is authenticated and redirect all pages to / with the authorization form if the user is not authenticated. My code does not want to work properly (auth based on the FastNotes example application): (
auths#create_form - html-template with the authorization form.
$r->route('/') ->to('auths#create_form') ->name('auths_create_form');
$r->route('/login') ->to('auths#create') ->name('auths_create');
$r->route('/logout') ->to('auths#delete') ->name('auths_delete');
$r->route('/signup') ->via('get') ->to('users#create_form') ->name('users_create_form');
$r->route('/signup') ->via('post') ->to('users#create') ->name('users_create');
#$r->route('/profile') ->via('get') ->to('pages#show', id => 'profile') ->name('pages_profile');
my $rn = $r->bridge('/')->to('auths#check');
$rn->route ->to('pages#show', id => 'profile') ->name('pages_profile');
$rn->route('/core/:controller/:action/:id')
->to(controller => 'pages',
action => 'show',
id => 'profile')
->name('pages_profile');
# Route to the default page controller
$r->route('/(*id)')->to('pages#show')->name('pages_show');
It seems you want / to render either a login form OR a profile page. The code above will always show / as login because it hits that route condition first and will never care if you're authenticated or not.
Try a switch in your initial route for / (your default route after the bridge is unnecessary).
my $r = $self->routes;
$r->get('/' => sub {
my $self = shift;
# Check whatever you set during authentication
my $template = $self->session('user') ? '/profile' : '/login';
$self->render( template => $template );
});
A couple of notes on your example:
Its much easier to help debug issues if you use Mojolicious::Lite for examples.
Try using under instead of bridge.
Try using $r->get(..) instead of $r->route(..)->via(..)
Hope this helps.

How to implement PayPal in Symfony 1.4 & Docrtrine

I want to use PayPal, Express Checkout, in a Symfony/Doctrine 1.4.8 the current plugins all seem to be in Beta and also somewhat over the top in the way they are implemented. I can follow the logic of the PayPal provided information and code although some items are a bit vague as to how i deal with them in Symfony.
Any class files are ok as i create a lib directory and rename the class and this gets instantiated. However i have some plain procedural PHP files i.e. expresscheckout.php and i am not sure where to put this to load as it doesn't seem to fit in the templates. Perhaps it goes in the actions?
I am not looking for a line by line solution here (but if you have one feel free) but really a few pointers as to where the elements go. As i say i am still suffering form a bit of Symfony blindness.
Finally would i be better to implement a simple (is that possible?) plugin to handle this or group the paypal items in a module on their own?
What I did was write a class, I called it PaypalNvp, name the file PaypalNvp.class.php and put it in your /lib folder and put in functions for the Nvp Ops.
Then you can choose to either call the functions statically (change your class functions as needed) or initialize the class and call the functions...
So something like:
PaypalNvp::doExpressCheckoutPaypment($token, $payer_id, $amount, $currency, $payment_action);
or
$paypal = new PaypalNvp();
$paypal->doExpressCheckoutPaypment($token, $payer_id, $amount, $currency, $payment_action);
I don't think there is a set way of of saying which way is better... I use the latter method myself.
My class has a helper function in it that does the final communication operation with Paypal:
protected function api($data = array())
{
if (empty($data) || !is_array($data)) return false;
// INIT
$data = array_merge($data, array(
'VERSION' => $this->VERSION,
'PWD' => $this->PASSWORD,
'USER' => $this->USERNAME,
'SIGNATURE' => $this->SIGNATURE
));
array_walk($data, array(&$this, 'urlencode_walk'));
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $this->getUrl() . '/nvp',
CURLOPT_VERBOSE => 1,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => http_build_query($data)
));
$response = curl_exec($curl);
if (curl_errno($curl)) {
curl_close($curl);
return false;
} else {
curl_close($curl);
return $this->deformatNVP($response);
}
}
Main things you need to remember is to set the api method, e.g. SetExpressCheckout, and any required fields according to the PaypalNvp API