GuzzleHTTP Request Error for VismaSign PHP - php

This is the error I am receiving from function authMiddleware() with GuzzleHttp. I am using this for VismaSign and receiving these errors. I am not sure what exactly is the issue:
Uncaught TypeError: Argument 1 passed to
app\handlers\integrations\VismaSign::authMiddleware() must be an
instance of GuzzleHttp\Psr7\Request, instance of
GuzzleHttp\HandlerStack given.
Here is my code:
<?php
namespace app\handlers\integrations;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
class VismaSign {
const API_URL = "https://sign.visma.net/api/v1/";
const CLIENT_IDENTIFIER = "ddf58116-6082-4bfc-a775-0c0bb2f945ce";
const SECRET_KEY = "jp7SjOOr4czRTifCo30qx0sZAIw9PW+vVpsbP09pQaY=";
private $client;
private $clientHandler;
public function __construct() {
$this->client = new Client();
$this->clientHandler = $this->client->getConfig('handler');
}
public function authMiddleware(Request $request) {
$request = new Request('POST', 'https://httpbin.org/post');
//return $request->withHeader('Authorization', $this->authorizationHeader(self::CLIENT_IDENTIFIER, base64_decode(self::SECRET_KEY), $request->getMethod(), (string)$request->getBody(), ($request->getHeader('Content-Type') ?: [''])[0], $request->getHeader('Date')[0], $request->getUri()->getPath() . ($request->getUri()->getQuery() ? '?' . $request->getUri()->getQuery() : '')));
return $request->withHeader(
'Authorization',
$this->authorizationHeader(
self::CLIENT_IDENTIFIER,
base64_decode(self::SECRET_KEY),
$request->getMethod(),
(string)$request->getBody(),
($request->getHeader('Content-Type') ?: [''])[0],
$request->getHeader('Date')[0],
$request->getUri()->getPath() .
($request->getUri()->getQuery() ? '?' . $request->getUri()->getQuery() : '')
)
);
}
private function authorizationHeader($username, $secret, $method, $body, $contentType, $date, $path) {
$str = join("\n", [
$method,
base64_encode(hash('md5', $body, true)),
$contentType,
$date,
$path
]);
$header = 'Onnistuu ' . $username . ':' . base64_encode(hash_hmac('sha512', $str, $secret, true));
return $header;
}
public function createDocument() {
$response = $this->client->request('POST', self::API_URL . 'document/', [
'json' => [
'document' => [
'name' => 'Test document ' . date(\DateTime::ATOM),
]
],
'headers' => [
'Date' => date('r'),
],
'handler' => $this->authMiddleware($this->clientHandler),
]);
if ($response->getStatusCode() !== 201) {
echo 'Could not create document';
return;
}
return $response->getHeader('Location')[0];
}
}

in your constructor create add new object request of class GuzzleHttp\Psr7\Request:
public function __construct() {
$this->request = new Request();
$this->client = new Client();
$this->clientHandler = $this->client->getConfig('handler');
}
and in your createDocument method pass this only:
'handler' => $this->authMiddleware($this->request),
because your error clearly says
must be an instance of GuzzleHttp\Psr7\Request, instance of GuzzleHttp\HandlerStack

Related

request body logic with JSON raw data

I am trying to send raw JSON data via Postman as required parameter.
My function works fine except that part. When I paste json data into postman it throws:
A non-empty request body is required.
I think there is a little modification needed but I can't find solution.
My code:
public function callApi(BaseRequest $request)
{
$token = $this->getTokenForRequest($request);
$method = $request->getMethod();
$client = new Client(['base_uri' => 'https://api-tst.testing.app/test/']);
$headers = [
'Authorization' => 'Bearer ' . $token,
'Ocp-Apim-Subscription-Key' => '1111112222',
'Content-Type' => 'application/json',
];
$request->getRequestParams();
$res = $client->request($method, $request->getUrl(), [
'headers' => $headers
]);
$res->getStatusCode();
$response = $res->getBody()->getContents();
return $response;
}
and form my BaseRequest:
public function getUrl()
{
return null;
}
public function getMethod()
{
return null;
}
/**
* #return array
*/
public function getRequestParams()
{
$data = [];
return $data;
}
And I am calling it in controller like:
public function testAll(Request $request)
{
$data = (string)$request->getContent();
$data = json_decode($data, true);
$response = $this->container->get('app')->myFunction($data);
dump($response);die;
}
Found it!
$res = $client->request($method, $request->getUrl(), [
'headers' => $headers,
'json' => $request->getRequestParams()
]);

Using OAUTH API with CURL on a PHP page

I'm working with a PHP website where I want to integrate to some 3rd party API.
In particular I am looking at using CURL to interact with their server but this is something I am far from an expert in so hoping the community can help me gain a better understanding of what I am doing.
I am unclear what options such as -X and -d do, also I am unclear how I script this command on my PHP page? (Unfortunately it's tricky searching google for "-d" as this isn't considered part of the search string)
My particular example I am stuck on is requesting an access token, the API documentation provided to me is;
curl -X POST \
-d \ "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&scope=REQUESTED_SCOPES" \
'https://api.example.com/token'
grant_type- client_credentials
client_id- Generated during setup
client_secret - Web apps only, generated during setup
scope Optional - List of comma separated values, see supported scopes
If the request is successful, an access token will be returned in the
response:
{
"access_token":"ACCESS_TOKEN",
"token_type":"Bearer",
"expires_in":3600
"scope":"REQUEST_SCOPES"
}
That is the above guidance, I have completed the pre-requisites so can confirm the client id, secret and required scope are correct.
I have tried both of the following in vein in my PHP script
$tk = curl_init();
curl_setopt($tk, CURLOPT_URL, "https://api.example.com/token");
curl_setopt($tk, CURLOPT_POST, 1);
curl_setopt($tk, CURL_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($tk, CURLOPT_POSTFIELDS, array( 'grant_type=client_credentials&client_id=myownid&client_secret=xyz123&scope=instrument'));
// grab URL and pass it to the browser
$result=curl_exec($tk);
// close cURL resource, and free up system resources
curl_close($tk);
And
$tk = curl_init();
curl_setopt($tk, CURLOPT_URL, "https://api.example.com/token?grant_type=client_credentials&client_id=myownid&client_secret=xyz123&scope=instrument");
curl_setopt($tk, CURLOPT_POST, 1);
curl_setopt($tk, CURL_HTTPHEADER, array('Content-Type: application/json'));
// grab URL and pass it to the browser
$result=curl_exec($tk);
// close cURL resource, and free up system resources
curl_close($tk);
Both of these examples produce the following error;
{"error_description":"grant_type parameter is
missing","error":"invalid_request"}
Any help on this particular issue or even to just understand how I am going wrong to give me some ideas of the correct syntax will be much appreciated!
Thank you all in advanced for your time.
(Please note I've changed to "example.com" for security of the 3rd
party)
Check out below sample code of cURL call in php. You need to change your domain name instead of example.com also put values for POSTFIELDS.
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://api.example.com/oauth/token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "{\"grant_type\":\"client_credentials\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"scope\": \"instrument\"}",
CURLOPT_HTTPHEADER => array(
"content-type: application/json"
),
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
If you'd like to do it OO instead of using cURL, you might like this better. First up require in Guzzle:
composer require guzzlehttp/guzzle
Create an ApiCredentials Object:
<?php
namespace Some\Company;
class ApiCredentials
{
private $clientKey;
private $clientSecret;
private $proxy;
private $baseUrl;
public function __construct(string $clientKey, string $clientSecret, string $proxy = '', string $baseUrl = 'https://api.somewhere.com')
{
$this->clientKey = $clientKey;
$this->clientSecret = $clientSecret;
$this->proxy = $proxy;
$this->baseUrl = $baseUrl;
}
public function getClientKey(): string
{
return $this->clientKey;
}
public function getClientSecret(): string
{
return $this->clientSecret;
}
public function getProxy(): string
{
return $this->proxy;
}
public function getBaseUrl(): string
{
return $this->baseUrl;
}
}
Now create an ApiService class:
<?php
namespace Some\Company;
use DateTime;
use GuzzleHttp\Client;
class ApiService
{
const API_TOKEN_ENDPOINT = '/token';
private $baseUrl;
private $client;
private $accessToken;
private $credentials;
public function __construct(ApiCredentials $credentials)
{
$this->baseUrl = $credentials->getBaseUrl();
$options = $this->initOptions($credentials);
$this->client = new Client($options);
$this->credentials = $credentials;
}
private function initOptions(ApiCredentials $credentials) : array
{
$options = [
'base_uri' => $this->baseUrl,
'verify' => false,
];
if ($credentials->getProxy() !== '') {
$options = \array_merge($options, ['proxy' => [
'https' => $credentials->getProxy(),
]]);
}
return $options;
}
private function hasAccessToken() : bool
{
return $this->accessToken instanceof AccessToken && $this->accessToken->getExpires() > new DateTime();
}
private function getAccessToken() : AccessToken
{
return $this->accessToken;
}
private function getCredentials(): ApiCredentials
{
return $this->credentials;
}
private function refreshAccessToken()
{
$client = $this->getClient();
$response = $client->post(
$this->baseUrl . self::API_TOKEN_ENDPOINT, [
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
],
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => $this->getCredentials()->getClientKey(),
'client_secret' => $this->getCredentials()->getClientSecret(),
'scope' => 'put your scopes in here',
],
]);
$json = $response->getBody()->getContents();
$this->accessToken = new AccessToken($json);
}
private function getClient() : Client
{
return $this->client;
}
private function validateToken()
{
if (!$this->hasAccessToken()) {
$this->refreshAccessToken();
}
}
public function getSomeEndpointData(string $someParam = 'whatever') : string
{
$this->validateToken();
$response = $this->getClient()->get(
$this->baseUrl . '/get/some/data/' . $someParam, [
'headers' => [
'Authorization' => 'Bearer ' . $this->getAccessToken()->getToken(),
],
'query' => [
'additional' => 'this creates stuff like ?additional=whatever',
],
]);
$json = $response->getBody()->getContents();
return $json;
}
}
And an access token class:
<?php
namespace Some\Company;
use DateTime;
class AccessToken
{
private $token;
private $scope;
private $type;
private $expires;
public function __construct(string $tokenJson)
{
$token = \json_decode($tokenJson, true);
$keys = [
'access_token', 'scope', 'token_type', 'expires_in',
];
$this->token = $token['access_token'];
$this->scope = $token['scope'];
$this->type = $token['token_type'];
$date = new DateTime('+' .$token['expires_in'] . ' seconds');
$this->expires = $date;
}
public function getToken(): string
{
return $this->token;
}
public function getScope(): string
{
return $this->scope;
}
public function getType(): string
{
return $this->type;
}
public function getExpires(): DateTime
{
return $this->expires;
}
}
Now, to use this stuff:
<?php
use Some\Company\ApiCredentials;
use Some\Company\ApiService;
$clientKey = 'client key key here';
$clientSecret = 'client secret here';
$proxy = 'tcp://199.199.132.132:80'; // optional
$creds = new ApiCredentials($clientKey, $clientSecret, $proxy);
$apiService = new ApiService($creds);
$results = $apiService->getSomeEndpointData('whatever'); // returns json
It will handle refreshing access tokens etc too.

Laravel 5.6, where to keep additional classes (additional functional)?

I need to make request on some CRM api in my controller. For making this I have pretty big method. It's look like ugly. I know that there are some "Services" and to put additional code into Service is a good way. But I don't know what is this. Is it a custom classes into app folder? Or maybe it's Service-providers? I have read service-providers documentation and I'm not sure that service-providers is suitable for this. Here is my code:
<?php
namespace App\Http\Controllers;
use App\User;
use App\UserInfo;
use Validator;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$users = User::with('info')
->paginate(20);
$users->withPath(DIRECTORY_SEPARATOR . $request->path() .DIRECTORY_SEPARATOR);
return response()->json($users)->setEncodingOptions(JSON_UNESCAPED_UNICODE);
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$data = $request->json()->all();
$rules = [
'name' => 'required',
'phone' => 'required|unique:users'
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) return response()->json(['errors'=>$validator->errors()]);
$user = new User();
$user->name = request('name');
$user->phone = request('phone');
$user_info_obj = $this->storeUserInfo();
if($user_info_obj === null){
return response('Impassible to define user geo data', 400);
}
$user->info_id = $user_info_obj->id;
$user->save();
$this->makeAMOLead($user->name,
$user->phone,
$user_info_obj->user_agent,
$user_info_obj->city,
$user_info_obj->country);
return response()->json(['success' => 'User created successfully']);
}
public function storeUserInfo()
{
$ip = request()->ip();
$reader = new \GeoIp2\Database\Reader('../resources/geo-lite2-city_20180807/GeoLite2-City.mmdb');
try {
$record = $reader->city($ip);
}
catch (\Throwable $e){
// Code bellow is for testing on localhost, Because of maybe exception instead of geo obj on localhost.
$info = new UserInfo();
$info->ip = '127.0.0.1';
$info->city = 'Some city';
$info->country = 'Some country';
$info->country_code = 'Some code';
$info->continent = 'Some continent';
$info->continent_code = 'no';
$info->user_agent = 'User agent';
$info->save();
return $info;
//return null;
}
$city = $record->city->names['ru'];
$continent = $record->continent->names['ru'];
$continent_code = $record->continent->code;
$country = $record->country->names['ru'];
$country_code = $record->country->isoCode;
$user_agent = \request()->userAgent();
$info = new UserInfo();
$info->ip = $ip;
$info->city = $city;
$info->country = $country;
$info->country_code = $country_code;
$info->continent = $continent;
$info->continent_code = $continent_code;
$info->user_agent = $user_agent;
$info->save();
return $info;
}
private function makeAMOLead($name, $phone, $userAgent, $city, $country)
{
$domain = env('AMO_DOMAIN');
$login = env('AMO_LOGIN');
$hash = env('AMO_HASH');
try {
$credentials = new \ddlzz\AmoAPI\CredentialsManager($domain, $login, $hash);
$settings = new \ddlzz\AmoAPI\SettingsStorage();
$settings->setCookiePath(env('AMO_COOKIE_FILE_PATH'));
$request = \ddlzz\AmoAPI\ClientFactory::create($credentials, $settings);
$lead = new \ddlzz\AmoAPI\Model\Amo\Lead();
$lead['name'] = $name;
if(env('AMO_PIPELINE_ID', null)){
$lead['pipeline_id'] = intval(env('AMO_PIPELINE_ID'));
}
$lead['name'] = 'New pickup user ' . $name;
$lead['custom_fields'] = [
[
'id' => env('AMO_NAME_FIELD_ID'),
'values' => [
['value' => $name],
]
],
[
'id' => env('AMO_USER_AGENT_FIELD_ID'),
'values' => [
['value' => $userAgent]
]
],
[
'id' => env('AMO_CITY_FIELD_ID'),
'values' => [
['value' => $city]
]
],
[
'id' => env('AMO_COUNTRY_FIELD_ID'),
'values' => [
['value' => $country]
]
],
];
$lead['created_at'] = time();
$result = $request->add($lead);
$pipelineId = json_decode($result)->_embedded->items{0}->id;
// create contact
$contact = new \ddlzz\AmoAPI\Model\Amo\Contact();
$contact['name'] = $name;
$contact['created_at'] = time();
$contact['leads_id'] = "$pipelineId";
// dd($request->accountInfo(), true); // Call this, if you need to know ids of default fields (like phone, or position)
$contact['custom_fields'] = [
[
'id' => env('AMO_CONTACT_PHONE_ID'),
'values' => [
[
'value' => $phone,
'enum' => 'MOB',
],
]
],
];
$result = $request->add($contact);
} catch (Exception $e) {
echo response()->json(['error' => $e->getFile() . ': ' . $e->getMessage()]);
}
}
}
Look on the makeAMOLead. This is big method in my controller and this is not ok for controller conception.
Please use repository pattern to split all the communication between the application and your data source. and call the repository functions inside your controller. It is good practice. Here is an article you can understand about that
Example:
Your functions can be separate from controller to repository.
storeUserInfo
makeAMOLeadin
Move your functions an repository and call them into your controller.

Laravel testing with JWT-Auth

I'm trying to test my api that's made with JWT_auth: https://github.com/tymondesigns/jwt-auth
class UpdateTest extends TestCase
{
use DatabaseTransactions;
public $token;
public function signIn($data = ['email'=>'mail#gmail.com', 'password'=>'secret'])
{
$this->post('api/login', $data);
$content = json_decode($this->response->getContent());
$this->assertObjectHasAttribute('token', $content);
$this->token = $content->token;
return $this;
}
/** #test */
public function a_user_updates_his_account()
{
factory(User::class)->create([
'name' => 'Test',
'last_name' => 'Test',
'email' => 'mail#gmail.com',
'mobile' => '062348383',
'function' => 'ceo',
'about' => 'About me.....',
'corporation_id' => 1
]);
$user = User::first();
$user->active = 2;
$user->save();
$this->signIn();
$url = '/api/user/' . $user->slug . '?token=' . $this->token;
$result = $this->json('GET', $url);
dd($result);
}
}
Result is always:
The token could not be parsed from the request
How do I get this t work!?
Source (https://github.com/tymondesigns/jwt-auth/issues/206)
One way to test your API in this situation is to bypass the actual token verification, but still log your user in (if you need to identify the user). Here is a snippet of the helper method we used in our recent API-based application.
/**
* Simulate call api
*
* #param string $endpoint
* #param array $params
* #param string $asUser
*
* #return mixed
*/
protected function callApi($endpoint, $params = [], $asUser = 'user#example.org')
{
$endpoint = starts_with($endpoint, '/')
? $endpoint
: '/' . $endpoint;
$headers = [];
if (!is_null($asUser)) {
$token = auth()->guard('api')
->login(\Models\User::whereEmail($asUser)->first());
$headers['Authorization'] = 'Bearer ' . $token;
}
return $this->json(
'POST',
'http://api.dev/' . $endpoint,
$params,
$headers
);
}
And is used like this:
$this->callApi('orders/list', [
'type' => 'customers'
])
->seeStatusOk()
Basically, there is not really a way for now. The fake request that is created during testing and is passed to Laravel to handle, somehow drops the token data.
It has alredy been reported in an issue (https://github.com/tymondesigns/jwt-auth/issues/852) but as far as I know, there is no solution yet.

Repeat function in Laravel / PHP Class in one shared function

With the following code, how do I place the on_stats function in one area? So it can be shared and called in all functions on this page instead of copying and pasting the same code over and over again. Also how do I declare $logger just once instead of over and over again.
class API extends Controller
{
public function __construct() {
}
public function api_call1() {
$client = new GuzzleHttp\Client(['defaults' => ['verify' => false]]);
$logger = new Logger('View Logs');
$logger->pushHandler(new StreamHandler(storage_path() . '/logs/api_log.log', Logger::INFO));
$res = $client->get($this->url . "/my/url/", [
'on_stats' => function (TransferStats $stats) use ($logger) {
$logger->info('Request' . $stats->getRequest()->getMethod() .
'Response' . $stats->getResponse()->getStatusCode() .
'Tx Time' . $stats->getTransferTime()
);
}
]);
$response = Response::make($res->getBody(), 200);
$response->header('Content-Type', 'application/json');
return $response;
}
public function api_call2() {
$client = new GuzzleHttp\Client(['defaults' => ['verify' => false]]);
$logger = new Logger('View Logs');
$logger->pushHandler(new StreamHandler(storage_path() . '/logs/api_log.log', Logger::INFO));
$res = $client->get($this->url . "/my/url2/", [
'on_stats' => function (TransferStats $stats) use ($logger) {
$logger->info('Request' . $stats->getRequest()->getMethod() .
'Response' . $stats->getResponse()->getStatusCode() .
'Tx Time' . $stats->getTransferTime()
);
}
]);
$response = Response::make($res->getBody(), 200);
$response->header('Content-Type', 'application/json');
return $response;
}
}
From my viewpoint, it would be a great idea to:
Declare a class property as $logger
Move logger initialization to the constructor
Rewrite the "on_stats" method as a class method, say logEvent(TransferStats $stats) using $this->logger, this way you can avoid using "use" keyword
Pass the client event "on_stats" as a standard PHP callable array:
['on_stats' => [$this, 'logEvent']]

Categories