Skip to content

Instantly share code, notes, and snippets.

@anpr
Last active June 28, 2018 15:58
Show Gist options
  • Save anpr/38b19090cb2299658a7d9ad1714e4977 to your computer and use it in GitHub Desktop.
Save anpr/38b19090cb2299658a7d9ad1714e4977 to your computer and use it in GitHub Desktop.
Thumbor: Url Signer with Expiration
# -*- 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())
# 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