Generate CSRF token dependig on datetime - php

Exists some way in Symfony 2 to generate CSRF token at each rendering of form?
In my controller I tried something like this:
$request = $this->get('request');
if ($request->getMethod() != 'POST') {
$csrf = $this->get('form.csrf_provider');
$date= new \DateTime("now");
$this->date = $date->format('Y-m-d H:i:s');
$token = $csrf->generateCsrfToken($this->date);
} elseif($request->getMethod() == "POST") {
$csrf = $this->get('form.csrf_provider');
$token = $csrf->generateCsrfToken($this->date);
}
$form = $this->createFormBuilder()
....
->add('_token','hidden',array(
'data' => $token
))->getForm();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
DO something
}
}
All the time is in my request right token hash. But after bindRequest its change to default hash generated from security string in parameters.ini and isValid method returns certainly FALSE response. Exists some way, how to adjust it?
EDIT
In response to theunraveler answer, I edited my controller and create my CSRF provider, but still im geting "The CSRF token is invalid. Please try to resubmit the form" error. My CSRF provider looks like this:
namespace My\Namespace;
use Symfony\Component\HttpFoundation\Session;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
class MyCsrfProvider implements CsrfProviderInterface
{
protected $session;
protected $secret;
protected $datetime;
public function __construct(Session $session, $secret)
{
$this->secret = $secret;
$this->datetime = new \DateTime('now');
$this->session = $session;
}
public function generateCsrfToken($intention)
{
return sha1($this->secret.$intention.$this->datetime->format('YmdHis').$this->getSessionId());
}
public function isCsrfTokenValid($intention, $token)
{
return $token === $this->generateCsrfToken($intention);
}
protected function getSessionId()
{
return $this->session->getId();
}
}
Than I add to config.yml service class:
services:
form.csrf_provider:
class: My\Namespace\MyCsrfProvider
arguments: [ #session, %kernel.secret% ]
And Controller I change To this:
//any form without _token field
$form = $this->createFormBuilder()
->add('field1')
->add('field2')->getForm()
if ($this->getRequest()->getMethod() == 'POST') {
$request = $this->getRequest();
$form->bindRequest($request);
if ($form->isValid()) {
Do something
return $this->redirect($this->generateUrl('somewhere'));
}
}
Cant be problem in seconds in my hash? Becouse if I remove seconds from datetime format, it works, but with 1 min expiration and its not a good solution. I think, the reason is, that the time was changed.

Symfony's Form component generates the CSRF token in a Form event. See the class Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener on how the existing implementation works. That means when you call bindRequest() (or bind() in Symfony 2.2+) on your form, that form event gets called and overwrites the token you declared.
The correct way to define a different token is to create a CsrfProvider, which would basically be a class that you write that implements Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface. Check out Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider for an example of how to implement this.
Once you write your class, in your app/config/parameters.yml (or another config file, it doesn't matter where), set the form.csrf_provider.class parameter to the name of your class.
After all of that, you should be able to remove all of the custom code you wrote from your controller, and just use the form system as though nothing were any different (i.e., remove the ->add('_token')... parts).
Hope that helps!

Are you aware of form_rest? Add this to the bottom of your form to have it automatically generate your CSRF token:
{{ form_rest(form) }}

Related

SilverStripe functional testing if the data is saved in the session after making post request

I am working on a SilverStripe project. I am writing functional tests for my unit test. Following is the scenario I am trying to test. When a POST request is made, I save the data from the request body into the SilverStripe session. I want to assert/ test that the data are stored in the session.
This is my controller class
class CustomFormPageController extends PageController
{
private static $allowed_actions = [
'testPostRequest',
];
private static $url_handlers = [
'testPostRequest' => 'testPostRequest',
];
public function testPostRequest(HTTPRequest $request)
{
if (! $request->isPOST()) {
return "Bad request";
}
//here I am saving the data in the session
$session = $request->getSession();
$session->set('my_session_key', $request->getBody());
return "Request successfully processed";
}
}
Following is my test class
class CustomFormPageTest extends FunctionalTest
{
protected static $fixture_file = 'fixtures.yml';
public function testTestingPost()
{
$formPage = $this->objFromFixture(CustomFormPage::class, 'form_page');
$formPage->publish("Stage", "Live");
$response = $this->post($formPage->URLSegment . '/testPostRequest', [
'name' => 'testing'
]);
$request = Injector::inst()->get(HTTPRequest::class);
$session = $request->getSession();
$sessionValue = $session->get('my_session_key');
var_dump($sessionValue);
}
}
When I run the test, I get the following error.
ArgumentCountError: Too few arguments to function SilverStripe\Control\HTTPRequest::__construct(), 0 passed and at least 2 expected
How can I fix it? How can I test if the data are stored in the session?
I tried this too and it always returns NULL
var_dump($this->session()->get('my_session_key');
The error you get happens when you ask Injector for the current request before it has created one. This is because FunctionalTest nests the Injector state in which it executes tests.
You can still access the FunctionalTest session using $this->session(), as you've noted.
The main reason your test is failing is because your fixtured page is not published, and FunctionalTest operates in the live stage by default (I'm assuming this, because you didn't post your fixture). You can use the draft stage using protected static $use_draft_site = true; in your test class, or you can publish the page in setUp() or your test before you make the POST request to it.
Your next problem is that $request->getBody() is null in your controller, so it's not setting anything.
This works, for an example:
//here I am saving the data in the session
$session = $request->getSession();
$session->set('my_session_key', $request->postVar('name'));
$response = $this->post($formPage->URLSegment . '/testPostRequest', [
'name' => 'testing'
]);
$sessionValue = $this->session()->get('my_session_key');
$this->assertSame('testing', $sessionValue);

Protecting some properties of an entity from modification

I have a Symfony 3 app that uses Doctrine ORM for Entity management. Currently, I am working on enabling CRUD support. I've already found out that I can use security voters to restrict access to entities or controllers. For example, I configured it the way that only admins can create, update or delete entities of type A.
For instances of my entity type B I also want to give the respective owner the power to update (not create or delete), which I managed to do easily. However, an owner shouldn't be allowed to modify all of the entity's properties - just some of them. How can I realize this with Symfony? Also, I am using the Form Bundle to create and validate forms.
EDIT: I added some related code. The controller invokes denyAccessUnlessGranted, which triggers the voter. Just to clarify, that code works fine already. My question is related to code I don't yet have.
Controller:
public function editAction(Request $request, int $id) {
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository(Project::class)->findOneBy(['id'=>$id]);
$this->denyAccessUnlessGranted(ProjectVoter::EDIT, $project);
$users = $em->getRepository(EntityUser::class)->findAll();
$groups = $em->getRepository(Group::class)->findAll();
$tags = $em->getRepository(Tag::class)->findAll();
$form = $this->createForm(ProjectType::class, $project, [
'possibleAdmins' => $users,
'possibleRequiredGroups' => $groups,
'possibleTags' => $tags,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$project = $form->getData();
$em->flush();
return $this->redirectToRoute('projects_show', ['id'=>$project->getId()]);
}
return $this->render('project/editor.html.twig',
['project'=>$project, 'form'=>$form->createView()]);
}
Voter:
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) {
/** #var UserInterface $user */
$user = $token->getUser();
if (!$user instanceof UserInterface) {
// the user must be logged in; if not, deny access
return false;
}
else if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
return true; // system-wide admins shall always have access
}
switch($attribute) {
case self::SHOW:
return ($subject->isVisible() || $subject->getAdmins()->contains($user);
case self::EDIT:
return $subject->getAdmins()->contains($user);
case self::REMOVE:
return false;
}
return false;
}
As far as I know there is no access functionality specifically related to individual properties. Of course as soon as I post this, someone else will come by with exactly that.
What you might consider doing is to define two edit roles, EDIT_BY_ADMIN and EDIT_BY_OWNER. You could then test the condition and select which form type to use.
$projectTypeClass = null;
if ($this->isGranted(ProjectVoter::EDIT_BY_ADMIN,$project)) {
$projectTypeClass = ProjectAdminType::class);
}
elseif ($this->isGranted(ProjectVoter::EDIT_BY_OWNER,$project)) {
$projectTypeClass = ProjectOwnerType::class);
}
if (!$projectTypeClass) {
// throw access denied exception
}
$form = $this->createForm($projectTypeClass, $project, [
And that should do the trick. There are of course many variations. You could stick with one project type and do the access testing within the type class though that would require a form listener.
If you need more granularity then you could instead add some EDIT_PROP1, EDIT_PROP2 type roles.
And of course if you were really into it then you could move some of the access code into a database of some sort. Or maybe take a look at some of the Access Control List components out there.
I came up with this solution in the end:
Instead of having multiple FormTypes I stuck with only a single one and ended up enabling or disabling the property's form field based on the result of the voter. For that I defined a new switch case as Cerad suggested (named ProjectVoter::MODIFY_PROTECTED_PROPERTY in this answer for demonstration purposes) and added the business logic per my liking.
I just enabled or disabled the form field because I actually want the user to see that he/she can't edit that property. But it would likely easily be possible to not add the field in the first place as well, so it's not visible.
Form Type:
Info: $this->tokenStorage and $this->accessDecisionManager are injected services ("security.token_storage" and "security.access.decision_manager" respectively).
public function buildForm(FormBuilderInterface $builder, array $options) {
$token = $options['token'] ?? $this->tokenStorage->getToken();
$project = $builder->getData();
$builder
->add('name')
// ...
->add('protectedProperty', null, [
'disabled' => !$this->accessDecisionManager->decide($token, [ProjectVoter::MODIFY_PROTECTED_PROPERTY], $project),
])
;
}
I also added an option to the form type called token in its configureOptions function which defaults to null, so that the form can be built for an arbitrary user instead of the one currently logged-in, if required.

Symfony2: dynamic routing parameter

I'm turning to this forum because I can't find a valid solution to my problem.
I have taken over the management of a Symfony2 application which processes orders, invoices... inside a company and the problem is that there isn't archiving functions on it. So, the manager asked me to add archiving 'by year' functionalities to the application (simply display data depending on a chosen year).
So, I decided to prefix all application routes by /{year}/, parameter which will match the year the manager want to see and, as all the documents are dated, I just have to update Doctrine requests for picking those that match the chosen year. So far no problems.
routes.yml
mes_routes:
resource: "mes_routes.yml"
prefix: /{year}
defaults: {'year': %current_year%}
With this, I have created a Symfony Extension which fills the 'current_year' var by default in my route definition, with the actual year if no year is provided.
MyAppExtension.php
class MyAppExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
// Fill parameter with the current year
$container->setParameter('current_year', date("Y"));
}
}
Next, I have created a RouteListener that stores a previous route and its parameters inside a session var, when a user displays a new page (in order to display a same page but with a different year next)
LastRouteListener.php
class LastRouteListener
{
public function onKernelRequest(GetResponseEvent $event)
{
// Don't store subrequests
if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
return;
}
$request = $event->getRequest();
$session = $request->getSession();
$routeName = $request->get('_route');
$routeParams = $request->get('_route_params');
if ($routeName[0] == "_") {
return;
}
$routeData = ['name' => $routeName, 'params' => $routeParams];
// Don't store the same route twice
$thisRoute = $session->get('this_route', []);
if ($thisRoute == $routeData) {
return;
}
$session->set('last_route', $thisRoute);
$session->set('this_route', $routeData);
}
}
services.yml
myapp.last_route_event_listener:
class: MyApp\EventListener\LastRouteListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 30 }
And finally, I have added a new controller which, via a dropdown menu in the application navbar, displays the current page the user is viewing, but with a different year
ArchiveController.php
class ArchiveController extends Controller
{
public function switchYearAction(Request $request, $year)
{
$session = $request->getSession();
$lastRoute = $session->get('last_route');
$route = $lastRoute["name"];
$routeParams = $lastRoute["params"];
if (array_key_exists("year", $routeParams)) {
$routeParams["year"] = $year;
$session->set("current_year", $year);
}
return $this->redirect($this->generateUrl($route, $routeParams));
}
}
Arrived here, everything work. If a user chose an other date, the application will display the same page but with the new date chosen.
However, and there is my problem, if, from a previous year, the user clicks on a link in the page, we come back to the actual year. Quite normal, because Twig paths in the application doesn't fill the 'year' routing parameter, and the router provide the current year by default.
So, my question is : How can I keep the chosen year in memory, and use it as a route parameter ?
First, I had thought about setting the local var 'current_year' when the application uses the switchYearAction(), but Symfony returns an error ('Frozen variable')
Next, I had thought about using a session var to store the chosen year, but I can't access to the session within my extension MyAppExtension.
There might be a third solution which consists in update all Twig paths and Controller redirect(), but it represents some much line to edit...
Or maybe with a routeEventListener... but I don't know how to proceed.
Thanks you in advance.
You can access the application session in Twig using {{ app.session }}. So something like this is possible:
{{ url('myapp.whatever', {year: app.session.get('current_year')}) }}
Since you have a bit of logic around your current year stuff (if it's not set in the session fallback to the current year, etc), a twig extension that provides a funtion to fetch the current year may be a better way to go. Quick, untested example:
<?php
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class CurrentYearExtension extends \Twig_Extension
{
private $session;
private $defaultYear;
public function __construct(SessionInterface $session, $defaultYear)
{
$this->session = $session;
$this->defaultYear = $defaultYear;
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('current_year', [$this, 'currentYear']),
];
}
public function currentYear()
{
return $this->session->get('current_year') ?: $this->defaultYear;
}
}
Then add it to your container and tag it with the twig.extension tag.
<service id="myapp.currentyear_extension" class="CurrentYearExtension" public="false">
<argument type="service" id="session" />
<argument>%current_year%</argument>
<tag name="twig.extension" />
</service>
And use it in your route generation:
{{ url('myapp.whatever', {year: current_year()}) }}
If you need the current year other places than twig, then pull a re-usable object out of the twig extension and use that both with the extension and elsewhere.
Thanks to the answer given by #chrisguitarguy : https://stackoverflow.com/a/13495302/1031898 I found a way to resolve my problem.
In fact, I could use my routeListener to do the job.
I just needed to implement the Router Interface.
LastRouteListener.php (updated)
class LastRouteListener
{
private $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
public function onKernelRequest(GetResponseEvent $event)
{
// Don't store subrequests
if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
return;
}
$request = $event->getRequest();
$session = $request->getSession();
$context = $this->router->getContext();
// If current_year exists in session, replace route parameter with it
if ($session->has('current_year')) {
$context->setParameter('year', $session->get('current_year'));
}
// Else, we set the current year by default
else {
$context->setParameter('year', date('Y'));
}
$routeName = $request->get('_route');
$routeParams = $request->get('_route_params');
if ($routeName[0] == "_") {
return;
}
$routeData = ['name' => $routeName, 'params' => $routeParams];
// On ne sauvegarde pas la même route plusieurs fois
$thisRoute = $session->get('this_route', []);
if ($thisRoute == $routeData) {
return;
}
$session->set('last_route', $thisRoute);
$session->set('this_route', $routeData);
}
}
Don't forget to inject the #router argument in services.yml
myapp.last_route_event_listener:
class: MyApp\EventListener\LastRouteListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 30 }
arguments: ['#router']
And then, no need to use 'current_year' default parameter in route config anymore.
routes.yml
mes_routes:
resource: "mes_routes.yml"
prefix: /{year}
MyAppExtension.php
class MyAppExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}

Creating Contact Form using Symfony

I am trying to create a form using Symfony 2.5 using this tutorial, but this tutorial is using old version of Symfony. I can get the form to display and created the entity as well however I am now working on submitting the form. Following is the code from tutorial and this code is inside default controller contactAction
public function contactAction()
{
$enquiry = new Enquiry();
$form = $this->createForm(new EnquiryType(), $enquiry);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// Perform some action, such as sending an email
// Redirect - This is important to prevent users re-posting
// the form if they refresh the page
return $this->redirect($this->generateUrl('BloggerBlogBundle_contact'));
}
}
return $this->render('BloggerBlogBundle:Page:contact.html.twig', array(
'form' => $form->createView()
));
}
My main concerns is in the following section of above code
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
As you will notice it is using getRequest() which has been depricated and then my IDE is telling me buildRequest method cannot be found.
I will really appreciate if someone call push me towards the right path of converting the contactAction for symfony verion 2.5, I will really appreciate it.
Declare the action like this:
public function contactAction(Request $request)
{
...
}
And import:
use Symfony\Component\HttpFoundation\Request;
And you will have the request inside your action, so you can remove this line:
$request = $this->getRequest();
Hi there are a few deprecated calls also I would really recommend going to the cookbook in Symfony. But anyway this below will help.
namespace myproject/mybundle/controller;
use Symfony\Component\HttpFoundation\Request;
Class Act //that is me ;) {
/**
* #Route("/contact", name="_lalalalala")
* #Template()
*/
public function contactAction(Request $request){
$enquiry = new Enquiry();
$form = $this->createForm(new EnquiryType(), $enquiry);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($enquiry);
$em->flush();
return $this->redirect($this->generateUrl('BloggerBlogBundle_contact'));
}
return ['form' => $form->createView()];
}
}
You can shorten this code even further by using symfony services container to inject your form. I would recommend reading this it is pretty awesome. As you can re use forms anywhere :)
You can change the getMethod to isMethod like
if ($request->isMethod('POST'))
And then your can submit the request data to the form using submit like
$form->submit($request->request->get($form->getName()));
Alternatively you can use the handleRequest method that will handle the above 2 method in one go and then you can move on with the rest of your action like
$form->handleRequest($request);
if ($form->isValid())
.. etc
To retrieve the request object, you have two possibilities. Either let Symfony pass the request as an argument to your controller action ...
public function contactAction(Request $request)
{
// ...
}
... or get the request object from the container.
$request = $this->get('request');
For the binding of the request to the form, the manual states the following:
Passing the Request directly to submit() still works, but is deprecated and will be removed in Symfony 3.0. You should use the method handleRequest() instead.
This way, the request binding part would look similar to this:
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
// do something
}
}

Correct way of implementing custom authentification rest cakephp api

I'm currently developing a restful/stateless api in cakephp which uses tokens (at the moment) and should use rolling tokens (like suggested here from g-shearer) in the future. My current implementation works, but i'm really concerned if i've implemented everything the right way (auth components especially custom auth components seem really confusing to me)
PS: I'm using the current version of cakephp (2.5.1).
1: I've created the file TokenAuthenticate.php in Controller/Component/Auth:
<?php
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
class TokenAuthenticate extends BaseAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
}
public function getUser(CakeRequest $request) {
//set values from request headers
$publictoken = $request->header('Security-Public-Token');
$accesstoken = $request->header('Security-Access-Token');
$timestamp = $request->header('Security-Timestamp');
// check if required header fields are set
if (empty($publictoken) || empty($accesstoken) || empty($timestamp)) {
return false;
}
// init required token model
$Token = ClassRegistry::init('Token');
//check if token pair exists
if ($dbtoken = $Token->findByPublic($publictoken)) {
if ($accesstoken == md5($dbtoken['Token']['private'] . $timestamp)) {
//valid token - return user
$User = ClassRegistry::init('User');
$dbuser = $User->findById($dbtoken['Token']['user_id'])['User'];
return $dbuser;
} else {
//invalid token
return false;
}
} else {
//invalid token pair
return false;
}
}
public function unauthenticated(CakeRequest $request, CakeResponse $response) {
return true;
}
}
?>
then i've added the following to my controller:
class UsersController extends AppController {
public $uses = array('User', 'Token');
public $components = array('Auth' => array('authenticate' => array('Token')));
public function beforeFilter() {
parent::beforeFilter();
AuthComponent::$sessionKey = false;
$this->Auth->autoRedirect = false;
$this->Auth->allow('login', 'register');
}
in my actions i check the status like so:
if (!$this->Auth->loggedIn()) {
$this->set(array('error' => 'INVALID_AUTHENTIFICATION'));
$this->render('view');
}
So I can set a custom error and output it without being redirected to the login action (note the unauthenticated function in my tokenauthentication file which returns true - so cakephp does not redirect you)
I think the login process should happen in the authenticate function of my TokenAuthenticate file and not in the login action of my controller, or am i wrong? What is the correct way to achieve this goal?
PS: How would it be possible to add a new token pair (to every authenticated output) automatically with cakephp so the tokens are 'rolling'?
The whole api output is json encoded if that matters
also cakephp still sets a cookie sometimes even though i disabled this (AuthComponent::$sessionKey = false;). How to stop this?
EDIT: So I've added an beforeRender() function to my userscontroller and now the tokens are rolling (Y)
//renew tokens
public function beforeRender() {
//only add tokens if logged in
if ($this->Auth->loggedIn()) {
//find old token
$oldToken = $this->Token->findByUser_id($this->Auth->user('id'));
//delete old token
$this->Token->delete($oldToken['Token']['id']);
//create new token pair
$this->Token->create();
$this->Token->save(array(
'user_id' => $this->Auth->user('id'),
'public' => Security::hash(substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!##$') , 0 , 15 )),
'private' => Security::hash(substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!##$') , 0 , 15 ))
));
//set it for the view
$this->set(array('token' => $this->Token->read()));
}
}
Is this the right way to implement something like this? I always want to do things the right and 'perfect' way so any criticsm is welcome :)

Categories