Repository: Shopify/shopify_python_api

Issue:
RecurringApplicationCharge.current() does not work as expected
In RecurringApplicationCharge class there is class method "current" that doesn't work as expexted. 

## Problem
I have few RecurringApplicationCharge instances for my test shop but none of them has status=active. I was expecting to get None as a result when using current() method but instead a RecurringApplicationCharge with status rejected was returned.

```python
class RecurringApplicationCharge(ShopifyResource):

    @classmethod
    def current(cls):
        return cls.find_first(status="active")
```

## Excpected result 
First RecurringApplicationCharge object with status=active should be returned or None if no matches found. 

Anyway I noticed that documentation (http://docs.shopify.com/api/recurringapplicationcharge) states that it is only possible to filter with field "since_id". If this is correct, then this method should be removed.


Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index f62c50b..ba6c0eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ tags
 /ShopifyAPI.egg-info
 *.egg
 /.idea
+.DS_Store
diff --git a/CHANGELOG b/CHANGELOG
index 00fe323..4301098 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,7 +2,6 @@
 
 * Added Checkout resource
 * Updated to pyactiveresource v2.1.1 which includes a test-related bugfix
-* Changed OAuth validation from MD5 to HMAC-SHA256
 
 == Version 2.1.0
 
diff --git a/README.md b/README.md
index 7234905..2f362f1 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,3 @@
-[![Build Status](https://travis-ci.org/Shopify/shopify_python_api.svg?branch=master)](https://travis-ci.org/Shopify/shopify_python_api)
-[![Package Version](https://pypip.in/version/ShopifyAPI/badge.svg)](https://pypi.python.org/pypi/ShopifyAPI)
-
 # Shopify API
 
 The ShopifyAPI library allows Python developers to programmatically
@@ -62,11 +59,11 @@ these steps:
      shop_url = "https://%s:%s@SHOP_NAME.myshopify.com/admin" % (API_KEY, PASSWORD)
      shopify.ShopifyResource.set_site(shop_url)
      ```
-
+     
      That's it you're done, skip to step 6 and start using the API!
      For a partner App you will need to supply two parameters to the
      Session class before you instantiate it:
-
+     
      ```python
      shopify.Session.setup(api_key=API_KEY, secret=SHARED_SECRET)
      ```
@@ -85,27 +82,27 @@ these steps:
     * scope – Required – The list of required scopes (explained here: http://docs.shopify.com/api/tutorials/oauth)
     * redirect_uri – Optional – The URL that the merchant will be sent to once authentication is complete. Defaults to the URL specified in the application settings and must be the same host as that URL.
     ```
-
+    
     We've added the create_permision_url method to make this easier, first
     instantiate your session object:
-
+    
     ```python
     session = shopify.Session("SHOP_NAME.myshopify.com")
     ```
-
+   
     Then call:
 
     ```python
     scope=["write_products"]
     permission_url = session.create_permission_url(scope)
     ```
-
+    
     or if you want a custom redirect_uri:
-
+    
     ```python
     permission_url = session.create_permission_url(scope, "https://my_redirect_uri.com")
     ```
-
+    
 4.  Once authorized, the shop redirects the owner to the return URL of your
     application with a parameter named 'code'. This is a temporary token
     that the app can exchange for a permanent access token. Make the following call:
@@ -119,11 +116,11 @@ these steps:
     * client_secret – Required – The shared secret for your app
     * code – Required – The code you received in step 3
     ```
-
+    
     and you'll get your permanent access token back in the response.
 
-    There is a method to make the request and get the token for you. Pass
-    all the params received from the previous call (shop, code, timestamp,
+    There is a method to make the request and get the token for you. Pass 
+    all the params received from the previous call (shop, code, timestamp, 
     signature) as a dictionary and the method will verify
     the params, extract the temp code and then request your token:
 
diff --git a/shopify/resources/recurring_application_charge.py b/shopify/resources/recurring_application_charge.py
index f0e3004..5ed4c92 100644
--- a/shopify/resources/recurring_application_charge.py
+++ b/shopify/resources/recurring_application_charge.py
@@ -1,11 +1,21 @@
 from ..base import ShopifyResource
 
+def _get_first_by_status(resources, status):
+    for resource in resources:
+        if resource.status == status:
+            return resource
+    return None
+
 
 class RecurringApplicationCharge(ShopifyResource):
 
     @classmethod
     def current(cls):
-        return cls.find_first(status="active")
+        """
+        Returns first RecurringApplicationCharge object with status=active.
+        If not found, None will be returned.
+        """
+        return _get_first_by_status(cls.find(), "active")
 
     def cancel(self):
         self._load_attributes_from_response(self.destroy)
diff --git a/shopify/session.py b/shopify/session.py
index 8741ec4..9058d55 100644
--- a/shopify/session.py
+++ b/shopify/session.py
@@ -1,6 +1,8 @@
 import time
-import hmac
-from hashlib import sha256
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
 try:
     import simplejson as json
 except ImportError:
@@ -51,7 +53,7 @@ class Session(object):
             return self.token
 
         if not self.validate_params(params):
-            raise ValidationException('Invalid HMAC: Possibly malicious login')
+            raise ValidationException('Invalid Signature: Possibly malicious login')
 
         code = params['code']
 
@@ -92,30 +94,18 @@ class Session(object):
         if int(params['timestamp']) < time.time() - one_day:
             return False
 
-        return cls.validate_hmac(params)
+        return cls.validate_signature(params)
 
     @classmethod
-    def validate_hmac(cls, params):
-        if 'hmac' not in params:
+    def validate_signature(cls, params):
+        if "signature" not in params:
             return False
 
-        hmac_calculated = cls.calculate_hmac(params)
-        hmac_to_verify = params['hmac']
+        sorted_params = ""
+        signature = params['signature']
 
-        # Try to use compare_digest() to reduce vulnerability to timing attacks.
-        # If it's not available, just fall back to regular string comparison.
-        try:
-            return hmac.compare_digest(hmac_calculated, hmac_to_verify)
-        except AttributeError:
-            return hmac_calculated == hmac_to_verify
+        for k in sorted(params.keys()):
+            if k != "signature":
+                sorted_params += k + "=" + str(params[k])
 
-    @classmethod
-    def calculate_hmac(cls, params):
-        """
-        Calculate the HMAC of the given parameters in line with Shopify's rules for OAuth authentication.
-        See http://docs.shopify.com/api/authentication/oauth#verification.
-        """
-        # Sort and combine query parameters into a single string, excluding those that should be removed and joining with '&'.
-        sorted_params = '&'.join(['{0}={1}'.format(k, params[k]) for k in sorted(params.keys()) if k not in ['signature', 'hmac']])
-        # Generate the hex digest for the sorted parameters using the secret.
-        return hmac.new(cls.secret.encode(), sorted_params.encode(), sha256).hexdigest()
+        return md5((cls.secret + sorted_params).encode('utf-8')).hexdigest() == signature


Test patch:
diff --git a/test/fixtures/recurring_application_charges.json b/test/fixtures/recurring_application_charges.json
new file mode 100644
index 0000000..45b4109
--- /dev/null
+++ b/test/fixtures/recurring_application_charges.json
@@ -0,0 +1,38 @@
+{
+  "recurring_application_charges": [
+    {
+      "activated_on": null,
+      "api_client_id": 755357713,
+      "billing_on": "2015-01-15T00:00:00+00:00",
+      "cancelled_on": null,
+      "created_at": "2015-01-16T11:45:44-05:00",
+      "id": 455696195,
+      "name": "Super Mega Plan",
+      "price": "15.00",
+      "return_url": "http://yourapp.com",
+      "status": "accepted",
+      "test": null,
+      "trial_days": 0,
+      "trial_ends_on": null,
+      "updated_at": "2015-01-16T11:46:56-05:00",
+      "decorated_return_url": "http://yourapp.com?charge_id=455696195"
+    },
+    {
+      "activated_on": "2015-01-15T00:00:00+00:00",
+      "api_client_id": 755357713,
+      "billing_on": "2015-01-15T00:00:00+00:00",
+      "cancelled_on": null,
+      "created_at": "2015-01-16T11:45:44-05:00",
+      "id": 455696195,
+      "name": "Super Mega Plan 2",
+      "price": "15.00",
+      "return_url": "http://yourapp.com",
+      "status": "active",
+      "test": null,
+      "trial_days": 0,
+      "trial_ends_on": null,
+      "updated_at": "2015-01-16T11:46:56-05:00",
+      "decorated_return_url": "http://yourapp.com?charge_id=455696195"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/test/fixtures/recurring_application_charges_no_active.json b/test/fixtures/recurring_application_charges_no_active.json
new file mode 100644
index 0000000..c806aa3
--- /dev/null
+++ b/test/fixtures/recurring_application_charges_no_active.json
@@ -0,0 +1,38 @@
+{
+  "recurring_application_charges": [
+    {
+      "activated_on": null,
+      "api_client_id": 755357713,
+      "billing_on": "2015-01-15T00:00:00+00:00",
+      "cancelled_on": null,
+      "created_at": "2015-01-16T11:45:44-05:00",
+      "id": 455696195,
+      "name": "Super Mega Plan",
+      "price": "15.00",
+      "return_url": "http://yourapp.com",
+      "status": "accepted",
+      "test": null,
+      "trial_days": 0,
+      "trial_ends_on": null,
+      "updated_at": "2015-01-16T11:46:56-05:00",
+      "decorated_return_url": "http://yourapp.com?charge_id=455696195"
+    },
+    {
+      "activated_on": "2015-01-15T00:00:00+00:00",
+      "api_client_id": 755357713,
+      "billing_on": "2015-01-15T00:00:00+00:00",
+      "cancelled_on": null,
+      "created_at": "2015-01-16T11:45:44-05:00",
+      "id": 455696195,
+      "name": "Super Mega Plan 2",
+      "price": "15.00",
+      "return_url": "http://yourapp.com",
+      "status": "rejected",
+      "test": null,
+      "trial_days": 0,
+      "trial_ends_on": null,
+      "updated_at": "2015-01-16T11:46:56-05:00",
+      "decorated_return_url": "http://yourapp.com?charge_id=455696195"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/test/recurring_charge_test.py b/test/recurring_charge_test.py
index eca78e2..70b3660 100644
--- a/test/recurring_charge_test.py
+++ b/test/recurring_charge_test.py
@@ -7,3 +7,17 @@ class RecurringApplicationChargeTest(TestCase):
         self.fake("recurring_application_charges/35463/activate", method='POST',headers={'Content-length':'0', 'Content-type': 'application/json'}, body=" ")
         charge = shopify.RecurringApplicationCharge({'id': 35463})
         charge.activate()
+
+    def test_current_method_returns_active_charge(self):
+        # Test that current() class method correctly returns
+        # first RecurringApplicationCharge with active status
+        self.fake("recurring_application_charges")
+        charge = shopify.RecurringApplicationCharge.current()
+        self.assertEqual(charge.id, 455696195)
+
+    def test_current_method_returns_none_if_active_not_found(self):
+        # Test that current() class method correctly returns
+        # None if RecurringApplicationCharge with active status not found
+        self.fake("recurring_application_charges", body=self.load_fixture("recurring_application_charges_no_active"))
+        charge = shopify.RecurringApplicationCharge.current()
+        self.assertEqual(charge, None)
diff --git a/test/session_test.py b/test/session_test.py
index c85eb4b..c8c1643 100644
--- a/test/session_test.py
+++ b/test/session_test.py
@@ -113,54 +113,24 @@ class SessionTest(TestCase):
         session = shopify.Session("testshop.myshopify.com", "any-token")
         self.assertEqual("https://testshop.myshopify.com/admin", session.site)
 
-    def test_hmac_calculation(self):
-        # Test using the secret and parameter examples given in the Shopify API documentation.
-        shopify.Session.secret='hush'
-        params = {
-          'shop': 'some-shop.myshopify.com',
-          'code': 'a94a110d86d2452eb3e2af4cfb8a3828',
-          'timestamp': '1337178173',
-          'signature': '6e39a2ea9e497af6cb806720da1f1bf3',
-          'hmac': '2cb1a277650a659f1b11e92a4a64275b128e037f2c3390e3c8fd2d8721dac9e2',
-        }
-        self.assertEqual(shopify.Session.calculate_hmac(params), params['hmac'])
-
-    def test_return_token_if_hmac_is_valid(self):
+    def test_return_token_if_signature_is_valid(self):
         shopify.Session.secret='secret'
         params = {'code': 'any-code', 'timestamp': time.time()}
-        hmac = shopify.Session.calculate_hmac(params)
-        params['hmac'] = hmac
+        sorted_params = self.make_sorted_params(params)
+        signature = md5((shopify.Session.secret + sorted_params).encode('utf-8')).hexdigest()
+        params['signature'] = signature
 
         self.fake(None, url='https://localhost.myshopify.com/admin/oauth/access_token', method='POST', body='{"access_token" : "token"}', has_user_agent=False)
         session = shopify.Session('http://localhost.myshopify.com')
         token = session.request_token(params)
         self.assertEqual("token", token)
 
-    def test_return_token_if_hmac_is_valid_but_signature_also_provided(self):
-        shopify.Session.secret='secret'
-        params = {'code': 'any-code', 'timestamp': time.time(), 'signature': '6e39a2'}
-        hmac = shopify.Session.calculate_hmac(params)
-        params['hmac'] = hmac
-
-        self.fake(None, url='https://localhost.myshopify.com/admin/oauth/access_token', method='POST', body='{"access_token" : "token"}', has_user_agent=False)
-        session = shopify.Session('http://localhost.myshopify.com')
-        token = session.request_token(params)
-        self.assertEqual("token", token)
-
-    def test_raise_error_if_hmac_is_invalid(self):
-        shopify.Session.secret='secret'
-        params = {'code': 'any-code', 'timestamp': time.time()}
-        params['hmac'] = 'a94a110d86d2452e92a4a64275b128e9273be3037f2c339eb3e2af4cfb8a3828'
-
-        with self.assertRaises(shopify.ValidationException):
-            session = shopify.Session('http://localhost.myshopify.com')
-            session = session.request_token(params)
-
-    def test_raise_error_if_hmac_does_not_match_expected(self):
+    def test_raise_error_if_signature_does_not_match_expected(self):
         shopify.Session.secret='secret'
         params = {'foo': 'hello', 'timestamp': time.time()}
-        hmac = shopify.Session.calculate_hmac(params)
-        params['hmac'] = hmac
+        sorted_params = self.make_sorted_params(params)
+        signature = md5((shopify.Session.secret + sorted_params).encode('utf-8')).hexdigest()
+        params['signature'] = signature
         params['bar'] = 'world'
         params['code'] = 'code'
 
@@ -172,13 +142,22 @@ class SessionTest(TestCase):
         shopify.Session.secret='secret'
         one_day = 24 * 60 * 60
         params = {'code': 'any-code', 'timestamp': time.time()-(2*one_day)}
-        hmac = shopify.Session.calculate_hmac(params)
-        params['hmac'] = hmac
+        sorted_params = self.make_sorted_params(params)
+        signature = md5((shopify.Session.secret + sorted_params).encode('utf-8')).hexdigest()
+        params['signature'] = signature
 
         with self.assertRaises(shopify.ValidationException):
             session = shopify.Session('http://localhost.myshopify.com')
             session = session.request_token(params)
 
+
+    def make_sorted_params(self, params):
+        sorted_params = ""
+        for k in sorted(params.keys()):
+            if k != "signature":
+                sorted_params += k + "=" + str(params[k])
+        return sorted_params
+
     def normalize_url(self, url):
         scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
         query = "&".join(sorted(query.split("&")))