Skip to content

Instantly share code, notes, and snippets.

@clnt
Created March 8, 2020 03:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save clnt/f629cc71217a822221a03713905f9f7b to your computer and use it in GitHub Desktop.
Save clnt/f629cc71217a822221a03713905f9f7b to your computer and use it in GitHub Desktop.
HERE API returning Signature mismatch
<?php
namespace App\Admin\Geocoding\Headers;
use Illuminate\Support\Str;
use InvalidArgumentException;
use RuntimeException;
class OAuth1Header
{
public const REQUEST_METHOD_HEADER = 'header';
public const REQUEST_METHOD_QUERY = 'query';
public const SIGNATURE_METHOD_HMAC_1 = 'HMAC-SHA1';
public const SIGNATURE_METHOD_HMAC_256 = 'HMAC-SHA256';
public const SIGNATURE_METHOD_RSA = 'RSA-SHA1';
public const SIGNATURE_METHOD_PLAINTEXT = 'PLAINTEXT';
/**
* @var array
*/
private $options;
/**
* @var string
*/
protected $method;
/**
* If post request, pass body contents.
*
* @var array
*/
protected $body;
/**
* @var string
*/
protected $nonce;
public function __construct(array $options = [], string $method = 'GET', array $body = [])
{
$this->options = array_merge($this->getDefaultOptions(), $options);
$this->method = $method;
$this->body = $body;
$this->nonce = Str::random(32);
}
public static function make(array $options = [], string $method = 'GET', array $body = []): self
{
return new self($options, $method, $body);
}
/**
* Returns the built OAuth 1.0 header as a query string
* or Authorization header value.
*/
public function build(): string
{
$parameters = $this->buildParameters();
if ($this->getRequestMethod() === self::REQUEST_METHOD_HEADER) {
return $this->buildAuthorizationHeader($parameters);
}
if ($this->getRequestMethod() === self::REQUEST_METHOD_QUERY) {
return $this->buildQuery($parameters);
}
throw new InvalidArgumentException('Invalid request_method option: ' . $this->getRequestMethod());
}
private function buildAuthorizationHeader(array $params): string
{
foreach ($params as $key => $value) {
if (array_key_exists($key, $this->body)) {
unset($params[$key]);
continue;
}
$params[$key] = $key . '="' . rawurlencode($value) . '"';
}
// Replicate same order as Postman
$params = [
$params['oauth_consumer_key'],
$params['oauth_signature_method'],
$params['oauth_timestamp'],
$params['oauth_nonce'],
$params['oauth_version'],
$params['oauth_signature'],
];
return 'OAuth ' . implode(',', $params);
}
private function buildQuery(array $params): string
{
return http_build_query($params, '', '&', PHP_QUERY_RFC3986);
}
private function buildParameters(): array
{
$params = $this->generateOAuthParameters();
if ($this->method === 'POST' && count($this->body) > 0) {
$params = array_merge($params, $this->body);
}
return tap($this->generateSignature($params), static function (array &$params): void {
uksort($params, 'strcmp');
});
}
private function generateOAuthParameters(): array
{
$params = [
'oauth_consumer_key' => $this->options['consumer_key'],
'oauth_signature_method' => $this->getSignatureMethod(),
'oauth_timestamp' => now()->timestamp,
'oauth_nonce' => $this->nonce,
];
$this->applyOptionalParameters($params);
return $params;
}
private function applyOptionalParameters(array &$params): void
{
$optionalParams = [
'callback' => 'oauth_callback',
'token' => 'oauth_token',
'verifier' => 'oauth_verifier',
'version' => 'oauth_version',
];
foreach ($optionalParams as $optionName => $oauthName) {
if (isset($this->options[$optionName]) === false) {
continue;
}
$params[$oauthName] = $this->options[$optionName];
}
}
private function generateSignature(array $params): array
{
$baseString = $this->generateBaseString($params);
if (in_array($this->getSignatureMethod(), [
self::SIGNATURE_METHOD_HMAC_1,
self::SIGNATURE_METHOD_HMAC_256,
], true)
) {
return array_merge($params, [
'oauth_signature' => $this->signUsingHmac($this->getSignatureMethod(), $baseString),
]);
}
if ($this->getSignatureMethod() === self::SIGNATURE_METHOD_RSA) {
return array_merge($params, [
'oauth_signature' => $this->signUsingRsa($baseString),
]);
}
return array_merge($params, [
'oauth_signature' => $baseString,
]);
}
private function generateBaseString(array $params): string
{
return strtoupper($this->method)
. '&' . rawurlencode($this->buildQuery($params));
}
private function signUsingHmac(string $algorithm, string $baseString): string
{
$secret = rawurlencode($this->options['consumer_secret']);
if (isset($this->options['token_secret'])) {
$secret .= '&' . rawurlencode($this->options['token_secret']);
}
return base64_encode(
hash_hmac(
$algorithm === self::SIGNATURE_METHOD_HMAC_1 ? 'sha1' : 'sha256',
$baseString,
$secret,
true
)
);
}
private function signUsingRsa(string $baseString): string
{
if (!function_exists('openssl_pkey_get_private')) {
throw new RuntimeException('RSA-SHA1 signature method '
. 'requires the OpenSSL extension.');
}
$privateKey = openssl_pkey_get_private(
file_get_contents($this->options['private_key_file']),
$this->options['private_key_passphrase']
);
$signature = '';
openssl_sign($baseString, $signature, $privateKey);
openssl_free_key($privateKey);
return $signature;
}
private function getDefaultOptions(): array
{
return [
'version' => '1.0',
'request_method' => self::REQUEST_METHOD_HEADER,
'consumer_key' => 'anonymous',
'consumer_secret' => 'anonymous',
'signature_method' => self::SIGNATURE_METHOD_HMAC_256,
];
}
private function getRequestMethod(): ?string
{
return $this->options['request_method'] ?? null;
}
private function getSignatureMethod(): ?string
{
return $this->options['signature_method'] ?? null;
}
public function getOptions(): array
{
return $this->options;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment