Cake PHP3 validate rest API header parameters - php

I'm developing REST API with cake PHP3 for the mobile application.
Every request header has custom parameter call X-App-Key for the verify mobile app. (Unique ID for the app - X-App-Key : '123456789')
I need to check that parameter value before give access to API endpoints.
How do it check from bootstrap.php or any other place. (in controllers).
Can this use cakephp Dispatcher Filters for validate and filter requests ?

Yes Dispatch Filters would be a good option here. If your application is going to serve browser requests as well, controller would be better. For Dispatch Filters, you can do it like :
1) config\bootstrap.php
DispatcherFactory::add('ApiHeader');
2) src\Routing\Filter\ApiHeaderFilter.php
namespace App\Routing\Filter;
use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;
class ApiHeaderFilter extends DispatcherFilter
{
public function beforeDispatch(Event $event)
{
$request = $event->data['request'];
$xAppKey = $request->header('X-App-Key');
if ($xAppKey != '123456789') {
// throw exception or message
exit;
}
}
}
You can also use TableRegistery for database query :
use Cake\ORM\TableRegistry;
...
$tableModel = TableRegistry::get('table_name');
$xAppKeys = $tableModel->find('all', ...
Reference Links :
Request Headers &
Dispatch Filters

Related

Phpo create endpoint and intercept methods

Hi i'm pretty new on create endpoints in php.
Now i have to create a little endpoint to intercept some updates from electronic invoice service.
From admin panel of the service i can specify endpoint url where my application is located.
For example I indicate: www.example.com/api/endpoint/index.php
NOte: If I set only www.example.com/api/endpoint/, panel admin tell me there is an error - page not found
Now in the admin panel I see I can intercept these POST methods:
/createInvoice
/createNotification
But i don't undertand HOW to differentiate there 2 methods...
Actually in my index.php i've:
<?php
function call_create_invoice(
...
);
function call_create_notification(
...
);
header('Content-Type: application/json; charset=utf-8');
var_dump($_POST);
If I use POSTMAN to do some test using POST call, i can correctly see $_POST parameters sent.... but i don't understand how to:
call call_create_invoice function if /createInvoice is called
call call_create_notification function if /createNotification is called
If you would stick to vanilla PHP, you need to parse the request URI to call the desired function like:
$path = $SERVER['REQUEST_URI'];
switch($path) {
case('/createInvoice'):
call_create_invoice();
break;
case('/createNotification'):
call_create_notification();
break;
}
As requirements grow, it may make sense to use a minimal PHP framework with routing functionality, such as Laravel Lumen.

Check if my GET request has Header: Token

I want to send a request with or without 'Token' as a header.
If request has 'Token' as a header: if the user already has that item, it will return the item with the proper item_id of a specific user (based on its token), otherwise it will return null.
If request doesn't have 'Token' as a header: it will return the item with that item_id
I'm working with Zend Framework and in ItemResource I have this method:
public function fetch($id)
{
}
How can I check if my request has Token as a header or not and implement both cases inside fetch()?
Using Laminas API Tools it depends on wether you 're using a RPC or a REST resource. I will explain which tools the Laminas API Tools give you to evaluate the received header data.
You don 't have to reinvent the wheel, because Laminas API Tools has the received headers already at hand, when you 're in your fetch method.
Representational State Transfer (REST)
Rest resources normally extend the \Laminas\ApiTools\Rest\AbstractResourceListener class. This class listens for \Laminas\ApiTools\Rest\ResourceEvent. Fortunately, this event provides you with a request object that also contains the received header data.
<?php
declare(strict_types=1);
namespace Marcel\V1\Rest\Example;
use Laminas\ApiTools\Rest\AbstractResourceListener;
class ExampleResource extends AbstractResourceListener
{
public function fetch($id)
{
// requesting for an authorization header
$token = $this->getEvent()->getRequest()->getHeader('Authorization', null);
if ($token === null) {
// header was not received
}
}
}
As you can see the ResourceEvent returns a \Laminas\Http\Request instance when calling getRequest(). The request instance already contains all request headers you 've received. Just call getHeader with the given name and as second parameter a default value, which should be returned, when the header was not set. If there is no http_token header, you 'll get null as a result.
Remote Procedure Calls (RPC)
Since RPC requests are handled with a MVC controller class, you can get the request as easy as in a rest resource. Controller classes extend from \Laminas\Mvc\Controller\AbstractActionController, which already contains a request instance.
<?php
declare(strict_types=1);
namespace Marcel\V1\Rpc\Example;
use Laminas\Mvc\Controller\AbstractActionController;
class ExampleController extends AbstractActionController
{
public function exampleAction()
{
$token = $this->getRequest()->getHeader('Authorization', null);
if ($token === null) {
// token was not set
}
}
}
As you can see getting header data in rpc requests is as easy as in resource listeners. The procedure is the same because a request instance is also used here.
Conclusion
There is absolutely no need for coding things, that are already there. Just get the request instance from the event or the abstract controller and retrieve the header you want. Always keep in mind, that there are security aspects like CRLF injections, when dealing with raw data. The Laminas framework handles all this for you already.
Additionally you can check for all received headers by calling ->getHeaders() instead of ->getHeader($name, $default). You 'll get a \Laminas\Http\Header instance with all received headers.
You can get all HTTP header values by getallheaders() or just get the specific value by $_SERVER['HTTP_XXX'], in your case, replace XXX with Token, $_SERVER['HTTP_Token'].
Manual: https://www.php.net/manual/en/reserved.variables.server.php
public function fetch($id)
{
$token = $_SERVER['HTTP_Token'];
// do your busniess code
}

How to save a JSON-response from a REST-client in Codeception without using PhpBrower

I'm trying to test my own Rest-API using a Client-library which handles the requests for me.
I've written a helper in which the Client gets initialized and a action is added called "wantToGetUserOverClient($userId)". The client does the request and returns a JSON, that works as intended when I var_export() the JSON.
I've tried to extend the Rest module and assigning the public response field directly but get an exception:
[ModuleException] PhpBrowser: Page not loaded. Use `$I->amOnPage` (or hidden API methods `_request` and `_loadPage`) to open it
My Question:
What is the best way to save the Client response in the Codeception REST module response so I can use the already existing JSON-actions.
For example:
$I->wantToGetUserOverClient(1234);
$I->canSeeResponseIsJson();
without loading a Page withe the PhpBrower.
Thanks in advance.
My helper:
class ApiHelper extends \Codeception\Module\REST
{
private $backendApi;
public function __construct(ModuleContainer $moduleContainer, $config = null)
{
parent::__construct($moduleContainer, $config);
$this->backendApi = new BackendRestAPI();
}
public function wantToGetUserOverClient($userId)
{
$this->response = $this->backendApi->user()->one($userId);
}
}
You are misusing REST module.
But if you insist, you have to replace PhpBrowser with your module.
Configuration:
REST:
depends: YourModule
To make seeResponseIsJson work, YourModule must extend Codeception\Lib\InnerBrowser and implement _getResponseContent method.

Zend setting the authorization header for unit testing using PHPUnit

Recently I tried to test my REST API's using PHPUnit.
I am facing problem to send http authorization header for my test case.
Every time I do that I get an 403 response instead of 200
Here is my code :
<?php
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
use Zend\Http\Request;
use Zend\Http\Headers;
use Zend\Http\Response;
class TrialTest extends AbstractHttpControllerTestCase
{
protected $traceError = true;
public function setUp()
{
$this->setApplicationConfig(
include 'config/application.config.php'
);
parent::setUp();
}
public function testAction()
{
$this->request = new Request();
$this->getRequest()->setMethod('GET');
//$headers = new \Zend\Http\Headers;
//$header = $headers->addHeader($headers->fromString('Authorization:Bearer test'));
$this->getRequest()->sendHeaders('Authorization:Bearer test');
//var_dump($headers);
//$this->getRequest()->setHeaders($header);
$this->dispatch('/campaign');
$this->assertResponseStatusCode(200);
}
}
Kindly help !! where am I going wrong ?
Try setting your headers like this:
$headers = new \Zend\Http\Headers;
$headers->addHeaderLine('Authorization', 'Bearer test');
$this->request->setHeaders($headers);
And you have to make sure that test a valid OAuth token otherwise it will never work. I am not so sure if a 4 character token will ever validate correctly...
UPDATE
I think there is a general problem with your test design. You only set the request object in the controller instance, but the service taking care of authentication has no access to this request object and thus it will not authorize the request correctly.
If you write a controller test in which you test the route '/campaign' you should only test the controller functionality and set mocks for all dependencies. I think the main problem starts in your setUp method. To test this controller you should not load your whole application.config.php. You should set an MvcEvent instance and attach all you need to this event (the correct Router instance, etc) and then dispatch the controller.
Check a proper example of such a ZF2 controller test here.
Testing your OAuth module should happen in an independent test.

API response - gathering errors from included classes

I'm working on a RESTful and am stuck on message gathering for returning to the user. Basically, depending on the options selected, a few classes will be included dynamically. I'll try to provide a real-world break down. We have a HTML-email-tempalte maker - depending on the template chosen a php script will be included. This script may have warnings and I need to pass them "upstream" so that the API can report them. So we have something like this ( -> = includes )
API -> HTMLGenerator -> (dynamically) template-script.php
I need the template-script to be able to report errors to the API controller so the API can report them to the API user. Not sure the best way/practice to accomplish this.
So far , my thoughts are maybe a singleton or session variable that the template-script can add messages to, then the API Controller can report them. Any thoughts?
Main API
REST create by POST to /v1/html basically just:
class API {
require($dynamic_script);
$errors = array('warnings'=>array('warning1',waring2'));
//set http header and return JSON
}
HTMLGenerator
class HTMLGenerator {
//basically some wrappers for junior / non-programmers
function addHeading($text) {
//Add a header and do some checks.
if(strlen($text) > $warnTooLong )
HTMLErrors::addWarning("Message");
}
}
Dynamic Script
$h = new HTMLGenerator();
$h->addHeader($text);
$h->addImage($imageUrl);
You need to use a custom error handler.
See this link - http://php.net/manual/en/function.set-error-handler.php
It allows us to handle a error that might be thrown to capture it and process it. So, when you capture it, you can pass this to the parent class and furthur upstream for further processing.
Global object would work, set_error_handler too, but these are just hacks. The cleanest option is to modify your app elements to do what they are suppose to do - return those messages.
These shouldn't be too hard to do:
function myOldFunction($param1, $param2)
{
// do something
}
modify this way:
function myOldFunction($param1, $param2, array &$messages = array())
{
// do something
$messages[] = 'hey mama, i\'m on stack overflow!';
}
usage:
$messages = array();
myOldFunction(1, 2, $messages);
print_r($messages);

Categories