Passing Delete Method Through sfGuard - php

I have an application with people and groups in Symfony, where a person may have a membership with multiple groups. To remove a person from a group, I currently have an action in a 'groupMembers' module that takes a 'delete' method and removes the pair from a many-to-many entity 'group_membership'. The route currently looks something like this:
remove_membership:
url: /group-membership/:group_id/:person_id
class: sfPropelRoute
options: { model: GroupMembership, type: object }
param: { module: groupMembers, action: remove }
requirements:
sf_method: [delete]
To perform this action, the user needs to be logged in, so I restricted it using sfGuard in the module's security.yml:
remove:
is_secure: true
So after clicking a 'remove' link, a user who isn't logged in is taken to the log in screen, but after clicking 'submit' the request is no longer a 'delete' but a 'get', meaning the similar add_to_group route is called instead!
add_to_group:
url: /group-membership/:group_id/:person_id
param: { module: groupMembers, action: create }
...
Is there any way to make sfGuard emulate a delete action and pass the parameters properly, or will I have to use a different route?

It seems that there is no way to achieve this without writing custom code or editing code of sfGuard plugin.
See plugins/sfGuardPlugin/modules/sfGuardAuth/lib/BasesfGuardAuthActions.class.php (its executeSignin method for details) how sign in is handled.
sfGuard gets user referrer and performs redirect with appropriate method of sfAction. This redirect is performed by using http header Location. So browser will use GET method to receive url content.
You can override default signin action and perform redirects from remove_membership route to an action which will use sfBrowser component to emulate POST request, but I highly recommend you to change routing scheme.

Related

Create route without path [Symfony3]

In security.yml in firewall/main/form_login I have this:
default_target_path: after_login
always_use_default_target_path: true
I want to create a route named after_login, but without a path, but symfony redirects me to homepage after login.
/**
* #Route(name="after_login")
*/
public function afterloginAction()
I want to nobody have access to this controller's method.
Is it possible to create such route or maybe another way to redirect after login to this method?
I want to add some variables to session but only once after login.
Best way approaching this would be listen on the security.interactive_login event:
default_target_path is not meant for that. That is just an redirect to an controller action, the user profile for example.
Using an controller action once and then make it not-accessible by setting a session key would be an ugly hack.
Read https://symfony.com/doc/current/components/security/authentication.html#authentication-events for that purpose. Using an event listener would make it hidden form the outside world automatically.
This is not possible with #Route. Is there wrong something here, why you want it?

Symfony routing for a REST API / Single page app

I'm trying to setup routes for my Symfony2 single page app and I'm not sure how to go about it correctly without it feeling super hacky.
Here is what I need it to do and how I've attempted to set it up:
When Authenticated
Any route requesting application/jsonshould hit the routes they have been setup for as usual.
Any route that is entered not requesting application/json should load a controller that renders a twig file containing all the JS for my single page app.
Any static resource that doesn't exist and ends looking for a symfony route eg [.js, .css, .jpeg, etc] should return 404.
When NOT Authenticated
Anything requesting application/json should return 403
Anything NOT requesting application/json should return to the login page
Here is what i've attempted so far:
Setup routes with the FOSRestBundle for each service
Setup a listener that returns the base controller html if the request isn't application/json
if (!in_array('application/json', $request->getAcceptableContentTypes())) {
$fakeRequest = $event->getRequest()->duplicate(
null,
null,
array('_controller' => 'HvHDashboardBundle:Dashboard:index')
);
$controller = $this->resolver->getController($fakeRequest);
$event->setController($controller);
}
Setup a bunch of 'catch all' routes to fake a 404 if the static resource doesn't exist.
# routing.yml
# Catch any files that are meant to be their own static resource and return 404
catch_all_fail:
pattern: /{uri}.{_format}
defaults: { _controller: MyBundle:Dashboard:return404 }
requirements:
_format: js|hbs|css|jpg|gif|jpeg|png
Issues
This approach feels like a massive hack, and is not how the Symfony routing system is intended to work
The base controller page is returned even if you aren't authenticated because the type listener is being hit before the security context and forcing that controller to render.
Question:
How do other solve this issue with routing and single page apps with Symfony where they initially need to render HTML with twig, then JS takes over and requests JSON?
Only make an API, no static pages at all. Trust me, I recently did a moderate sized API with Symfony and that's the way to go. It will simplify your backend security a lot, if you do not mix the API with static pages. Ofcourse you can still have static pages, if you want to have some sort of Landing page or something. but try not to mix them with the main app.
Even for login, do not make a static page, but instead have an api route that will validate username/password and return the user the auth token in response. One user can have multiple tokens for example (can be logged in at multiple locations), and the token is sent in the request headers everytime.
If the token is validated ok, symfony will know which user it belongs, so you will know the 'User'. If no token is present, it should return "Not Authenticated", and if token is invalid also something like that or 'Bad request'.
One thing that I had to do when doing with APIs is that I had to write a request listener to accept JSON content and transform it into request object, so I could access data with $request->request.
If you have any questions, let me know in comment and I can help.
As far as routing is concerned, follow the REST rules and you will be good to go.

URL routing patterns for frontend pages in PHP

Im creating a MySQL database driven PHP (W)CMS application which follows the MVC pattern. First take a look at the framework:
The MVC framework handles the request and decides what to load/call based on the URL, like: http://domain.com/user/details/121 will load and instantiate a User controller object, and calls its details(121) method with the userid passed as a parameter, and then instantiate a User_Model and "ask" it for the detailed data of the user with the 121 userid, and at last display the result with a View. This is the basic concept of an MVC architecture. Nothing particular, everything is clear at this point.
Whereas this will be a CMS, I want to handle a Page model. A user with the nesessary permissions (mostly admin and/or root) can perform basic CRUD operations and other stuff on a page, for example:
I can create a page with the:
tile = 'About us' (this will be displayed as a headline of the page or the title of the browser tab like eg.: HTML title and h1 tags)
URL denomination = '*about_us*' (this will be the URI endpoint, like: http://domain.com/about_us)
reference name = 'Who we are' (This is the text displayed in the menubar)
page content = 'lorem ipsum...' (The actual content of the page...by a WYSIWYG html text editor)
and much more options like structuring the pages, to assign sub pages under a parent page, or making a page startpage (which means if I set 'About us' as a start page, then http://domain.com will automaticall load that page content)...
Or I can modify these properties, even I can delete a page...etc.
The MVC framework makes no difference between handling a frontend and a backend call.
For example we have some requests:
http://domain.com/user/details/121
http://domain.com/about_us
http://domain.com/our_products/1255
The first will load a backend controller as I detailed before,
but the others will load a frontend content.
When the Bootstrap loads the appropriate controller/action we look for the actual controller file, in the example above :
/controllers/Users.php
/controllers/About_us.php
/controllers/Our_products.php
The first can be loaded because that is a 'static' controller written before, but the About_us and Our_products are not existing controllers so If it is impossible to load the controller, the bootstrap searches the database if is there a page with the same URL denimination (like: about_us, our_products). If there is, we load a common FrontEndController and display the requested page data, if there isn't, display a 404 error.
I do this because I want the bootstrap to handle all requests the same way, but I dont want to every frontend URL compulsorily contain the FrontEndController (e.g.:http://domain.com/FrontEndController/our_products/1255). So this is how I hide it from the user, so the URL can remain more user friendly. My question is: Is this a good practice? Or are there any other proper ways to do this?
The MVC framework handles the request and decides what to load/call based on the URL
What you would normally is have is some sort of Router and Dispatcher class. The router would accept the the user/details/121, parse it and return a Route.
$route = $router->route( $request->getUri() );
The router could hold config values like the allowed space character in URI's, default allowed characters etc.
You can also add custom routes to the router
$router->addRoutes($routes);
The custom routes can be a simple associative array
$routes['requested-uri'] = 'custom-route'
In the example above you said when they visit the root of the website you want them to actually see the About Us page so that could be done like this:
$router->addRoutes([
'' => 'about-us
]);
Meaning when the URI is ''(blank) then go to the 'about-us' route. It shouldn't do a redirect, just transparently load up a different route while keeping the URI in the clients web browser the same.
Routing can obviously be more complex, using route objects added to a route collection for more advanced custom routing with more control. Some frameworks use annotations and all sort of different ways to achieve flexible routing.
The dispatcher could then accept the route returned from the router and dispatch it. That means verifying if the requested route actually exists i.e does the controller file exist and the requested method in the controller exist.
$view = $dispatcher->dispatch($route);
Inside the Dispatcher::Dispatch() method:
// Check if the controller file exists.
// Instantiate the controller file, preferably using a controller factory.
// Check if the controller method exists.
// Call the controller method
call_user_func_array([$controller, $route->getMethod()], $route->getParams());
$view = $controller->getView();
$action = $route->getAction();
// Call the view method.
if( method_exists($view, $action) ) {
$view->$action();
}
return $view;
I find the following a very easy to understand way of dealing with controller methods/actions. Let's say you have a login controller, the user sends a GET request to it first and a POST request to it when sending the login details in the form.
public function getIndex() { }
public function postIndex() {
$username = $this->request->post('username');
$password = $this->request->post('password');
}
The get and post in front of the method name is the request type, this prevents you having to do something like this
public function index() {
if( $this->request->getType() === 'POST' ) {
$username = $this->request->post('username');
$password = $this->request->post('password');
}
}
It also gives you more control over authorisation(if you do it at the routing layer) because you can easily allow a user to send a GET request to the controller but deny them access to sending a POST request.
Each controller has a one to one relationship with a view. The view get's injected into the controller on construction, preferably using a controller factory.
What would happen when you send a GET request http://domain.com/user/details/121 is the router would break up the URI and turn it into a route targeting the User controller, the getDetails() method with the parameter 121, the dispatcher checks if the controller and method exist, it then calls the method supplying the user ID as an argument, the controller sets the user ID in the view. Below is the User controller.
public function getDetails($userId) {
$this->getView()->setUserId( (int)$userId );
}
The view then has a method called details(). The same name as the method called in the controller, just without the request type in front of it.
The dispatcher then calls the details() method of the view which then fetches the required data.
Setting the title of the page is done in the view, as it is for presentation purposes only.
Part of the view that is related to the User controller
public function details() {
// Fetch the user by using the previously set user ID from the controller.
// If he doesn't exist set an error template, set the response code to 404,
// or redirect. Do whatever you want really.
$this->setTitle('User Details');
// Build template objects, bind the fetched user data to main template.
}
How you implement the setTitle method and all over view related stuff is up to you.
The view sends the response back to the client, whether it is HTML content, JSON, XML, or any other content type.
For example your application lets you search for users and export them to a Microsoft Excel Workbook file(.xlsx) and prompt the user to download it.
The view would:
Fetch the users
Generate the file
Set the HTTP response headers like Content-Type
Send the response

CodeIgniter: some doubts about HMVC and Views

I've just discovered HMVC Modular Extension for CodeIgniter https://bitbucket.org/wiredesignz/codeigniter-modular-extensions-hmvc/wiki/Home and it seems perfect for my needs but I have some question.
Let's say I have two controllers:
Site which is the main controller and is used to show site's pages and may call methods of Users controller for example to show a form
User controller is used to authenticate users, to show login/sign up forms...
Now I have these questions:
If the user access the User controller directly (mysite.com/user/method) I want to show a full page while if i load a method of User from within the Site controller I want to show only a form (for example), is this possible?
What happens to view of a module loaded from another module: is the view shown automatically or i need to show it manually and how does the view behave?
If you method is being called via Modules::run()
There is a third optional parameter lets you change the behavior of
the function so that it returns data as a string rather than sending
it to your browser.
Eg:
//put underscore in front to prevent uri access to this method.
public function _module1()
{
$this->load->view('partial_view', array('some data'=>'some data'), TRUE)
}
call it inside your SITE view easily
Modules::run('User/_module1')
// should show whatever is in partial_view ie: a form
//an alternative is to pass in any params if the method requires them
Modules::run('User/_module1', $param)

Passing Params to Symfony Routes

I have a defined route that displays a dynamic page:
page_show:
url: /:domain_slug/:slug
class: sfPropelRoute
options:
model: Page
type: object
method: doSelectByDomain
param: { module: page, action: show }
requirements:
sf_method: [get]
This works great, but now I want my homepage URI to route to a specific page. To do that, I assume that I have to pass the :domain_slug and :slug values of the page I want to display as the homepage. That's fine, but I can't seem to track down any documentation or example that shows me how to go about doing that.
Is it possible to specify specific variable values in a route? In this case, I want to pass :domain_slug => portal, :slug => dashboard (that syntax doesn't work, btw). Essentially, I want to create a homepage route that looks something like this:
homepage:
url: /
class: sfPropelRoute
param: { module: page, action: show, array( :domain_slug => portal, :slug => dashboard ) }
options:
model: Page
type: object
method: doSelectByDomain
But different enough that it, you know, works. :-) I suppose I could create a simple route to a different method, modify the request parameters manually and forward to the executeShow() method, but that's a hack I'd rather avoid if a more elegant solution is available.
Thanks.
You can define values in the param key of the route... for example:
homepage:
url: /
class: sfPropelRoute
param: { module: page, action: show, domain_slug: portal, slug: dashboard}
options:
model: Page
type: object
method: doSelectByDomain
At least thats how it works with a non propel/doctrine route. I assume it should be the same with any type of route in the framework.
I never found a way to do this that worked (or maybe I just couldn't manage to do it correctly). My solution was to create the route and pass it through a custom method that does the work to specify the appropriate page details:
homepage:
url: /
class: sfPropelRoute
options:
model: Page
type: object
method: doSelectHomepage
param: { module: page, action: show ) }
requirements:
sf_method: [get]
The doSelectHomepage() method does all of the work I was hoping I'd be able to do by hardcoding values in the route itself.
Actually it did work, it was just the order in which the routes were defined. The first answer is correct. It may have worked for you if you added "homepage" rule BEFORE the "page_show" rule. So maybe Symfony isn't as stupid as I thought...No, no, it is. It's just not that bad.

Categories