I have access_control setup in the security configuration of my Symfony app so, clearly, the application can detect if the current user has access for the current request (that works just fine). What I want to be able to is have the app figure out if the current user would have access to another request (eg. a different path or method) from a Controller action.
What is the proper way to do this in Symfony? I can hack together going through and accessing the FirewallContext of the current request and looking for the proper listener with the proper AccessMap but this is ugly and I fear that it will break easily. Any suggestions?
This question has gone unanswered for almost a week now so I've found a decent workaround solution in the meantime, if anyone finds this and wants to do something similar.
First of all, I pulled the functionality from AccessListener::handle(GetResponseEvent) out into a new class/method Authorization::checkAccess(Request) which takes a Request object instead of a GetResponseEvent.
Next (and the necessity of this depends on whether or not checkAccess differs from the way handle handles requests), I created a separate class to override AccessListener and use Authorization::checkAccess(Request) to do the checking (and swapped it out in the configuration by setting the security.access_listener.class parameter)
Next, setup a service in the configuration to construct an Authorization object with all of the parameters that are injected into the AccessListener.
Finally, in order to check a particular request, I use this slice of code in the controller:
$check = $this->getRequest()->duplicate();
$check->server->set('REQUEST_URI', $requestUri);
$check->setMethod('GET');
$this->get('my_access_control_service')->checkAccess($check);
I hope this helps someone out there...
Related
Session is retrieved in SF2 through the Request object. Hmm, this becomes kind of problematical when some architecture is considered - in cases where access to a session vars is needed from within a service.
Or perhaps, I'm not quite correct in this matter? (Hope so).
Obviously each request from a user through a webbrowser is a Request. So as long as we use controller actions in standard SF2, we have Request. But should we then pass the Request object to any service we need?
Passing Request object to all of the services that needs their methods to be run (e.g. storing info, checking settings, putting filters for displaying data etc. - well in some bigger apps it's quite a lot of them!) because it might be needed because of the dependent services, seems to be a very stupid idea to me. It also breaks the "S" in S.O.L.I.D. recommendation for OOP.
So I came up to a conclusion I need either to:
Pass the Request obj to many services just because the dependant service might need some data from it (i.e. broken "S" as above)
Retrieve and process data from Request every time I needed in every single controller action (i.e. code duplication) - in this case I don't pass a Request obj, but previously prepare all data needed - but I must do it then in many action methods in almost all controllers (retrieving/processing data from Request is just a simple call for another service, but then it's not centralized)
I'm putting this question, because I have e.g. the following problem to solve:
I'm using the same filters for all different data (from different data-sources) on the whole page.
Filters can be enabled and disabled - and they should be remembered for all the pages for the single session
I decided that saving "disabled" filters to a session is probably the best approach (because by default all data should be seen, i.e. all filters should be in "enable" state)
The 3rd point - saving data (filters) to a session - is what makes trouble in SF2, as described above. For displaying filtered data on page, I need access to a session, and thus access to the Request obj. And this means I have difficulties in keeping "S" in SOLID, because of making dependency on a service's method to always pass a Request obj to it.
Is there any other, better solution than the 2 mentioned (i.e. one, breaking SOLID, or two, code duplication)?
The session is also a service in the symfony di container, you can simply inject session into your servives
Coming from a straight PHP and Drupal background, I am recently learning the Symfony2 framework. Currently I am in the routing chapter of the book. This is probably a simple question.
What are some real world use cases for why one would want to generate URLs in a Symfony app? I understand the code but I'm having a bit of trouble determining its practical applications.
I'm referring to this section should you need a refresher.
As always, thank you!
P.S. Symfony is amazing. :)
Basically, you need to generate a URL whenever you need to link to anywhere in your application.
Let's say that you have an application that needs to manage some users. This means that you will probably have URLs like /user/create, /user/edit/(user id) and /user/remove/(user id).
Whenever you display a link to edit a user you need to know on what URL you can find the page that allows you to edit a user. So you need to link to /user/edit/(user id). One solution would be to have this as a fixed link so that in your code you could just write
edit this user
But what if you want to change this URL scheme? Let's say someone is unhappy with the term "user", after all the humans managed by this system are not only users, they are actually "person"s! So now you need to change all the URLs containing "user". Probably there are quite a few places in your app where you have had to hardcode these URLs and now you will need to find and change all of them. Eugh.
But fear not, for Symfony routing comes to the rescue!
Instead of hardcoding these URLs, we can simply let the Symfony router generate them for us. This means that we first need to tell Symfony which routes we have, e.g. by adding the following YAML code to our routes config file:
user_edit:
path: /user/edit/{userId}
defaults: { _controller: AppBundle:User:edit }
requirements:
userId: \d+
This tells our application "Okay, whenever somebody requests a page that looks like /user/edit/{userId}, then you need to call the editAction method in our UserController class in the AppBundle namespace and you need to pass the userId as a parameter. Oh, and also you should only call the controller if userId is a valid integer with at least one number."
So this is how Symfony knows how to map URLs to controllers. But the goodness that comes along with it is that we can use this information for the reverse way as well.
Usually, in our application we do not really care about what the URL looks like for a certain action we want to perform. All we know is that when clicking a certain link, then the browser should jump to a page that allows me to edit a user. And since we just defined a route that takes us right there, we can have Symfony generate the correct URL to achieve just that.
So in your view you can now discard the hardcoded URL from earlier and instead replace it with a route generated by the Symfony router:
edit this user
Now whenever you need to change what the URL actually looks like all you need to do is edit your routing config and not a lot of separate views.
Because, imagine you want to change a given page URL and you've hardcoded it in 10 Twig templates. You will have to modify all these files. On the opposite, when using the routing component:
You would only have to change the URL where the route is defined.
The routing component "takes" care of the current environment you are using (dev, prod...)
Also note that is a bad practice to "switch environment", a typical issue is to hardcode an URL in a Javascript. In this case you can expose your Symfony2 routes in the Javascript by using a bundle like FOSJsRoutingBundle.
I almost immediately realized their use and now I feel silly. :) For those who stop by this question in the future:
Notes about Generating URLs:
Similar to the Drupal l() function, sometimes you need to generate links inside your application based on a variety of parameters.
You don't always want to hardcode your links in case you decide to change paths sometime down the line.
In summary: Think of this as an alternative to using straight anchor tags with href elements all over the app and, instead, keeping things dynamic.
Use case
In the project I'm working I use generateUrl to redirect the user
after creating, editing an entity.
For example after creating a Service entity, I redirect the user to the view
of the just created Service.
Controller
return $this->redirect($this->generateUrl('myentity_view', array('id'=> $id)));
Additional note
In twig files, you can use the path function which call the routing component and generate url with given route name and parameters.
Context: I'm developing a turn-based game that requires client-server communication. The client uses RESTful requests via HTTPS and the server uses JSON responses. I'm not using REST because I need to expose the API, but because the REST paradigm was comfortable to get the client/server interaction sorted out. The server is to be written in PHP.
To help me out with routing the REST requests I've been searching for a useful routing library. There are a staggering amount of them available, but I have difficulties finding one that fits my specific needs. My problem is this: a player should only be allowed to follow certain routes if certain conditions are met. Obviously I can check if the conditions are met in the method that is called after a route match, but this seems an error-prone approach because conditions are the same for many routes. It would be much simpler to first do a partial match, then decide some conditions must be met to follow more specific routes under the partial match.
To illustrate, there are 4 levels of 'authentication' in the game:
Not authenticated
Authenticated as {id} (we know which player it is)
Authenticated as {id} and participant in session {sesid}
Authenticated as {id}, participant in session {sesid}, AND it's the player's turn
Assume for now that authentication happens in the background. Routes you can follow on each level, progressively:
LEVEL 1:
POST \Players- sign up
LEVEL 2:
PUT \Players\{id} - change profile
GET \Players\{id}\Sessions - get list of sessions
POST \Players\{id}\Sessions - create session
POST\Players\{id}\Inventory - buy items
PUT \Players\{id}\Invites\{sesid} - join invitation for session
DELETE \Players\{id}\Invites\{sesid} - decline invitation for session
LEVEL 3:
GET \Players\{id}\Sessions\{sesid} - get session state
DELETE \Players{id}\Sessions\{sesid} - cancel session
LEVEL 4:
PUT \Players\{id}\Sessions\{sesid}\... - set several session state parameters
So I wish to be check a few parameters up front before matching the next set of routes. I've spent quite some Google and Packagist time finding a suitable routing package (I've looked at the docs for Klein, Zend, PHP-Router, Fat_free, Slim, TORO, Aura, FlightPHP, Phalcon, FuelPHP to name but a few) but almost all libraries require that you define routes up front and find a single match in one go - sometimes allowing you to set an order, and sometimes going from most specific to least specific, but mostly executing only one route.
I would be somewhat helped if I could do a partial route match in a defined order - for example, any route starting with players\{id} first checks authentication, exits when it's not there, whilst continuing to check the next pattern in defined order if authentication is OK.
Any routing library that would allow me to match and execute a route on the spot would also help - provided that it can do partial matches so that I can pick up parameters I require to check the authentication level requirements (id, sesid). Obviously, to keep things slim I'd prefer a library that is not part of a larger framework.
From the documentation pages I've read at Packagist, I have difficulties finding out if you can partially match routes in certain libraries - match parameters, yes, but routes? - and sometimes it's unclear if the first match found is the only match. Any pointers?
Or am I missing a more straightforward solution here?
Well, to start with, Klein will let you execute more than one route for the same callback, like this:
// This route matches everything
$klein->respond('*', function ($request, $response, $service) { myAuthFunction($request); });
//other routes
// This route matches only a specific path
$klein->respond('GET', '/Players/[i:id]/Sessions', function ($request, $response, $service) { echo "This is the Sessions page for User ID $request->id"; })
Check out the section on routing, and scroll to the paragraph that starts with "Note." If I remember correctly, the routes are executed in the order they are declared, so you catch all route you use for checking credentials would need to be first, before the more specific ones.
In your auth function, you need to throw an exception, in order to prevent the later route from being run, and then catch it. This link shows how to catch throw HTTP errors. To throw one, simply call $router->abort(404) inside your callback. You have to send $router (your Klein instance ) using use, so your callback would actually be:
$klein->respond('*', function ($request, $response, $service) use ($klein) { myAuthFunction($request); });
There's also a routing namespace system, which I looked at, but haven't used myself, but might be helpful for what you're trying to do.
Lastly, what I ended up doing, was grouping my actions into Controllers, and doing a permission check in the constructor for the controller. I don't want to waste a lot of time explaining how to set that up if you don't think it's going to be relevant for you, but I can provide more details upon request.
A final note, the docs say to install Klein like this:
php composer.phar require klein/klein v2.0.x
But I've found that the dev-master code works better, so I would recommend doing this:
php composer.phar require klein/klein dev-master
Hope that helps!
I am attempting to create a controller that can detect if it is called from another controller in the application.
If it called directly via the URL, however, I need to know so I can perform some other actions.
i.e.
cheese/modulename calling potato/modulename is different to someone accessing site/cheese/modulename via URL - and I need to be able to pick up on this and act accordingly.
I am aware of:
$this->router->class
but it will not work as I may have the same named class in another module (HMVC pattern as an FYI) that may want to call this controller (cheese/modulename calling potato/modulename as an example would return 'modulename' - so I can't use that as a check to see if it was called by itself.)
I basically need a check for:
controller was called via another controller = true / false
can anyone tell me how (or if I am being thick!)
I am doing this in the __construct() just in case your solution will have a problem with that (can't see why but you never know!)
EDIT
Thank you to Mohammad Walid for his answer.
For clarity the structure is
CLIENTS
MODELS
CONTROLLERS
- Client
- Api
VIEWS
JOBS
MODELS
CONTROLLERS
- Jobs
- Api
VIEWS
I will be calling the API from Client - but may also call it from another API (possibly) That may be
In another Module
For Example the CLIENTS Api might get called from the JOBS Api Controller (I have no intention of doing this at present but it may be a possibility under different scenarios I haven't forseen and want to make it future-proof so I don't have a massive refactoring job in the future.)
You can try this:
function is_called_via_url($controller_object){
return $this->uri->segment(1) === get_class($controller_object);
}
and in your controller:
if(is_called_via_url($this)){
//do something
}
I'm not quite sure if passing $this as an argument in the constructor will work, but it worth try.
Reference and a hint from MonkeyZeus's comment.
From the comments there seems to be no way to do this without using debug_backtrace($options, $limit)
However the work-around I have ended up doing is to add a 'flag' within the authorisation module (which is called before all controllers)
The flag defaults to false.
If a controller from within my application calls the API page I turn this flag to true (is_application = true - I am currently just manually pasting this into the __construct of any controllers in my application that need to talk to my API)
I can then just do a simple
if(!is_application){
//this was called directly from the URL not from within the application
}
from within the API controller.
Hopefully this will be helpful for others who are attempting this sort of thing and once again thank you to the people who took the time to comment / answer.
I'm adding an API to a Symfony-application which should act as a REST web-service. But there are a few open issues.
Different URIs for bots?
I often read the "suggestion" to use URIs like /api/:id/[...], but I think they wouldn't be REST-compliant: No matter whether bot or human - the same unique resource is identified.
I'm asking, since my statement above makes sense, but I don't expect all the others to be at fault.
Modifying existing controllers?
There are several reasons why I need a separate controller-logic for both cases:
No session-login in the case of a api-requests
different Symfony-forms have to be created (For instance, no widgets are required, at all.)
JSON / XML instead of HTML output
I don't want to modify existing controllers. According to the Open-Closed Principle, classes should be open for extension but closed for modifications, and the controller classes are already in use in a "production"-environment.
My idea is to use an extra HTTP header-field (e.g. "X-UseApi"). The routing should call different actions by evaluating it. Is this possible inside routing.yml? How? Do you have other ideas?
Authentication
This is how I implemented bot-authentication:
$user = Doctrine_Core::getTable('sfGuardUser')->findOneByUsername($params['user']);
if($user->checkPassword($params['password']))
{
//...
}
But the code looks like a workaround to my eyes. Are there better solutions for the whole REST authentication issue? Is the sfGuardPlugin / sfDoctrineGuardPlugin not meeting conditions for such use cases?
Thanks in advance and cheers,
fishbone
my way of doing this would be to use sf_format in routes to distinguish between robot and human (robot will probably need to consume XML whereas human will want HTML.
I would alter my controllers in a way that I would delegate the logic to separate classes depending on what format is requested (this shouldn't be too much work and you would get the flexibility you need).
As for authentication - please provide a bit more information on how do you do it now - the example isn't enough for me to get the general idea of how your implementation works.
Different URIs for bots?
I suggest to not worry too much about URIs. There are more problems with them and thinking too much about it just results in losing time. IMHO it would be great if there would be standardized conventions how to define RESTful URIs. Here is an article about it: http://redrata.com/restful-uri-design/ . You can see that every way of designing your uris has its pros and cons.
But today I would reject the statement that 'api/...' isn't REST compliant. I would just avoid it.
Controller and authentication
Finally, my solution was to implement some sfFilters with responsibilities as follows:
ApiAccessFilter: sets request-attribute 'isApiRequest' if X-ApiKey is defined as header field.
ApiKeyAuthFilter: identifies a user by X-ApiKey, calls signIn / forwards to login-action.
SecureApiAccessFilter: Checks whether the current user has credential
'apiWriteAccess', if HTTP-method is POST, PUT or DELETE.
The first filter allows me to call $request->getAttribute('isApiRequest') later in my actions. That's similar to isXmlHttpRequest(). Finally I came to the conclusion that I have to modify existing actions, because requirements have changed due to the web-service extension.
Cheers, fishbone