-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow self-signed certificate in chain for devel test app #39
Comments
Looks like uploading document does not work at all. Following is standalone code that fails uploading PDF as descripbed in API documentation: // verify => false ... bypass error in devel "self signed certificate in certificate chain"
$client = new \GuzzleHttp\Client(['verify' => false]);
$response = $client->request('POST', 'https://api-eval.signnow.com/document', [
'multipart' => [
[
'name' => 'file',
'contents' => file_get_contents($pathToPdfFile),
]
],
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$bearerAccessToken,
],
]);
$resp = $response->getBody(); // false
I tried few other clients - cURL, multipart file_get_contents - same error. Seems related - signnow/SignNowNodeSDK#1 EDIT: // verify => false ... bypass error in devel "self signed certificate in certificate chain"
$client = new \GuzzleHttp\Client(['verify' => false]);
$response = $client->request('POST', 'https://api-eval.signnow.com/document', [
'multipart' => [
[
'name' => 'file',
'filename' => basename($path), // <-- (!) improtant, tell file extension and stored filename
'contents' => file_get_contents($pathToPdfFile),
]
],
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$bearerAccessToken,
],
]);
$resp = $response->getBody(); // OK |
Here is working PHP wrapper draft around Signnow API - gets bearer token & uploads file. Usage: // init class
$signnow = new Signnow("https://api-eval.signnow.com", "your@email", "login.PWD!", "abc123-YOUR-API-TOKEN***");
// get bearer token
$accessToken = $signnow->getAccessToken();
// upload file
$path = '/var/test/sample-contract.pdf';
$resp = $signnow->uploadFile($path); // e.g. {'id' => 'abc123*****'} PHP wrapper: /**
* Standalone PHP wrapper around Signnow API - basic working draft, add more methods as needed ..
* (no dependencies, no guzzle, ..)
*/
class Signnow
{
protected
/** @var string API URL */
$urlEndpoint = 'https://api-eval.signnow.com',
/** @var string (Static) auth token under user's account needed to obtain access/bearer token */
$basicAuthToken = 'XD8ODNmNTU6NWNmN....',
/** @var string Signnow account user creadentials */
$loginEmail = '[email protected]',
$loginPassword = 'Your.pwd!',
/** @var string (Dynamic) access (bearer) token used across most of requests */
$accessToken,
/** @var string Cache key specific to credentials */
$cacheKey;
/**
* Constructor - set user credentials for API requests
* @param string $url
* @param string $email
* @param string $pwd
* @param string $basicToken
*/
public function __construct($url, $email, $pwd, $basicToken)
{
$this->urlEndpoint = trim($url);
$this->loginEmail = trim($email);
$this->loginPassword = trim($pwd);
$this->basicAuthToken = trim($basicToken);
$this->cacheKey = substr(md5($url.$email.$basicToken), 0, 10);
if(!$this->cacheKey){
throw new \Exception('Cache key may not be empty.');
}
$this->cacheKey = "signnow.bearer.resp.{$this->cacheKey}";
}
/**
* Return bearer (access) token
* Either load valid from cache or obtain a new token
*/
public function getAccessToken($forceNew = false, $refresh = false) : string
{
if($this->accessToken && !$forceNew && !$refresh){
return $this->accessToken;
}
// optional - set your cache handler, this is just example for Yii2 framework
//$cache = Yii::$app->cache;
$cache = null;
$resp = $forceNew || !$cache? '' : $cache->get($this->cacheKey);
if(!$resp || $forceNew){
// generate bearer token
$data = [
// password|refresh_token|authorization_code
'grant_type' => 'password',
'username' => $this->loginEmail,
'password' => $this->loginPassword,
];
$options = [
'headers' => [
'Authorization: Basic '.$this->basicAuthToken,
'Content-type: application/x-www-form-urlencoded',
]];
$resp = $this->doHttpRequest('/oauth2/token', $data, $options);
if(!empty($resp['access_token']) && !empty($resp['expires_in']) && $cache){
// store response to cache
$cache->set($this->cacheKey, $resp);
}
}
// should be valid, default expiration is 30 days - 2592000 secs
$this->accessToken = empty($resp['access_token']) ? '' : $resp['access_token'];
if($this->accessToken && $refresh && !$forceNew && !empty($resp['refresh_token'])){
// refresh but only if this is not just forced new token, makes no sense to refresh new token
$data = [
'grant_type' => 'refresh_token',
'refresh_token' => $resp['refresh_token'],
];
$options = [
'headers' => [
'Authorization: Basic '.$this->basicAuthToken,
'Content-type: application/x-www-form-urlencoded',
]];
$resp = $this->doHttpRequest('/oauth2/token', $data, $options);
if(!empty($resp['access_token']) && !empty($resp['expires_in']) && $cache){
$cache->set($this->cacheKey, $resp);
$this->accessToken = $resp['access_token'];
}
}
return $this->accessToken;
}
/**
* Return info about bearer token validity - expires_in [secs], token_type, ..
*/
public function verifyToken()
{
$info = [];
$accessToken = $this->getAccessToken();
if($accessToken){
$options = [
'method' => 'GET', // (!) may not be POST, or returns "invalid_client"
'headers' => [
'Authorization: Bearer '.$accessToken,
'Content-type: application/x-www-form-urlencoded',
]];
$info = $this->doHttpRequest('/oauth2/token', [], $options);
}
return $info;
}
/**
* Return user account info - id, primary_email, emails[0] .., first_name, last_name, ..
*/
public function getUserInfo()
{
$info = [];
$accessToken = $this->getAccessToken();
if($accessToken){
$options = [
'method' => 'GET', // (!) may not be POST, or returns "invalid_client"
'headers' => [
'Authorization: Bearer '.$accessToken,
'Content-type: application/json',
]];
$info = $this->doHttpRequest('/user', [], $options);
}
return $info;
}
/**
* Uploads a file to user's account and returns unique id of the uploaded document.
* Accepts .doc, .docx, .pdf, .xls, .xlsx, .ppt, .pptx and .png file types
* @param string $path Abs. path to file
*/
public function uploadFile($path)
{
if(!is_file($path)){
throw new \Exception("File not found in [{$path}].");
}
if(!$this->getAccessToken()){
throw new \Exception("Invalid access token.");
}
// based on https://stackoverflow.com/questions/4003989/upload-a-file-using-file-get-contents
$boundary = '-----------------------'.microtime(true);
$ext = strtolower( pathinfo($path, PATHINFO_EXTENSION) );
$file_contents = file_get_contents($path);
if(!in_array($ext, ['pdf', 'docx', 'xlsx', 'png'])){
throw new \Exception("File type [{$ext}] not allowed for uploading.");
}
// build multipart stream
$data = "--{$boundary}\r\n"
// (!) important - must include filename=\"".basename($path)."\" - to detect file type
// also will store file name in dashboard/documents
."Content-Disposition: form-data; name=\"file\"; filename=\"".basename($path)."\"; \r\n\r\n"
.$file_contents."\r\n"
."--{$boundary}--\r\n";
$options = [
'headers' => [
'Authorization: Bearer '.$this->accessToken,
'Content-type: multipart/form-data; boundary='.$boundary,
]];
// resp e.g. {'id' => "3b323840975b9a*********"}
return $this->doHttpRequest('/document', $data, $options);
}
/**
* HTTP/HTTPS request without SSL verification and without any dependency
* https://stackoverflow.com/questions/11319520/php-posting-json-via-file-get-contents
* @param string $url
* @param string|array $data
* @param string $options e.g. timeout => 5, method => 'GET', content, header, ..
* @param bool $tryJson If TRUE, try to convert response string into JSON
*/
protected function doHttpRequest($url, $data = null, array $options = [], $tryJson = true)
{
$options = array_change_key_case($options, CASE_LOWER);
// default Signnow API headers
$headers = [
'Accept: application/json',
];
if(!empty($options['headers'])){
$headers = array_merge($headers, $options['headers']);
unset($options['headers']);
}
$http = [
'timeout' => 5, // 5 secs
'method' => 'POST',
'header' => $headers,
];
if($data){
$http['content'] = $data;
}
// explicitly defined HTTP section
if(!empty($options['http'])){
$http = $options['http'] + $http;
unset($options['http']);
}
$ssl = [
// disable certificate check if needed in sandbox
//'verify_peer' => DEBUG_MODE ? false : true,
//'verify_peer_name' => DEBUG_MODE ? false : true,
];
// explicitly defined SSL section
if(!empty($options['ssl'])){
$ssl = $options['ssl'] + $ssl;
unset($options['ssl']);
}
// merge remaining HTTP options
if(!empty($options)){
$http = $options + $http;
}
// build e.g. POST FORM data - must be "Content-type: application/x-www-form-urlencoded"
if(!empty($http['content']) && is_array($http['content'])){
$http['content'] = http_build_query($http['content']);
}
$ctx = stream_context_create([
'http' => $http,
'ssl' => $ssl,
]);
// convert relative URL to absolute
if(false === stripos($url, '://')){
$url = rtrim($this->urlEndpoint, ' \/') .'/'. ltrim($url, ' \/');
}
$response = file_get_contents($url, false, $ctx);
// attempt converting to JSON
if($tryJson && $response && is_string($response) && !!($tmp = json_decode($response, true))){
$response = $tmp;
}
return $response;
}
} |
When attempting to upload a file with sample code (developer test app):
Throws error:
Unexpected exception of type [SignNow\Rest\EntityManager\Exception\EntityManagerException] with message [Request failed! Reason: cURL error 60: SSL certificate problem: self signed certificate in certificate chain (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://api-eval.signnow.com/document] in [...\app\vendor\signnow\rest-entity-manager\src\EntityManager.php:318]
How to allow Guzzle to accept self signed certificate in client wrapper?
The text was updated successfully, but these errors were encountered: