Skip to content

Commit fb7d64c

Browse files
author
Robert Wittman
committed
Fixed cURL instantiation
1 parent 2ae4784 commit fb7d64c

File tree

6 files changed

+156
-24
lines changed

6 files changed

+156
-24
lines changed

README.md

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,44 @@ This SDK was created to enable rapid efiicient development using Shopify's API.
77

88
## Installation
99

10-
```json
11-
{
12-
"require" : {
13-
"robby-bugatti/shopify-php-sdk": "1.0.0"
14-
}
15-
}
16-
```
17-
or
10+
Easily install this package with composer
1811

1912
```shell
2013
composer require robby-bugatti/shopify-php-sdk
2114
```
2215

23-
then install
24-
25-
```shell
26-
composer install
27-
```
28-
2916
Before you can start using this SDK, you have to create a <a href="https://partners.shopify.com/">Shopify Application</a>
3017
You can now use the API key and secret to generate access tokens, which can then access a stores data
3118

19+
### A Note on Strict Execution
20+
21+
When the SDK is run, it defaults to running in a strict environment. This requires that:
22+
A) The HMAC Hash of request parameters matches output generated by your application's secret key
23+
B) Authentication responses contain a 'state' parameter that matches the one placed in the request
24+
25+
Because you can be deploying your application in distributed environments, or be using any number of
26+
storage engines, this SDK will not store these state variables for you. However, it does expose a few
27+
functions for generating and managing them
28+
29+
```php
30+
\Shopify\Auth::generateNonce() // Returns a hashed string of <store>.<timestamp>, using API Secret as key
31+
// hash_hmac('sha256', <store>.<timestamp>, \Shopify\Shopify::api_secret());s
32+
```
33+
This will return a hashed string, composed by concatenating the store name with a timestamp, using the API Secret
34+
as the key.
3235

33-
## Usage / Examples
36+
```php
37+
\Shopify\Auth::setNonce( $nonce = NULL )
38+
```
39+
This will set a nonce in the Auth Object. It will be added to the authorizationUrl, and when required,
40+
compare it to the ?state=<nonce_here> returned by Shopify
3441

35-
Essentially, there are 2 ways to initialize this SDK.
42+
```php
43+
\Shopify\Auth::checkNonce( $nonce = NULL )
44+
```
45+
This will return TRUE or FALSE, depending on if the nonce in the URL matches a nonce set through setNonce()
46+
This function is automatically run during accessToken() in strict environments, so you shouldnt need to
47+
call it explicitly
3648

3749
### Authentication
3850

@@ -46,14 +58,17 @@ $options = array(
4658
'permissions' => "<permissions your applicaiton requires, comma separated>",
4759
'store' => "myshopify.domain.com"
4860
);
49-
5061
\Shopify\Shopify::init($options);
5162

63+
// Store this somewhere so we can compare it later
64+
$storageEngine->store($nonce);
65+
5266
if(isset($_GET['code']))
5367
{
5468
// Redirect to Shopify to start OAuth
5569
header("Location: ".\Shopify\Auth::authorizationUrl());
5670
} else {
71+
5772
// We can go ahead and get the access token
5873
echo \Shopify\Auth::accessToken();
5974
// This should return something that looks like this:
@@ -74,6 +89,7 @@ $options = array(
7489

7590
You now have access to all the methods the SDK provides!
7691

92+
7793
### Reading
7894

7995
The SDK uses static methods to fetch data from Shopify

lib/Auth.php

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public static function authorizationUrl()
3333
'client_id' => \Shopify\Shopify::api_key(),
3434
'scope' => \Shopify\Shopify::permissions()
3535
);
36+
3637
// Check if we require strict standards
3738
if(is_null(self::$nonce) && \Shopify\Shopify::strict())
3839
{
@@ -44,15 +45,16 @@ public static function authorizationUrl()
4445

4546
/**
4647
* Fetch an access token
48+
* @param string $nonce
4749
* @return \Shopfiy\AccessToken
4850
*/
49-
public static function accessToken()
51+
public static function accessToken( $nonce = NULL )
5052
{
5153
// Do any strict checking for our OAuth requests
5254
if(\Shopify\Shopify::strict())
5355
{
54-
if( is_null(self::$nonce) || ! isset( $_GET['state'])) throw new \Exception("Strict API execution requires a nonce for Authentication requests");
55-
if(!\Shopify\Shopify::strict() && !\Shopify\Shopify::validateHmac()) throw new Exception\ApiException("Strict API execution requires a valid HMAC signature");
56+
if( !self::checkNonce( $nonce ) ) throw new \Exception("Strict API execution requires a nonce for Authentication requests");
57+
if( !\Shopify\Shopify::validateHmac() ) throw new Exception\ApiException("Strict API execution requires a valid HMAC signature");
5658
}
5759
return \Shopify\AccessToken::createFromCode($_GET['code']);
5860
}
@@ -65,4 +67,32 @@ public static function setNonce($nonce)
6567
{
6668
self::$nonce = $nonce;
6769
}
70+
71+
/**
72+
* Verify that nonces match
73+
* @param string $nonce
74+
* @return boolean
75+
*/
76+
public static function checkNonce($nonce = NULL)
77+
{
78+
return $nonce === $_GET['state'];
79+
}
80+
81+
public static function getNonce()
82+
{
83+
return self::$nonce;
84+
}
85+
86+
/**
87+
* Create a nonce to be used for authentication
88+
*
89+
* This can be substituted with whichever technique a developer wants to implement
90+
*
91+
* @return string
92+
*/
93+
public static function generateNonce()
94+
{
95+
if(is_null(\Shopify\Shopify::api_secret())) throw new Exception\ApiException("API Secret required for nonce generation");
96+
return hash_hmac('sha256', \Shopify\Shopify::store().'.'.time(),\Shopify\Shopify::api_secret() );
97+
}
6898
}

lib/Http/Client.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,8 @@ public function request(Request $request)//$method, $url, $params = array(), $js
115115
// Execute our curl request
116116
curl_setopt_array($curl, $opts);
117117
$res_body = curl_exec($curl);
118-
$errno = curl_errno($curl);
119118
$rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
120-
curl_close($curl);
119+
$errno = curl_errno($curl);
121120

122121
// Check if curl instantiated, or throw an error
123122
if($res_body === false)
@@ -146,6 +145,7 @@ public function request(Request $request)//$method, $url, $params = array(), $js
146145
}
147146
unset($jsonBody);
148147

148+
curl_close($curl);
149149

150150
$this->handleHttpCode($response->getHttpCode());
151151
return $response;
@@ -168,6 +168,7 @@ private function handleHttpCode($code)
168168
case 404:
169169
$msg = "That resource does not exist";
170170
break;
171+
case 400:
171172
case 406:
172173
$msg = "Request Information was Not Acceptable";
173174
break;
@@ -180,6 +181,9 @@ private function handleHttpCode($code)
180181
case 500:
181182
$msg = "There was an error comunicating with Shopify";
182183
break;
184+
case 503:
185+
$msg = "Service unavailable. Is your domain set correctly?";
186+
break;
183187
default: $msg = "An unknown error code [{$code}] was returned";
184188
}
185189
throw new \Shopify\Exception\ApiException($msg);

tests/AuthTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Shopify;
4+
5+
class AuthTest extends TestCase
6+
{
7+
// private $opts = [
8+
// 'test' => TRUE,
9+
// 'debug' => FALSE,
10+
// 'strict' => FALSE,
11+
// 'api_key' => '1231g3g24gq3v',
12+
// 'api_secret' => "asdpaosidcoaij23r",
13+
// 'redirect_uri' => "https://some_randomg_url",
14+
// 'access_token' => 'asdifpaoisdjpfaoijsdfp',
15+
// 'permissions' => "read_products,write_products",
16+
// 'store' => "dev.myshopify.com"
17+
// ];
18+
19+
public function testGenerateNonce()
20+
{
21+
$nonce = Auth::generateNonce();
22+
$this->assertEquals($nonce, hash_hmac('sha256', Shopify::store().'.'.time(), Shopify::api_secret()));
23+
}
24+
25+
public function testSetNonce()
26+
{
27+
Auth::setNonce('some-random-string');
28+
$this->assertEquals(Auth::getNonce(), 'some-random-string');
29+
}
30+
31+
public function testAuthorizationUrl()
32+
{
33+
Auth::setNonce('some-random-string');
34+
$url = Auth::authorizationUrl();
35+
$query_string = parse_url($url, PHP_URL_QUERY);
36+
$params = [];
37+
parse_str($query_string, $params);
38+
$this->assertNotNull($params['state']);
39+
$this->assertEquals($params['state'], 'some-random-string');
40+
}
41+
}

tests/Http/ClientTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Shopify;
4+
5+
class ClientTest extends TestCase
6+
{
7+
protected $client;
8+
9+
public function setUp()
10+
{
11+
$this->client = new Http\Client();
12+
}
13+
14+
/**
15+
* @expectedException \Shopify\Exception\ApiException
16+
*/
17+
public function testClientInvalidDomain()
18+
{
19+
$request = new Http\Request('0.0.0.0', 'GET', [], []);
20+
$response = $this->client->request($request);
21+
}
22+
23+
/**
24+
* @expectedException \Shopify\Exception\CurlException
25+
*/
26+
public function testClientInitFailure()
27+
{
28+
$request = new Http\Request('http://test-shop/admin/products.json' ,'GET', [], []);
29+
$response = $this->client->request($request);
30+
}
31+
}

tests/TestCase.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,19 @@
55
class TestCase extends \PHPUnit_Framework_TestCase
66
{
77
protected $client;
8-
8+
private $opts = [
9+
'test' => TRUE,
10+
'debug' => FALSE,
11+
'strict' => FALSE,
12+
'api_key' => '1231g3g24gq3v',
13+
'api_secret' => "asdpaosidcoaij23r",
14+
'redirect_uri' => "https://some_randomg_url",
15+
'access_token' => 'asdifpaoisdjpfaoijsdfp',
16+
'permissions' => "read_products,write_products",
17+
'store' => "dev.myshopify.com"
18+
];
919
public function setUp()
1020
{
11-
Shopify::init(['test' => TRUE]);
21+
Shopify::init($this->opts);
1222
}
1323
}

0 commit comments

Comments
 (0)