Receiving exception when attempting to integrate an API using PHP - php

Currently, I am working on integrating a chat API (it is called pusher Chatkit) to an app I am working on. For the backend, PHP and laravel are being used, and postman is being used to test the server. When testing on Postman I am receiving a 500 internal server error with this exception:
{
"message": "You must provide an instance_locator",
"exception": "Chatkit\\Exceptions\\MissingArgumentException",
"file": "/Users/shaquilenoor/Desktop/chatapi/vendor/pusher/pusher-chatkit-server/src/Chatkit.php",
"line": 49,
"trace": [
in trace, many files/lines are referred to, so I left out as it is too much to fit in this post (you can ask if needed though and I can make a Gdrive link with it in)
The code leading up to the line + function being referred to is this:
<?php
namespace Chatkit;
use Chatkit\Exceptions\ChatkitException;
use Chatkit\Exceptions\ConfigurationException;
use Chatkit\Exceptions\ConnectionException;
use Chatkit\Exceptions\MissingArgumentException;
use Chatkit\Exceptions\TypeMismatchException;
use Firebase\JWT\JWT;
class Chatkit
{
protected $settings = array(
'scheme' => 'https',
'port' => 80,
'timeout' => 30,
'debug' => false,
'curl_options' => array(),
);
protected $logger = null;
protected $ch = null; // Curl handler
protected $api_settings = array();
protected $authorizer_settings = array();
protected $cursor_settings = array();
const GLOBAL_SCOPE = 'global';
const ROOM_SCOPE = 'room';
/**
*
* Initializes a new Chatkit instance.
*
*
* #param array $options Options to configure the Chatkit instance.
* instance_locator - your Chatkit instance locator
* key - your Chatkit instance's key
* scheme - e.g. http or https
* host - the host; no trailing forward slash.
* port - the http port
* timeout - the http timeout
*/
public function __construct($options)
{
$this->checkCompatibility();
if (!isset($options['instance_locator'])) {
throw new MissingArgumentException('You must provide an instance_locator');
}
if (!isset($options['key'])) {
throw new MissingArgumentException('You must provide a key');
}
$this->settings['instance_locator'] = $options['instance_locator'];
$this->settings['key'] = $options['key'];
$this->api_settings['service_name'] = 'chatkit';
$this->api_settings['service_version'] = 'v2';
$this->authorizer_settings['service_name'] = 'chatkit_authorizer';
$this->authorizer_settings['service_version'] = 'v2';
$this->cursor_settings['service_name'] = 'chatkit_cursors';
$this->cursor_settings['service_version'] = 'v2';
foreach ($options as $key => $value) {
// only set if valid setting/option
if (isset($this->settings[$key])) {
$this->settings[$key] = $value;
}
}
}
The instance_locator refers to a code generated on Pusher Chatkit, which I link in my files for integration of that API. This is my first time integrating a backend server using PHP so a little lost! Would appreciate some advice on where I should be looking to solve the issue and also more than happy to provide further info. Cheers

Depending on how you create API request you should do something like this:
$chatkit = new Chatkit(['instance_locator' => *locator*, 'key' => *actualKey*]);
The error you are getting means that you haven't passed variables in array.

Related

Get url path from guzzleHttp requestAsync after setup

I've a problem using guzzleHttp with multiple requests. I want to get the path of an url after setup the request but before sending it. Here's my code:
include "../../vendor/autoload.php";
use GuzzleHttp\Client;
/* Initiate Guzzle Client */
$client = new Client([
"verify" => false, // disable ssl certificate verification
"timeout" => 30, // maximum timeout for requests
"http_errors" => false, // disable exceptions
]);
$requests = [];
$requests["a"] = $client->requestAsync('GET', "https://www.aaa.de/aaa.html");
$requests["b"] = $client->requestAsync('GET', "https://www.bbb.de/bbb.html");
$requests["c"] = $client->requestAsync('GET', "https://www.ccc.de/ccc.html");
$content = performMultiRequest($requests);
function performMultiRequest($requests)
{
foreach ($requests as $key => $object) {
print_r($object);
exit;
}
/**
* here comes more to send the requests, but that doesn't care for this problem
*/
}
In this case I get a GuzzleHttp\Promise\Promise Object. My goal is to get only the path /aaa.html from $object. It must be found inside the function performMultiRequest(). There's no chance to read and parse the url before, e.g. when the requestAsync() is used.
This is the relevant part of $object I need:
I tried SO, Guzzle documentation, google, trail & error... nothing found... any ideas?

Issue with DocuSign sending Envelopes

I recently changed my DocuSign integration to use the JWT OAuth flow. To achieve this I have a few classes.
OAuth Client
<?php
namespace App\DocuSign;
use DocuSign\eSign\Client\ApiClient;
use DocuSign\eSign\Client\Auth\OAuth;
use DocuSign\eSign\Configuration;
use Exception;
use Illuminate\Support\Facades\Log;
/**
* Helper class to generate a DocuSign Client instance using JWT OAuth2.
*
* #see
*
*/
class OAuthClient
{
/**
* Create a new DocuSign API Client instance using JWT based OAuth2.
*/
public static function createApiClient()
{
$config = (new Configuration())->setHost(config('docusign.host'));
$oAuth = (new OAuth())->setOAuthBasePath(config('docusign.oauth_base_path'));
$apiClient = new ApiClient($config, $oAuth);
try {
$response = $apiClient->requestJWTUserToken(
config('docusign.integrator_key'),
config('docusign.user_id'),
config('docusign.private_key'),
'signature impersonation',
60
);
if ($response) {
$accessToken = $response[0]['access_token'];
$config->addDefaultHeader('Authorization', 'Bearer ' . $accessToken);
$apiClient = new ApiClient($config);
return $apiClient;
}
} catch (Exception $e) {
// If consent is required we just need to give the consent URL.
if (strpos($e->getMessage(), 'consent_required') !== false) {
$authorizationUrl = config('docusign.oauth_base_path') . '/oauth/auth?' . http_build_query([
'scope' => 'signature impersonation',
'redirect_uri' => config('docusign.redirect_url'),
'client_id' => config('docusign.integrator_key'),
'response_type' => 'code'
]);
Log::critical('Consent not given for DocuSign API', [
'authorization_url' => $authorizationUrl
]);
abort(500, 'Consent has not been given to use the DocuSign API');
}
throw $e;
}
}
}
Signature Client Service
<?php
namespace App\DocuSign;
use DocuSign\eSign\Api\EnvelopesApi;
use DocuSign\eSign\Client\ApiClient;
class SignatureClientService
{
/**
* DocuSign API Client
*/
public ApiClient $apiClient;
/**
* Create a new instance of our class.
*/
public function __construct()
{
$this->apiClient = OAuthClient::createApiClient();
}
/**
* Getter for the EnvelopesApi
*/
public function getEnvelopeApi(): EnvelopesApi
{
return new EnvelopesApi($this->apiClient);
}
}
Then, in my constructors where I want to use it I'm doing
/**
* Create a new controller instance
*/
public function __construct()
{
$this->clientService = new SignatureClientService();
$this->envelopesApi = $this->clientService->getEnvelopeApi();
}
Finally, I use it like so
$envelopeSummary = $this->envelopesApi->createEnvelope(config('docusign.api_account_id'), $envelopeDefinition);
But I get an error that reads
DocuSign\eSign\Client\ApiException: Error while requesting server,
received a non successful HTTP code [400] with response Body:
O:8:"stdClass":2:{s:9:"errorCode";s:21:"USER_LACKS_MEMBERSHIP";s:7:"message";s:60:"The
UserID does not have a valid membership in this Account.";} in
/homepages/45/d641872465/htdocs/sites/ita-portal/vendor/docusign/esign-client/src/Client/ApiClient.php:344
I researched this and this would imply that the user is not within the account, but they are. I also checked that this account owns the envelopes that I'm trying to send.
For reference I took inspiration for envelope sending from here: https://developers.docusign.com/docs/esign-rest-api/how-to/request-signature-template-remote/
What I think is happening is that the request is going to the wrong server or the wrong account.
I'd suggest using a packet analyser like Fiddler or Wireshark to log where your requests are headed (or just log the request within your application)
The auth URLs seem to be correct since you're not getting a 401 unauthorised error but the envelopes and other queries' must match the base URL located in your account under the Apps and Keys page. It would be of the form demo.docusign.net for our demo environment or xxx.docusign.net for our production environment

GuzzleHttp and Memcached key

I'm playing around with the GuzzleHttp client, GuzzleCacheMiddleware and Memcached.
The setup is calling the same url with different parameters.
This results in one! memcached hit, so I think the memcached key is created from the url and only the url.
Can I somehow change this behaviour, so the key includes a md5 of the parameters?
You would have to create your own CacheStrategy class. For example you can extend PrivateCacheStrategy class and override getCacheKey method which is responsible for creating the cache key.
https://github.com/Kevinrob/guzzle-cache-middleware/blob/master/src/Strategy/PrivateCacheStrategy.php#L123
You are right that it creates storage key based on only the URL and request method.
Decided to look into it. You are right that it needs GreedyCacheStrategy because it literally caches everything regardless of any RFC standards.
Custom class for cache key creating.
class ParamsGreedyCacheStrategy extends GreedyCacheStrategy
{
/**
* Ignoring any headers, just straight up cache key based on method, URI, request body/params
*
* #param RequestInterface $request
* #param KeyValueHttpHeader|null $varyHeaders
* #return string
*/
protected function getCacheKey(RequestInterface $request, KeyValueHttpHeader $varyHeaders = null)
{
return hash(
'sha256',
'greedy' . $request->getMethod() . $request->getUri() . $request->getBody()
);
}
}
Creating requests. I used Laravel caching here, you can use memcached. I also allow POST HTTP method to be cached, because by default only GET is being cached!
$handlerStack = HandlerStack::create();
$cacheMiddleware = new CacheMiddleware(
new ParamsGreedyCacheStrategy(
new LaravelCacheStorage(
Cache::store('file')
),
10
)
);
// Not documented, but if you look at the source code they have methods for setting allowed HTTP methods. By default, only GET is allowed (per standards).
$cacheMiddleware->setHttpMethods(['GET' => true, 'POST' => true]);
$handlerStack->push(
$cacheMiddleware,
'cache'
);
$client = new Client([
'base_uri' => 'https://example.org',
'http_errors' => false,
'handler' => $handlerStack
]);
for($i = 0; $i < 4; $i++) {
$response = $client->post('/test', [
'form_params' => ['val' => $i]
]);
// Middleware attaches 'X-Kevinrob-Cache' header that let's us know if we hit the cache or not!
dump($response->getHeader('X-Kevinrob-Cache'));
}

Amazon ElasticSearch service Signature mismatch for PUT Request - Amazon SDK php V2

I am using Amazon ElasticSearch Service and when i tried to create SignatureV4 Request it is working fine for search operations (GET Requests). But when i tried to do some operations like create indices (Using PUT request), it will trough the Signature mismatch error.
I am using Amazon SDK version 2 SignatureV4 library for signing the requests. Also created a custom Elasticsearch handler to add tokens to the request.
Does anybody have such issue with SignatureV4 library in Amazon SDK php V2.
{"message":"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.\n\nThe Canonical String for this request should have been\n'PUT\n/test_index_2\n\nhost:search-test-gps2gj4zx654muo6a5m3vxm3cy.eu-west-1.es.amazonaws.com\nx-amz-date:XXXXXXXXXXXX\n\nhost;x-amz-date\n271d5ef919251148dc0b5b3f3968c3debc911a41b60ef4e92c55b98057d6cdd4'\n\nThe String-to-Sign should have been\n'AWS4-HMAC-SHA256\XXXXXXXXXXXX\n20170511/eu-west-1/es/aws4_request\n0bd34812e0727fba7c54068b0ae1114db235cfc2f97059b88be43e8b264e1d57'\n"}
This tweak only necessary for the users who are still using Amazon SDK PHP version 2. In version 3, it supported by default.
For signed request i updated the current elsticsearch client handler by adding a middle ware for signing the request.
$elasticConfig = Configure::read('ElasticSearch');
$middleware = new AwsSignatureMiddleware();
$defaultHandler = \Elasticsearch\ClientBuilder::defaultHandler();
$awsHandler = $middleware($defaultHandler);
$clientBuilder = \Elasticsearch\ClientBuilder::create();
$clientBuilder->setHandler($awsHandler)
->setHosts([$elasticConfig['host'].':'.$elasticConfig['port']]);
$client = $clientBuilder->build();
I used the following library for this purpose
use Aws\Common\Credentials\CredentialsInterface;
use Aws\Common\Signature\SignatureInterface;
use Guzzle\Http\Message\Request;
class AwsSignatureMiddleware
{
/**
* #var \Aws\Credentials\CredentialsInterface
*/
protected $credentials;
/**
* #var \Aws\Signature\SignatureInterface
*/
protected $signature;
/**
* #param CredentialsInterface $credentials
* #param SignatureInterface $signature
*/
public function __construct()
{
$amazonConf = Configure::read('AmazonSDK');
$this->credentials = new \Aws\Common\Credentials\Credentials($amazonConf['key'], $amazonConf['secret']);
$this->signature = new \Aws\Common\Signature\SignatureV4('es', 'eu-west-1');
}
/**
* #param $handler
* #return callable
*/
public function __invoke($handler)
{
return function ($request) use ($handler) {
$headers = $request['headers'];
if ($headers['host']) {
if (is_array($headers['host'])) {
$headers['host'] = array_map([$this, 'removePort'], $headers['host']);
} else {
$headers['host'] = $this->removePort($headers['host']);
}
}
if (!empty($request['body'])) {
$headers['x-amz-content-sha256'] = hash('sha256', $request['body']);
}
$psrRequest = new Request($request['http_method'], $request['uri'], $headers);
$this->signature->signRequest($psrRequest, $this->credentials);
$headerObj = $psrRequest->getHeaders();
$allHeaders = $headerObj->getAll();
$signedHeaders = array();
foreach ($allHeaders as $header => $allHeader) {
$signedHeaders[$header] = $allHeader->toArray();
}
$request['headers'] = array_merge($signedHeaders, $request['headers']);
return $handler($request);
};
}
protected function removePort($host)
{
return parse_url($host)['host'];
}
}
The exact line i tweaked for this purpose is
if (!empty($request['body'])) {
$headers['x-amz-content-sha256'] = hash('sha256', $request['body']);
}
For PUT and POST request the payload hash was wrong because i was not considering the request body while generating payload.
Hope this code is beneficial for anyone who is using Amazon SDK PHP version 2 and using the IAM based authentication for Elasticsearch Hosted service in Amazon cloud.

Fatal error: Uncaught exception 'CFCredentials_Exception' with message 'No credentials were provided for Amazon S3

This is killing me.I have done everything but not right as its not still giving me this error
Fatal error: Uncaught exception 'CFCredentials_Exception' with message 'No credentials were provided. I am trying to upload files to S3 using AWS sdk 1.6 example and jquery file upload plugin.I found an example on the wiki.
This is my file where I set the credentials awssdk.php from the wiki example
require_once 'sdk.class.php';
require_once 'utilities.class.php';
require_once 'credential.class.php';
if (!class_exists('CFCredentials'))require_once('credentials.class.php');
$name=null;
CFCredentials::set(array(
$name => array(
'key' => 'access key',
'secret' => 'secret key',
'certificate_authority' => false
),
'#default' => $name
));
if (!class_exists('S3'))require_once('S3.php');
$s3 = new AmazonS3();
I am quite sure I should't need so many files but as the errors suggested I had to add the dependency classes.But still I get the above error. I also tried to include the config.class.php file for credentials and still got this error.I have been spending a lot of time on this and now kind of frustrated with this AWS sdk.
I am a bit new to OO PHP and probably hence finding it difficult.Experts please suggest some solution to where I am wrong.
EDIT: I believe this class causes the issue,not sure how!!
class CFCredentials
{
/**
* The key used to specify the default credential set
*/
const DEFAULT_KEY = 'my key';
/**
* The key used to identify inherited credentials
*/
const INHERIT_KEY = 'my secret key';
/**
* Stores the credentials
*/
protected static $credentials = array();
/**
* Prevents this class from being constructed
*/
final private function __construct() {}
/**
* Stores the credentials for re-use.
*
* #param array $credential_sets (Required) The named credential sets that should be made available to the application.
* #return void
*/
// private $credential_sets = array('key' => 'xxxxxxxxxxxxxxxxxxxx','secret' => 'xxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx','certificate_authority' => false);
public static function set(array $credential_sets)
{
// Make sure a default credential set is specified or can be inferred
if (count($credential_sets) === 1)
{echo "in count if-->".self::DEFAULT_KEY;
$credential_sets[self::DEFAULT_KEY] = reset($credential_sets);
}
// Resolve any #inherit tags
foreach ($credential_sets as $credential_name => &$credential_set)
{
if (is_array($credential_set))
{
foreach ($credential_set as $credential_key => &$credential_value)
{
if ($credential_key === self::INHERIT_KEY)
{
if (!isset($credential_sets[$credential_value]))
{
throw new CFCredentials_Exception('The credential set, "' . $credential_value . '", does not exist and cannot be inherited.');
}
$credential_set = array_merge($credential_sets[$credential_value], $credential_set);
unset($credential_set[self::INHERIT_KEY]);
}
}
}
}
// Normalize the value of the #default credential set
if (isset($credential_sets[self::DEFAULT_KEY]))
{
$default = $credential_sets[self::DEFAULT_KEY];
if (is_string($default))
{
if (!isset($credential_sets[$default]))
{
throw new CFCredentials_Exception('The credential set, "' . $default . '", does not exist and cannot be used as the default credential set.');
}
$credential_sets[self::DEFAULT_KEY] = $credential_sets[$default];
}
}
// Store the credentials
self::$credentials = $credential_sets;
}
/**
* Retrieves the requested credentials from the internal credential store.
*
* #param string $credential_set (Optional) The name of the credential set to retrieve. The default value is set in DEFAULT_KEY.
* #return stdClass A stdClass object where the properties represent the keys that were provided.
*/
public static function get($credential_name = self::DEFAULT_KEY)
{
//echo $credential_name; exit;
// Make sure the credential set exists
if (!isset(self::$credentials[$credential_name]))
{
throw new CFCredentials_Exception('The credential set, "' . $credential_name . '", does not exist and cannot be retrieved.');
}
// Return the credential set as an object
return new CFCredential(self::$credentials[$credential_name]);
}
/**
* Retrieves a list of all available credential set names.
*
* #return CFArray A list of all available credential set names.
*/
public static function list_sets()
{
return new CFArray(array_keys(self::$credentials));
}
}
class CFCredentials_Exception extends Exception {}
Thank you
It seems like whatever tutorial you are following is making this unnecessarily difficult. Try this:
require_once 'sdk.class.php';
$s3 = new AmazonS3(array(
'key' => 'your_aws_access_key_id',
'secret' => 'your_aws_secret_key',
));
You shouldn't need to require any other files.
You have set all Credential well but still you have leave one credential 'default_cache_config' => ''
Just Put it in Credential array with some location for caching the default credential.
just have a look
$name=null;
CFCredentials::set(array(
$name => array(
'key' => 'access key',
'secret' => 'secret key',
'default_cache_config' => '/home/user/any location',
'certificate_authority' => false
),
'#default' => $name
));**

Categories