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!
Related
I'm new to Laravel and I am handed an existing application that is composed of two parts:
1 - An admin backend built on Laravel and uses Vueify
2 - The frontend website built on next.js and uses react components
The admin part communicates with Laravel using the "web routes" but also uses the "api routes" as well since the vue components make AJAX requests using those "api routes".
I am now tasked with "connecting" the frontend part to the laravel app. The frontend part will be using AJAX as well to communicate with laravel but I was told I should not use the same "api route" that is used by the admin backend because that has a lot more privileges that should not be accessible by the frontend. Basically it's a security risk and that I should somehow separate the two.
I'm not actually sure which term to use.. I initially thought it was called "channel" but I see that channel is one of the 4 "ways" of connecting to laravel (the other 3 being web, api and console). So I think routes is the term to use and forgive me for the double-quotes.
I have made a simple diagram to show the structure I mean. What I need to know is is there a way to create a second api route that would be used exclusively by the frontend and would include only a limited set of priviledges. I imagine something like /frontapi/ or /webapi/ as opposed to /api/ which is used now by the backend.
Thanks a lot for your help and please correct me if I am using wrong terminology.
EDIT
Thank you all for answering the part regarding separating the route prefix and the api route files.
One part of the question that I realized late that I hadn't made clear was the importance of separating the API Keys for both APIs since I think that is the main security issue and what would really make then two individual API "Channels or ways". I think that is one reason why I was confusing about the terminology because "way" sounded to me more separate that just a "route". I've also edited the question to reflect that. Thank you again for taking the time to help.
You can decompose routes in as many files as you want, you can also give each file its own prefix (like how api.php routes start with /api)
The modification need to be done in App\Providers\RouteServiceProvider
//in map() add $this->mapApiTwoRoutes()
public function map()
{
$this->mapApiRoutes();
$this->mapApiTwoRoutes();//<---this one
$this->mapWebRoutes();
}
//now add the method mapApiTwoRoutes
protected function mapApiTwoRoutes()
{
Route::prefix('api2')//<-- prefix in the url
->middleware('api')//<-- api middleware (throttle and such check App\Http\Kernal.php)
->namespace('App\Http\Controllers') //<-- you can modify the namespace of the controllers
->group(base_path('routes/apiTwo.php'));//<-- file containing the routes
}
And that's it.
You need to define a new route file, firstly add a new entry $this->mapApi2Routes(); in the map() function in app\Providers\RouteServiceProvider.
Then add a new function in that file, basically copying the mapApiRoutes() function, call it mapApi2Routes(). You can use different middleware etc. for the new file.
The last step would be adding a new file api2.php in the routes folder.
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.
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 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...
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