I am having a problem with the Zend Framework 2 formElement csrf.
It works fine until I submit an invalid form, hit the same page then refresh the page. A "notTheSame" validation error occurs on the field and the message "The form submitted did not originate from the expected site"
appears. And that is correct, because if I check the value of the csrf field, it is different from the one before submission.
The form was working fine before I decided to add the csrf field.
I am creating my csrf field as follow:
class SignupForm extends Form
{
public function __construct()
{
parent::__construct('signup');
$this->setAttribute('method', 'post')
->setHydrator(new ClassMethodsHydrator(false))
->setInputFilter(new InputFilter());
$this->add(array(
'type' => 'Zend\Form\Element\Csrf',
'name' => 'csrf',
'options' => array(
)
));
// I also add a couple of fieldsets after this
And in the view file :
<?php
$form = $this->form;
$form->setAttribute('action', $this->url('needfunding', array('action' => 'register')));
$form->setAttribute('class', "signup-form start");
$form->prepare();
echo $this->form()->openTag($form);
$applicant = $form->get('applicant');
?>
<?php $this->FormErrors($form); ?>
<?php echo $this->formRow($form->get('csrf')); ?>
(FormErrors is a view helper that retrieves the form messages and styles them)
In my controller :
public function signupAction()
{
$form = new SignupForm();
/* some unrelated code [...] */
$request = $this->getRequest();
if ($request->isPost()) {
$category_group_id = $request->getPost()->category_group;
$selected_categories = array();
foreach ($categories as $c) {
$selected_categories[$c->getId()]=html_entity_decode($c->getName());
}
$form->get('category')->setValueOptions($selected_categories);
$form->setData($request->getPost());
if ($form->isValid()) {
/* some unrelated code [...] */
return $this->redirect()->toRoute('signupconfirmation');
}
else {
}
}
return array('form' => $form, 'categories' => $ordered_categories);
}
I guess my question is, why is my csrf regenerated once I'm back on my form page because the form wasn't valid ?
PS: I could not find my solution in this post Zend Framework 2 CSRF Protection
I had experienced a similar problem.
In my case I was using a login form with an Zend\Authentication\Validator\Authentication validation.
The validator was destroying the session on each validation attempt, because it was using a Zend\Authentication\AuthenticationService with the default Zend\Authentication\Storage\Session storage.
Because the csrf value is stored in session, using the validator caused destroyal of the csrf value, so it had to be recreated on each login form POST attempt.
So, my advice is: try to check whether the session does not get destroyed during the refresh (it should not). This reference might help: http://framework.zend.com/manual/2.2/en/modules/zend.session.config.html
Related
I'm new in Silex and I can not find how to change SecurityServiceProvider to restrict access for 24 hours after 3 bad connections.
The authentication works perfectly.
Thank you very much for your help and ideas.
You can create a CustomAuthenticationFailureHandler which extends the DefaultAuthenticationFailureHandler.
Create a class:
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
class CustomAuthenticationFailureHandler extends DefaultAuthenticationFailureHandler
{
/**
* (non-PHPdoc)
* #see \Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler::onAuthenticationFailure()
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// create a failure counter for the access restriction
return $this->httpUtils->createRedirectResponse($request, $this->options['failure_path']);
}
}
share this class:
$app['security.authentication.failure_handler.general'] = $app->share(function() use ($app) {
return new CustomAuthenticationFailureHandler($app['security.http_utils'], array(), $app);
});
where this failure handler is matching to the firewall named general:
// init the firewall
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'general' => array(
'pattern' => '^/',
'anonymous' => true,
'form' => array(
'login_path' => '/login',
'check_path' => '/admin/login_check'
),
...
)
)
);
you will also need a CustomAuthenticationSuccessHandler which extends the DefaultAuthenticationSuccessHandler:
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
class CustomAuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler
{
/**
* (non-PHPdoc)
* #see \Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler::onAuthenticationSuccess()
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
// handle the 24 hour restriction for the user ...
return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
}
}
and share this class:
$app['security.authentication.success_handler.general'] = $app->share(function () use ($app) {
return new CustomAuthenticationSuccessHandler($app['security.http_utils'], array(), $app);
});
hope this helpful for you ...
Thanks to Ralf Hertsch for giving a clever answer, however as tekilatexee points, the Failure Handler does not accept an HttpUtils instance as first parameter to the constructor, if it extends DefaultAuthenticationFailureHandler without overriding the constructor.
Also, you don't need to write both Custom Handlers (one for failure, one for success), you could link only a Failure Handler, or only a Success Handler.
To instance correctly your Failure Handler extending the Symfony's default, the correct definition of the handler service is like this:
$app['security.authentication.failure_handler.general'] = $app->share(function() use ($app) {
return new CustomAuthenticationFailureHandler($app['kernel'], $app['security.http_utils']);
});
This passes the Symfony\Component\HttpKernel\HttpKernelInterface as first argument to the constructor.
However, if your Custom Handler instead of extending Symfony's default, implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface it just needs to implement onAuthenticationFailure that receives a Symfony's Request object and an AuthenticationException, and you could pass in the constructor, an instance of Doctrine\DBAL\Connection to log and check previous authentication failures, or any other custom set of services.
Hi everyone I'm new with Zend Framework 2 , for ruthentification on my project i used this module (( http://samsonasik.wordpress.com/2013/05/29/zend-framework-2-working-with-authenticationservice-and-db-session-save-handler/#comment-5393 )) and i add the field "Role" on data base.
I want to ask how can i make a specific route for any member of user, for example if the user’s Admin when he connect he will be redirected automatically to route “Admin” and if the user’s “visitor” he will be redirected to route “visitor” ???
Thx
/** this function called by indexAction to reduce complexity of function */
protected function authenticate($form, $viewModel)
{
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$dataform = $form->getData();
$this->authService->getAdapter()
->setIdentity($dataform['username'])
->setCredential($dataform['password']);
$result = $this->authService->authenticate();
if ($result->isValid()) {
//authentication success
$resultRow = $this->authService->getAdapter()->getResultRowObject();
$this->authService->getStorage()->write(
array('id' => $resultRow->id,
'username' => $dataform['username'],
'ip_address' => $this->getRequest()->getServer('REMOTE_ADDR'),
'user_agent' => $request->getServer('HTTP_USER_AGENT'))
);
// your userid -> select the role
$role = $this->getRoleUser($resultRow->id);
return $this->redirect()->toRoute('success', array('action' => 'index', 'role'=>$role));
} else {
$viewModel->setVariable('error', 'Login Error');
}
}
}
}
Then into your success page, just perform some actions using the param role
Don't forget to create a function $role = $this->getRoleUser($resultRow->id); to get the role of the user.
To implement roles function
check before this documentation to how to configure and create models/database: http://framework.zend.com/manual/2.1/en/user-guide/database-and-models.html
protected function getRoleUser($userid){
$table = $this->getServiceLocator()->get('User\Model\UserTable');
return $table->find($userid)->current()->role;
}
I have overlays and a hidden panel on my site which uses AJAX to pull in content from another controller/action. You click a link which pushState the URL and AJAX pulls in the content with setTerminal(true); so there is no layout wrapped around. These overlay/hiddenpanels are login/register and would also like them to already be in HTML when you request the page without ajax (refresh) so they can be deeplinked to.
Currently I have something like this in the register controller/action:
public function registerAction () {
$request = $this->getRequest();
// Possible $form = new RegisterForm(); with validation and error population etc
// If we're not requesting via AJAX, forward dispatch to index
if (!$request->isXmlHttpRequest()) {
return $this->forward()->dispatch('index', array(
'action' => 'index',
'overlay' => 1
));
}
$view = new ViewModel();
$view->setTerminal(true);
return $view;
}
If there isn't an XmlHttpRequest then it forward dispatches to index/index with the following check:
public function indexAction () {
$routeRequest = $this->getEvent()->getRouteMatch();
$view = new ViewModel([]);
// Check if user went to somewhere like register page
$overlay = $routeRequest->getParam('overlay', null);
if (null !== $overlay) {
$view->overlay = true;
}
return $view;
}
In my layout I'm checking if the view has the overlay view variable set and then including the overlay in HTML for the previous action like so (controllerName and actionName ViewHelpers get populated onBootstrap so contain register as controller etc):
// Layout.phtml
if ($viewVariables->overlay) {
echo $this->partial('application/' . $this->controllerName() . '/' . $this->actionName() . '.phtml');
}
But this currently doesn't contain any data from the registerAction. I was thinking of having a registration form and having everything registration related done inside there as well and having it accessible where-ever I might forward dispatch to but now it's getting rather complex forwarding and passing variables about.
I was thinking of creating a ViewHelper like echo $this->overlay() which would contain ViewVariables from the previous view and include the other view as a partial but then also thought about passing the view from registerAction to indexAction via the forward dispatcher and nesting it.
Right now i'm triggering statechange with JS at the bottom of the page which takes the current URL and grabs it via AJAX but making the user wait for a full page render and then loading symbol after that is confusing when it can already be there.
This seems a rather complex problem as I'm not too familiar with what's available. I'm seeing alot of modules with the eventManager so am wondering how others would approach this?
Managed to do this and it was easier than I expected.
// RegisterController/indexAction
public function registerAction()
{
$request = $this->getRequest();
$view = new ViewModel();
// If we're not requesting via AJAX, forward dispatch to index
if (!$request->isXmlHttpRequest()) {
$view->setTemplate('application/register/index');
return $this->forward()->dispatch('index', array(
'action' => 'index',
'overlay' => $view
));
}
$view->setTerminal(true);
return $view;
}
// IndexController/indexAction
public function indexAction()
{
$request = $this->getRequest();
$routeRequest = $this->getEvent()->getRouteMatch();
$view = new ViewModel();
$overlay = $routeRequest->getParam('overlay', null);
if (null !== $overlay) {
$view->addChild($overlay, 'overlay');
}
return $view;
}
My only gripe with this is that I have to manually set the template for the child view before I forward dispatch it, as it gets populated onDispatch with a default path if none was specified.
public function registerAction () {
$request = $this->getRequest();
// Possible $form = new RegisterForm(); with validation and error population etc
// If we're not requesting via AJAX, forward dispatch to index
if (!$request->isXmlHttpRequest()) {
return $this->forward()->dispatch('index', array(
'action' => 'index',
'overlay' => 1
));
}
$view = new ViewModel();
$view->setTerminal(true);
return $view;
}
Hi I created two modules first application second comment.
Idea is to use comment module(Widget) in any application action (website page).
Application module
Test controller
public function commentAction(){
//seting redirection for form
$this->getCommentService()->setRedirection('test/comment');
$list = $this->forward()->dispatch('comment_controrller', array('action' => 'list'));
$add = $this->forward()->dispatch('comment_controrller', array('action' => 'add'));
$view = new ViewModel();
$view->addChild($list, 'list');
$view->addChild($add, 'add');
return $view;
}
View
Comment module
Comment controller
public function addAction()
{
$form = new CommentForm();
$form->get('submit')->setAttribute('value', 'Add');
$request = $this->getRequest();
if ($request->isPost()) {
$comment = new Comment();
$form->setInputFilter($comment ->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$comment ->exchangeArray($form->getData());
$this->getCommentTable()->saveComment($comment);
// Redirect to test controller in application module
return $this->redirect()->toRoute($this->getCommentService()->getRedirection());
}
}
return array('form' => $form);
}
public function listAction()
{
return new ViewModel(array(
$list=> 'test'
));
}
With simple variable (list) all working fine,
Problem I get when trying to redirect form back to comment action in test controller
I can add redirection to test/comment in case form is not valid
but how I will pass all validating errors to test/comment(form)
Can you tell me, if what I'm doing logically correct or in ZF2 we have different way to do widgets
Thanks for help
Answer from weierophinney
http://zend-framework-community.634137.n4.nabble.com/zf2-widget-base-app-logic-td4657457.html
This what I've got so far:
https://github.com/nsenkevich/comment
I have a need to display a form to enter a blog post on the same page on which a news story is being displayed. The user is entering a blog post related to the story.
In my blog form I'm currently doing this to get the id of the story being displayed:
public function configure()
{
$this->setWidget('image_filename', new sfWidgetFormInputFileEditable(array(
'file_src' => '/uploads/blogpost_images/thumbnails/thumb_'.$this->getObject()->image_filename, //displayed for existing photos
'edit_mode' => !$this->isNew(),
'is_image' => true,
'with_delete' => false,
)));
$this->setValidator('image_filename', new sfValidatorFile(array(
'mime_types' => 'web_images',
'required' => $this->isNew(),
'path' => sfConfig::get('sf_upload_dir').'/blogpost_images',
'validated_file_class' => 'BlogPostValidatedFile',
)));
$this->setValidator('url', new sfValidatorUrl(array('required' => true)));
$this->setValidator('title', new sfValidatorString(array('required' => true)));
$this->setWidget('user_id', new sfWidgetFormInputHidden(array(),array(
'value'=>sfContext::getInstance()->getUser()->getId())
));
// get the request params to access the notice ID and pass it back to the form for
// saving with the blog post
$params = sfContext::getInstance()->getRequest()->getParameterHolder();
$this->setWidget('notice_id',
new sfWidgetFormInputHidden(array(),array(
'value'=>$params->get('id'))
));
$this->removeFields();
}
Is there a cleaner way of doing this? It feels hacky to be taking the id of the notice (news story) from the request parameters.
UPDATE
I'm actually posting the form from a modal dialog via ajax and trying to maintain the notice_id value across requests. I am binding my parameters with the form before returning it to display errors:
public function executeModal(sfWebRequest $request) {
if ($request->isXmlHttpRequest()) {
//return $this->renderText('test'.$request->getParameterHolder()->getAll());
$params = $request->getParameter('nb_blog_post');
$form = new nbBlogPostForm(null,array('notice_id',$request->getPostParameter('notice_id')));
$form->bind($params,$request->getFiles());
if ($form->isValid()) {
$nb_blog_post = $form->save();
$this->getUser()->setFlash('notice', 'Your blog post was successfully created');
$this->redirect('#noticeboard');
} else {
return $this->renderPartial('form',array('form'=>$form,'form_id'=>'blogpost'));
}
}
}
I can't seem to get the notice_id to be bound with the form (it is a hidden field). The other values are binding fine.
I've also tried $form = new nbBlogPostForm(null,array('notice_id',$request->getPostParameter('nb_blog_post[notice_id]')));
FURTHER UPDATE
On the first pass through the form configure method relies on the notice_id being in the request, I think it was setting this to null when the form was created again via ajax. This fixes it:
$params = sfContext::getInstance()->getRequest()->getParameterHolder();
if (($params->get('id'))) {
$this->setWidget('notice_id',
new sfWidgetFormInputHidden(array(),array(
'value'=>$params->get('id'))
));
} else {
$this->setWidget('notice_id',
new sfWidgetFormInputHidden());
}
If anyone has a cleaner way please let me know.
The correct way is:
$form = new YourForm(null,array('notice_id' => $notice_id));
As you mentioned the first parameter must be an object so null is given, the second parameter should contain the variables to be passed to the form
UPDATE
With the new info provided. You have two options, first one:
Create a new method inside your form like this:
public function setNoticeId($id){
$this->getWidget('notice_id')->setAttribute('value'),$id);
}
Then call it after the creation of the form within your action:
$this->form = new Form();
$this->form->setNoticeId($id);
The second one is not to set the notice id when you display the form but pass such parameter within create action in the way I mentioned before:
public function executeCreate(sfWebRequest $request)
{
$this->form = new Form(null, array('notice_id'=>$id));
$this->processForm($request,$this->form);
}
Then you have to use it inside your form save method (at the beggining) and assign it to the desired field like this:
$this->values['notice_id'] = $this->getOption('notice_id');
The firs method is cleaner but the latter is useful when you need some data that is not related to your object, for example, if you need to save an image with your post inside the user's folder and you need the name of such user folder.
I hope this helps.
I think that the easiest and convenient way to do this is to pass the default value for form element when creating new form object.
Like this:
$form = new YourForm(array('notice_id' => $notice_id));
This will automatically set the value for your form element. (And also work for all form elements.)
Regards.