Skip to content
This repository has been archived by the owner on Feb 15, 2024. It is now read-only.

Commit

Permalink
Allow mult-dimension arrays when generating base string for HMAC-SHA1…
Browse files Browse the repository at this point in the history
… signature. Fixes thephpleague#39

With this implementation, multi-dimensional arrays can be passed
to League\OAuth1\Client\Server::getHeaders() and be correctly
transformed. For prior art, consider the WP REST API - OAuth 1.0a Server
plugin. specifically:
* [https://github.com/WP-API/OAuth1/blob/master/lib/class-wp-rest-oauth1.php#L667-L674](https://github.com/WP-API/OAuth1/blob/master/lib/class-wp-rest-oauth1.php#L667-L674)
* [https://github.com/WP-API/OAuth1/blob/master/lib/class-wp-rest-oauth1.php#L706-L753](https://github.com/WP-API/OAuth1/blob/master/lib/class-wp-rest-oauth1.php#L706-L753)
  • Loading branch information
jtsternberg committed Feb 10, 2016
1 parent 3df4d74 commit dece413
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 7 deletions.
49 changes: 42 additions & 7 deletions src/Client/Signature/HmacSha1Signature.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,54 @@ protected function baseString(Url $url, $method = 'POST', array $parameters = ar

$data = array();
parse_str($url->getQuery(), $query);
foreach (array_merge($query, $parameters) as $key => $value) {
$data[rawurlencode($key)] = rawurlencode($value);
}
$data = array_merge($query, $parameters);

ksort($data);
array_walk($data, function (&$value, $key) {
$value = $key.'='.$value;
// normalize data key/values
array_walk_recursive($data, function (&$key, &$value) {
$key = rawurlencode(rawurldecode($key));
$value = rawurlencode(rawurldecode($value));
});
$baseString .= rawurlencode(implode('&', $data));
ksort($data);

$baseString .= $this->queryStringFromData($data);

return $baseString;
}

/**
* Creates an array of urlencoded strings out of each array key/value pair
* Handles multi-demensional arrays recursively.
*
* @param array $data Array of parameters to convert.
* @param array $queryParams Array to extend. False by default.
* @param string $prevKey Optional Array key to append
*
* @return string urlencoded string version of data
*/
protected function queryStringFromData($data, $queryParams = false, $prevKey = '')
{
if ($initial = (false === $queryParams)) {
$queryParams = array();
}

foreach ($data as $key => $value) {
if ($prevKey) {
$key = $prevKey.'['.$key.']'; // Handle multi-dimensional array
}
if (is_array($value)) {
$queryParams = $this->queryStringFromData($value, $queryParams, $key);
} else {
$queryParams[] = urlencode($key.'='.$value); // join with equals sign
}
}

if ($initial) {
return implode('%26', $queryParams); // join with ampersand
}

return $queryParams;
}

/**
* Hashes a string with the signature's key.
*
Expand Down
111 changes: 111 additions & 0 deletions tests/HmacSha1SignatureTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php namespace League\OAuth1\Client\Tests;

/**
* Part of the Sentry package.
*
Expand Down Expand Up @@ -44,6 +45,116 @@ public function testSigningRequest()
$this->assertEquals('A3Y7C1SUHXR1EBYIUlT3d6QT1cQ=', $signature->sign($uri, $parameters));
}

public function testQueryStringFromArray()
{
$array = array('a' => 'b');
$res = $this->invokeQueryStringFromData($array);

$this->assertSame(
'a%3Db',
$res
);
}

public function testQueryStringFromIndexedArray()
{
$array = array('a', 'b');
$res = $this->invokeQueryStringFromData($array);

$this->assertSame(
'0%3Da%261%3Db',
$res
);
}

public function testQueryStringFromMultiDimensionalArray()
{
$array = array(
'a' => array(
'b' => array(
'c' => 'd',
),
'e' => array(
'f' => 'g',
),
),
'h' => 'i',
'empty' => '',
'null' => null,
'false' => false,
);

// Convert to query string.
$res = $this->invokeQueryStringFromData($array);

$this->assertSame(
'a%5Bb%5D%5Bc%5D%3Dd%26a%5Be%5D%5Bf%5D%3Dg%26h%3Di%26empty%3D%26null%3D%26false%3D',
$res
);

// Reverse engineer the string.
$res = urldecode($res);

$this->assertSame(
'a[b][c]=d&a[e][f]=g&h=i&empty=&null=&false=',
$res
);

// Finally, parse the string back to an array.
parse_str($res, $original_array);

// And ensure it matches the orignal array (approximately).
$this->assertSame(
array(
'a' => array(
'b' => array(
'c' => 'd',
),
'e' => array(
'f' => 'g',
),
),
'h' => 'i',
'empty' => '',
'null' => '', // null value gets lost in string translation
'false' => '', // false value gets lost in string translation
),
$original_array
);
}

public function testSigningRequestWithMultiDimensionalParams()
{
$signature = new HmacSha1Signature($this->getMockClientCredentials());

$uri = 'http://www.example.com/';
$parameters = array(
'a' => array(
'b' => array(
'c' => 'd',
),
'e' => array(
'f' => 'g',
),
),
'h' => 'i',
'empty' => '',
'null' => null,
'false' => false,
);

$this->assertEquals('ZUxiJKugeEplaZm9e4hshN0I70U=', $signature->sign($uri, $parameters));
}

protected function invokeQueryStringFromData(array $args)
{
$signature = new HmacSha1Signature(m::mock('League\OAuth1\Client\Credentials\ClientCredentialsInterface'));
$refl = new \ReflectionObject($signature);
$method = $refl->getMethod('queryStringFromData');
$method->setAccessible(true);
return $method->invokeArgs($signature, array($args));
}

protected function getMockClientCredentials()
{
$clientCredentials = m::mock('League\OAuth1\Client\Credentials\ClientCredentialsInterface');
Expand Down

0 comments on commit dece413

Please sign in to comment.