"""Signed URLs Utility.""" import datetime import json from botocore.signers import CloudFrontSigner from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding from podcast import config from podcast.constants.asset_types import DEFAULT_SIGNED_URL_DURATION from podcast.utils.exc import OwsError key = None def _get_private_key(): global key if not key: key = serialization.load_pem_private_key( config.CLOUDFRONT_PRIVATE_KEY, password=None, backend=default_backend() ) return key def _rsa_signer(message): signing_key = _get_private_key() return signing_key.sign(message, padding.PKCS1v15(), hashes.SHA1()) def _sign_url(url, signer, policy): policy['Statement'][0]['Resource'] = url policy_json = json.dumps(policy, separators=(',', ':')) return signer.generate_presigned_url(url, policy=policy_json) def _get_policy_object(duration): expire_date = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=duration) expires_at = int(expire_date.timestamp()) condition = { 'DateLessThan': { 'AWS:EpochTime': expires_at } } return { 'Statement': [ { 'Resource': None, 'Condition': condition } ] } def sign_url(url, duration=DEFAULT_SIGNED_URL_DURATION): """Generate a signed url for ad read PDF or episode audio wav file. Args: url (str): URL to sign Returns: datetime.datetime: converted object """ if not config.CLOUDFRONT_PRIVATE_KEY: raise OwsError.internal_server_error('Cloudfront private key is not set') key_id = config.CLOUDFRONT_KEY_PAIR_ID if not key_id: raise OwsError.internal_server_error('Cloudfront key id not set') signing_policy = _get_policy_object(duration) cloudfront_signer = CloudFrontSigner(key_id, _rsa_signer) return _sign_url(url, cloudfront_signer, signing_policy)