Last active
June 28, 2018 15:58
-
-
Save anpr/38b19090cb2299658a7d9ad1714e4977 to your computer and use it in GitHub Desktop.
Thumbor: Url Signer with Expiration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# thumbor-side code, added to url signers of thumbor | |
from datetime import datetime | |
import base64 | |
import hashlib | |
import hmac | |
from thumbor.url_signers import BaseUrlSigner | |
try: | |
unicode # Python 2 | |
except NameError: | |
unicode = str # Python 3 | |
class UrlSigner(BaseUrlSigner): | |
"""Expects a signature of the form <hmac>:<expiry>, where | |
- hmac is a urlsafe base64 encoded HMAC using SHA256. | |
- expiry encodes the time in UTC until the url is valid, in the format YYYYMMDDHHMMSS. | |
Example value of a signature: 8khP4iEZhka4aIHfMZD4Ic8SqE-dLHIp7_eXroyfp4M=:20180626000000 | |
""" | |
def validate(self, hmac_and_expiry, quoted_path): | |
given_signature, expiry_string = hmac_and_expiry.split(':') | |
if self._check_expiry(expiry_string): | |
url_signature = self.signature(expiry_string, quoted_path) | |
return url_signature == given_signature | |
else: | |
return False | |
def _check_expiry(self, expiry_string): | |
try: | |
expiry = datetime.strptime(expiry_string, '%Y%m%d%H%M%S') | |
return datetime.utcnow() < expiry | |
except ValueError: | |
return False | |
def signature(self, expiry_string, quoted_path): | |
key = unicode(self.security_key).encode('utf-8') | |
msg = unicode(expiry_string + quoted_path).encode('utf-8') | |
return base64.urlsafe_b64encode(hmac.new(key, msg, hashlib.sha256).digest()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Client-side code creating the URL to thumbor | |
from datetime import datetime, timedelta | |
import base64 | |
import hashlib | |
import hmac | |
from urllib.parse import ParseResult, quote | |
from django.conf import settings | |
def thumbor_url(thumbor_path: str, | |
s3_path: str, | |
thumbor_endpoint: ParseResult = None) -> str: | |
"""Generates URL to thumbor, which retrieves a thumbnail | |
Args: | |
thumbor_path: path that comes after the signature to thumbor, without the media URL. | |
Basically encodes the transformation to be applied to the media_url. | |
See http://thumbor.readthedocs.io/en/latest/usage.html | |
Example: 100x0 | |
s3_path: <bucket name> + '/' + <object key>; identifies the media file to be transformed. | |
thumbor_endpoint: URL to thumbor API. Default: settings.THUMBOR_PUBLIC_ENDPOINT | |
""" | |
if thumbor_endpoint is None: | |
thumbor_endpoint = settings.THUMBOR_PUBLIC_ENDPOINT | |
# Thumbor "normalizes" the url in the following way, | |
# cf https://github.com/thumbor/thumbor/blob/master/thumbor/loaders/http_loader.py#L25 | |
# (also used by S3 loader). | |
# That's why we need to do the same before calculating the signature. Moreover, | |
# we transform the path here because e.g. an object key with an '&' in it isn't interpreted | |
# as query parameter to thumbor then. | |
quoted_path = quote(thumbor_path + '/' + s3_path, safe='~@#$&()*!+=:;,.?/\'') | |
signature = _thumbor_signature(quoted_path) | |
return thumbor_endpoint.geturl() + '/' + signature + '/' + quoted_path # type: ignore | |
def _thumbor_signature(quoted_path): | |
key = settings.THUMBOR_SECURITY_KEY.encode('utf-8') | |
expiry_string = _end_of_today().strftime('%Y%m%d%H%M%S') | |
msg = (expiry_string + quoted_path).encode('utf-8') | |
hmac_urlsafe = base64.urlsafe_b64encode(hmac.new(key, msg, hashlib.sha256).digest()).decode('utf-8') | |
return hmac_urlsafe + ':' + expiry_string | |
def _end_of_today(): | |
return datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment