Skip to content
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

Fix Signature Authentication #27

Merged
merged 8 commits into from
Mar 26, 2024
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
### Unreleased
### 0.2.2 / 2023-27-03 ###
- Added `sha_384` as hash algorithm for the signature authentication.
- Drop Python 3.6 from CI. It has been unsupported since December 2021 and github actions runner don't support anymore (https://github.com/actions/setup-python/issues/544)

### 0.2.1/ 2022-29-08 ###
Expand Down
7 changes: 7 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import datetime


def request_body_matcher(pattern):
def _callback(request):
return pattern in request.text

return _callback


def get_test_time():
return datetime.datetime.fromisoformat("2024-04-04T04:44:44")
73 changes: 73 additions & 0 deletions tests/test_signature_authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import copy
import json
import unittest
import requests_mock
from datetime import timedelta
from transloadit.client import Transloadit
from transloadit.request import Request
from . import get_test_time, request_body_matcher


class TestSignatureAuthentication(unittest.TestCase):
def setUp(self):
self.mock_client = MockClient("TRANSLOADIT_KEY", "TRANSLOADIT_SECRET")
self.json_response = (
'{"ok": "ASSEMBLY_COMPLETED", "assembly_id": "abcdef45673"}'
)

@requests_mock.Mocker()
def test_fixed_signature(self, mock):
# Test a request with a fixed timestamp in order to have reproducible results
assembly = self.mock_client.new_assembly()
assembly.add_step("import", "/http/import",
options={"url": "https://demos.transloadit.com/inputs/chameleon.jpg"})
assembly.add_step("resize", "/image/resize", {"use:": "import", "width": 70, "height": 70})

url = f"{self.mock_client.service}/assemblies"
mock.post(
url,
text=self.json_response,
additional_matcher=request_body_matcher(
"signature=sha384"
"%3A46943b5542af95679940d94507865b20b36cb1808a7a9dc64c6255f26d1e921bd6574443b80b0dcd595769268f74273c"),
)

assembly.create(resumable=False)


class MockClient(Transloadit):
"""
Mock Class of the Transloadit Clients, which loads the modified MockRequest Class
instead of the Standard Request class.
"""

def __init__(self,
auth_key: str,
auth_secret: str,
service: str = "https://api2.transloadit.com",
duration: int = 300):
if not service.startswith(("http://", "https://")):
service = "https://" + service

self.service = service
self.auth_key = auth_key
self.auth_secret = auth_secret
self.duration = duration
self.request = MockRequest(self)


class MockRequest(Request):
"""
Mock Request Class, which uses a fixed datetime for generating the signature.
This is for having a reproducible value to test against.
"""
def _to_payload(self, data):
data = copy.deepcopy(data or {})
expiry = timedelta(seconds=self.transloadit.duration) + get_test_time()
data["auth"] = {
"key": self.transloadit.auth_key,
"expires": expiry.strftime("%Y/%m/%d %H:%M:%S+00:00"),
}
json_data = json.dumps(data)

return {"params": json, "signature": self._sign_data(json_data)}
11 changes: 6 additions & 5 deletions transloadit/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
if typing.TYPE_CHECKING:
from requests import Response


class Transloadit:
"""
This class serves as a client interface to the Transloadit API.
Expand All @@ -30,11 +31,11 @@ class Transloadit:
"""

def __init__(
self,
auth_key: str,
auth_secret: str,
service: str = "https://api2.transloadit.com",
duration: int = 300,
self,
auth_key: str,
auth_secret: str,
service: str = "https://api2.transloadit.com",
duration: int = 300,
):
if not service.startswith(("http://", "https://")):
service = "https://" + service
Expand Down
5 changes: 2 additions & 3 deletions transloadit/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,8 @@ def _to_payload(self, data):
return {"params": json_data, "signature": self._sign_data(json_data)}

def _sign_data(self, message):
return hmac.new(
b(self.transloadit.auth_secret), message.encode("utf-8"), hashlib.sha1
).hexdigest()
hash_string = hmac.new(b(self.transloadit.auth_secret), message.encode("utf-8"), hashlib.sha384).hexdigest()
return f"sha384:{hash_string}"

def _get_full_url(self, url):
if url.startswith(("http://", "https://")):
Expand Down
Loading