def test_thumbor_can_decrypt_lib_thumbor_generated_url(): key = "my-security-key" image = "s.glbimg.com/et/bb/f/original/2011/03/24/VN0JiwzmOw0b0lg.jpg" thumbor_crypto = Cryptor(key) crypto = CryptoURL(key=key) url = crypto.generate( width=300, height=200, smart=True, image_url=image, old=True ) reg = "/([^/]+)/(.+)" options = re.match(reg, url).groups()[0] decrypted_url = thumbor_crypto.decrypt(options) assert decrypted_url assert decrypted_url['height'] == 200 assert decrypted_url['width'] == 300 assert decrypted_url['smart'] assert decrypted_url['image_hash'] == hashlib.md5(image).hexdigest()
def test_usage(): key = "my-security-key" image = "s.glbimg.com/et/bb/f/original/2011/03/24/VN0JiwzmOw0b0lg.jpg" thumbor_crypto = Cryptor(key) thumbor_options = thumbor_crypto.encrypt(width=300, height=200, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=False, flip_vertical=False, halign='center', valign='middle', trim=None, crop_left=0, crop_top=0, crop_right=0, crop_bottom=0, filters=[], image=image) thumbor_url = "/%s/%s" % (thumbor_options, image) crypto = CryptoURL(key=key) url = crypto.generate(width=300, height=200, smart=True, image_url=image, old=True) assert url == thumbor_url
def test_decrypting_combinations(): cryptor = Cryptor('my-security-key') for test in DECRYPT_TESTS: base_copy = copy.copy(BASE_PARAMS) base_copy.update(test['params']) encrypted = cryptor.encrypt(**base_copy) yield decrypted_result_should_match, cryptor.decrypt(encrypted), test['result']
def topic(self): cryptor = Cryptor('my-security-key') for test in DECRYPT_TESTS: base_copy = copy.copy(BASE_PARAMS) base_copy.update(test['params']) encrypted = cryptor.encrypt(**base_copy) yield (cryptor.decrypt(encrypted), test['result'])
def topic(self): cryptor = Cryptor('my-security-key') for test in DECRYPT_TESTS: base_copy = copy.copy(BASE_PARAMS) base_copy.update(test['params']) encrypted = cryptor.encrypt(**base_copy) yield(cryptor.decrypt(encrypted), test['result'])
def get(self, **kw): url = self.request.uri if not self.validate(kw["image"]): self._error(404, "No original image was specified in the given URL") return self.context.request = RequestParameters(**kw) self.context.request.unsafe = self.context.request.unsafe == "unsafe" if self.request.query: self.context.request.image_url += "?%s" % self.request.query self.context.request.image_url = quote(self.context.request.image_url, "/:?%=&") has_none = not self.context.request.unsafe and not self.context.request.hash has_both = self.context.request.unsafe and self.context.request.hash if has_none or has_both: self._error(404, "URL does not have hash or unsafe, or has both: %s" % url) return if self.context.request.unsafe and not self.context.config.ALLOW_UNSAFE_URL: self._error(404, "URL has unsafe but unsafe is not allowed by the config: %s" % url) return url_signature = self.context.request.hash if url_signature: signer = Signer(self.context.server.security_key) url_to_validate = url.replace("/%s/" % self.context.request.hash, "") valid = signer.validate(url_signature, url_to_validate) if not valid and self.context.config.STORES_CRYPTO_KEY_FOR_EACH_IMAGE: # Retrieves security key for this image if it has been seen before security_key = self.context.modules.storage.get_crypto(self.context.request.image_url) if security_key is not None: signer = Signer(security_key) valid = signer.validate(url_signature, url_to_validate) if not valid: is_valid = True if self.context.config.ALLOW_OLD_URLS: cr = Cryptor(self.context.server.security_key) options = cr.get_options(self.context.request.hash, self.context.request.image_url) if options is None: is_valid = False else: self.context.request = RequestParameters(**options) logger.warning( "OLD FORMAT URL DETECTED!!! This format of URL will be discontinued in upcoming versions. Please start using the new format as soon as possible. More info at https://github.com/globocom/thumbor/wiki/3.0.0-release-changes" ) else: is_valid = False if not is_valid: self._error(404, "Malformed URL: %s" % url) return return self.execute_image_operations()
def test_usage(): key = "my-security-key" image = "s.glbimg.com/et/bb/f/original/2011/03/24/VN0JiwzmOw0b0lg.jpg" thumbor_crypto = Cryptor(key) thumbor_options = thumbor_crypto.encrypt( width=300, height=200, smart=True, adaptive=False, fit_in=False, flip_horizontal=False, flip_vertical=False, halign='center', valign='middle', crop_left=0, crop_top=0, crop_right=0, crop_bottom=0, filters=[], image=image ) thumbor_url = "/%s/%s" % (thumbor_options, image) crypto = CryptoURL(key=key) url = crypto.generate( width=300, height=200, smart=True, image_url=image, old=True ) assert url == thumbor_url
def test_decrypting_with_wrong_key(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" cryptor = Cryptor(security_key="simething") wrong = cryptor.decrypt(encrypted_str) right = self.cryptor.decrypt(encrypted_str) expect(wrong['image_hash']).not_to_equal(str(right['image_hash']))
def test_decrypting_combinations(): cryptor = Cryptor('my-security-key') for test in DECRYPT_TESTS: base_copy = copy.copy(BASE_PARAMS) base_copy.update(test['params']) encrypted = cryptor.encrypt(**base_copy) yield decrypted_result_should_match, cryptor.decrypt( encrypted), test['result']
def test_get_options_from_storage(self): image_url = "/some/image.jpg" custom_security_key = "custom-sec" cryptor = Cryptor(security_key=custom_security_key) decryptor = Cryptor(security_key="something") expected_options = dict( width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image=image_url, ) encrypted_str = cryptor.encrypt(**expected_options) mock_storage = mock.Mock() decryptor.context = mock.Mock( config=mock.Mock( STORES_CRYPTO_KEY_FOR_EACH_IMAGE=True ), modules=mock.Mock( storage=mock_storage ), ) mock_storage.get_crypto.return_value = custom_security_key options = decryptor.get_options(encrypted_str, image_url) expect(options).not_to_be_null() expected_options = { 'trim': 'trim', 'full': False, 'halign': 'center', 'fit_in': False, 'vertical_flip': True, 'image': '/some/image.jpg', 'crop': {'top': 11, 'right': 12, 'bottom': 13, 'left': 10}, 'height': 300, 'width': 300, 'meta': False, 'horizontal_flip': True, 'filters': 'some_filter()', 'valign': 'middle', 'debug': False, 'hash': 'e2baf424fa420b73a97476956dfb858f', 'adaptive': False, 'smart': True } expect(options).to_be_like(expected_options)
def setUp(self, *args, **kw): super(CryptoTestCase, self).setUp(*args, **kw) self.cryptor = Cryptor(security_key="something") self.cryptor.context = mock.Mock( config=mock.Mock( STORES_CRYPTO_KEY_FOR_EACH_IMAGE=False ), )
def test_get_options_from_storage_returns_null_if_key_not_found(self): image_url = "/some/image.jpg" custom_security_key = "custom-sec" cryptor = Cryptor(security_key=custom_security_key) decryptor = Cryptor(security_key="something") expected_options = dict( width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image=image_url, ) encrypted_str = cryptor.encrypt(**expected_options) mock_storage = mock.Mock() decryptor.context = mock.Mock( config=mock.Mock( STORES_CRYPTO_KEY_FOR_EACH_IMAGE=True ), modules=mock.Mock( storage=mock_storage ), ) mock_storage.get_crypto.return_value = None options = decryptor.get_options(encrypted_str, image_url) expect(options).to_be_null()
def test_thumbor_can_decrypt_lib_thumbor_generated_url(): key = "my-security-key" image = "s.glbimg.com/et/bb/f/original/2011/03/24/VN0JiwzmOw0b0lg.jpg" thumbor_crypto = Cryptor(key) crypto = CryptoURL(key=key) url = crypto.generate(width=300, height=200, smart=True, image_url=image, old=True) reg = "/([^/]+)/(.+)" options = re.match(reg, url).groups()[0] decrypted_url = thumbor_crypto.decrypt(options) assert decrypted_url assert decrypted_url['height'] == 200 assert decrypted_url['width'] == 300 assert decrypted_url['smart'] assert decrypted_url['image_hash'] == hashlib.md5(image).hexdigest()
def test_get_options_from_storage_returns_null_if_key_not_found(self): image_url = "/some/image.jpg" custom_security_key = "custom-sec" cryptor = Cryptor(security_key=custom_security_key) decryptor = Cryptor(security_key="something") expected_options = dict( width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image=image_url, ) encrypted_str = cryptor.encrypt(**expected_options) mock_storage = mock.Mock() decryptor.context = mock.Mock( config=mock.Mock(STORES_CRYPTO_KEY_FOR_EACH_IMAGE=True), modules=mock.Mock(storage=mock_storage), ) mock_storage.get_crypto.return_value = None options = decryptor.get_options(encrypted_str, image_url) expect(options).to_be_null()
def get(self, **kw): url = self.request.uri if not self.validate(kw['image']): self._error(404, 'No original image was specified in the given URL') return self.context.request = RequestParameters(**kw) self.context.request.unsafe = self.context.request.unsafe == 'unsafe' if (self.request.query): self.context.request.image_url += '?%s' % self.request.query self.context.request.image_url = self.encode_url( self.context.request.image_url.encode('utf-8')) has_none = not self.context.request.unsafe and not self.context.request.hash has_both = self.context.request.unsafe and self.context.request.hash if has_none or has_both: self._error( 404, 'URL does not have hash or unsafe, or has both: %s' % url) return if self.context.request.unsafe and not self.context.config.ALLOW_UNSAFE_URL: self._error( 404, 'URL has unsafe but unsafe is not allowed by the config: %s' % url) return url_signature = self.context.request.hash if url_signature: signer = Signer(self.context.server.security_key) url_to_validate = self.encode_url(url).replace( '/%s/' % self.context.request.hash, '') valid = signer.validate(url_signature, url_to_validate) if not valid and self.context.config.STORES_CRYPTO_KEY_FOR_EACH_IMAGE: # Retrieves security key for this image if it has been seen before security_key = self.context.modules.storage.get_crypto( self.context.request.image_url) if security_key is not None: signer = Signer(security_key) valid = signer.validate(url_signature, url_to_validate) if not valid: is_valid = True if self.context.config.ALLOW_OLD_URLS: cr = Cryptor(self.context.server.security_key) options = cr.get_options(self.context.request.hash, self.context.request.image_url) if options is None: is_valid = False else: self.context.request = RequestParameters(**options) logger.warning( 'OLD FORMAT URL DETECTED!!! This format of URL will be discontinued in upcoming versions. Please start using the new format as soon as possible. More info at https://github.com/globocom/thumbor/wiki/3.0.0-release-changes' ) else: is_valid = False if not is_valid: self._error(404, 'Malformed URL: %s' % url) return return self.execute_image_operations()
def topic(self, encrypted, crypto): crypto2 = Cryptor(security_key="simething") return (crypto2.decrypt(encrypted), crypto.decrypt(encrypted))
def main(arguments=None): '''Converts a given url with the specified arguments.''' if arguments is None: arguments = sys.argv[1:] parser = optparse.OptionParser(usage='thumbor-url [options] imageurl or type thumbor-url -h (--help) for help', description=__doc__, version=__version__) parser.add_option('-l', '--key_file', dest='key_file', default=None, help = 'The file to read the security key from [default: %default].' ) parser.add_option('-k', '--key', dest='key', default=None, help = 'The security key to encrypt the url with [default: %default].' ) parser.add_option('-w', '--width', dest='width', type='int', default=0, help = 'The target width for the image [default: %default].' ) parser.add_option('-e', '--height', dest='height', type='int', default=0, help = 'The target height for the image [default: %default].' ) parser.add_option('-n', '--fitin', dest='fitin', action='store_true', default=False, help = 'Indicates that fit-in resizing should be performed.' ) parser.add_option('-m', '--meta', dest='meta', action='store_true', default=False, help = 'Indicates that meta information should be retrieved.' ) parser.add_option('', '--adaptive', action='store_true', dest='adaptive', default=False, help = 'Indicates that adaptive fit-in cropping should be used.' ) parser.add_option('-s', '--smart', action='store_true', dest='smart', default=False, help = 'Indicates that smart cropping should be used.' ) parser.add_option('-t', '--trim', action='store_true', default=False, help='Indicate that surrounding whitespace should be trimmed.') parser.add_option('-f', '--horizontal-flip', action='store_true', dest='horizontal_flip', default=False, help = 'Indicates that the image should be horizontally flipped.' ) parser.add_option('-v', '--vertical-flip', action='store_true', dest='vertical_flip', default=False, help = 'Indicates that the image should be vertically flipped.') parser.add_option('-a', '--halign', dest='halign', default='center', help = 'The horizontal alignment to use for cropping [default: %default].' ) parser.add_option('-i', '--valign', dest='valign', default='middle', help = 'The vertical alignment to use for cropping [default: %default].' ) parser.add_option('', '--filters', dest='filters', default='', help = 'Filters to be applied to the image, e.g. brightness(10) [default: %default].' ) parser.add_option('-o', '--old-format', dest='old', action='store_true', default=False, help = 'Indicates that thumbor should generate old-format urls [default: %default].' ) parser.add_option('-c', '--crop', dest='crop', default=None, help = 'The coordinates of the points to manual cropping in the format leftxtop:rightxbottom (100x200:400x500) [default: %default].' ) (parsed_options, arguments) = parser.parse_args(arguments) if not arguments: print 'Error: The image argument is mandatory. For more information type thumbor-url -h' return image_url = arguments[0] if image_url.startswith('/'): image_url = image_url[1:] try: config = Config.load(None) except: config = None if config: print print "USING CONFIGURATION FILE AT %s" % config.config_file print if not parsed_options.key and not config: print 'Error: The -k or --key argument is mandatory. For more information type thumbor-url -h' return if parsed_options.key_file: f = open(parsed_options.key_file) security_key = f.read().strip() f.close() else: security_key = config.SECURITY_KEY if not parsed_options.key else parsed_options.key crop_left = crop_top = crop_right = crop_bottom = 0 if parsed_options.crop: crops = parsed_options.crop.split(':') crop_left, crop_top = crops[0].split('x') crop_right, crop_bottom = crops[1].split('x') if parsed_options.old: crypt = Cryptor(security_key) opt = crypt.encrypt(parsed_options.width, parsed_options.height, parsed_options.smart, parsed_options.adaptive, parsed_options.fitin, parsed_options.horizontal_flip, parsed_options.vertical_flip, parsed_options.halign, parsed_options.valign, crop_left, crop_top, crop_right, crop_bottom, parsed_options.filters, image_url) url = '/%s/%s' % (opt, image_url) print 'Encrypted URL:' else: signer = Signer(security_key) url = Url.generate_options( width=parsed_options.width, height=parsed_options.height, smart=parsed_options.smart, meta=parsed_options.meta, adaptive=parsed_options.adaptive, fit_in=parsed_options.fitin, horizontal_flip=parsed_options.horizontal_flip, vertical_flip=parsed_options.vertical_flip, halign=parsed_options.halign, valign=parsed_options.valign, trim=parsed_options.trim, crop_left=crop_left, crop_top=crop_top, crop_right=crop_right, crop_bottom=crop_bottom, filters=parsed_options.filters ) url = '%s/%s' % (url, image_url) url = url.lstrip('/') signature = signer.signature(url) url = '/%s/%s' % (signature, url) print 'Signed URL:' print url
def topic(self): return Cryptor(security_key="something")
def test_can_create_crypto_with_key(self): cryptor = Cryptor("key") expect(cryptor).not_to_be_null() expect(cryptor.security_key).to_equal("keykeykeykeykeyk")
class CryptoTestCase(TestCase): def setUp(self, *args, **kw): super(CryptoTestCase, self).setUp(*args, **kw) self.cryptor = Cryptor(security_key="something") self.cryptor.context = mock.Mock( config=mock.Mock( STORES_CRYPTO_KEY_FOR_EACH_IMAGE=False ), ) def test_can_create_crypto_with_key(self): cryptor = Cryptor("key") expect(cryptor).not_to_be_null() expect(cryptor.security_key).to_equal("keykeykeykeykeyk") def test_can_encrypt(self): encrypted = self.cryptor.encrypt( width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image="/some/image.jpg" ) encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" expect(encrypted).to_equal(encrypted_str) def test_can_decrypt(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" decrypted = self.cryptor.decrypt(encrypted_str) expect(decrypted).not_to_be_null() expect(decrypted).not_to_be_an_error() expect(decrypted).not_to_be_empty() expect(decrypted['width']).to_equal(300) expect(decrypted['height']).to_equal(300) expect(decrypted['smart']).to_be_true() expect(decrypted['fit_in']).to_be_false() expect(decrypted['horizontal_flip']).to_be_true() expect(decrypted['vertical_flip']).to_be_true() expect(decrypted['halign']).to_equal('center') expect(decrypted['valign']).to_equal('middle') expect(decrypted['crop']['left']).to_equal(10) expect(decrypted['crop']['top']).to_equal(11) expect(decrypted['crop']['right']).to_equal(12) expect(decrypted['crop']['bottom']).to_equal(13) expect(decrypted['filters']).to_equal('some_filter()') image_hash = hashlib.md5('/some/image.jpg').hexdigest() expect(decrypted['image_hash']).to_equal(image_hash) def test_decrypting_with_wrong_key(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" cryptor = Cryptor(security_key="simething") wrong = cryptor.decrypt(encrypted_str) right = self.cryptor.decrypt(encrypted_str) expect(wrong['image_hash']).not_to_equal(str(right['image_hash'])) def test_get_options(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" image_url = "/some/image.jpg" expected_options = { 'trim': 'trim', 'full': False, 'halign': 'center', 'fit_in': False, 'vertical_flip': True, 'image': '/some/image.jpg', 'crop': {'top': 11, 'right': 12, 'bottom': 13, 'left': 10}, 'height': 300, 'width': 300, 'meta': False, 'horizontal_flip': True, 'filters': 'some_filter()', 'valign': 'middle', 'debug': False, 'hash': 'e2baf424fa420b73a97476956dfb858f', 'adaptive': False, 'smart': True } options = self.cryptor.get_options(encrypted_str, image_url) expect(options).to_be_like(expected_options) def test_get_options_returns_null_if_invalid(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" image_url = "/some/image.jpg" options = self.cryptor.get_options(encrypted_str, image_url) expect(options).to_be_null() def test_get_options_returns_null_if_different_path(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" image_url = "/some/image2.jpg" options = self.cryptor.get_options(encrypted_str, image_url) expect(options).to_be_null() def test_get_options_from_storage(self): image_url = "/some/image.jpg" custom_security_key = "custom-sec" cryptor = Cryptor(security_key=custom_security_key) decryptor = Cryptor(security_key="something") expected_options = dict( width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image=image_url, ) encrypted_str = cryptor.encrypt(**expected_options) mock_storage = mock.Mock() decryptor.context = mock.Mock( config=mock.Mock( STORES_CRYPTO_KEY_FOR_EACH_IMAGE=True ), modules=mock.Mock( storage=mock_storage ), ) mock_storage.get_crypto.return_value = custom_security_key options = decryptor.get_options(encrypted_str, image_url) expect(options).not_to_be_null() expected_options = { 'trim': 'trim', 'full': False, 'halign': 'center', 'fit_in': False, 'vertical_flip': True, 'image': '/some/image.jpg', 'crop': {'top': 11, 'right': 12, 'bottom': 13, 'left': 10}, 'height': 300, 'width': 300, 'meta': False, 'horizontal_flip': True, 'filters': 'some_filter()', 'valign': 'middle', 'debug': False, 'hash': 'e2baf424fa420b73a97476956dfb858f', 'adaptive': False, 'smart': True } expect(options).to_be_like(expected_options) def test_get_options_from_storage_returns_null_if_key_not_found(self): image_url = "/some/image.jpg" custom_security_key = "custom-sec" cryptor = Cryptor(security_key=custom_security_key) decryptor = Cryptor(security_key="something") expected_options = dict( width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image=image_url, ) encrypted_str = cryptor.encrypt(**expected_options) mock_storage = mock.Mock() decryptor.context = mock.Mock( config=mock.Mock( STORES_CRYPTO_KEY_FOR_EACH_IMAGE=True ), modules=mock.Mock( storage=mock_storage ), ) mock_storage.get_crypto.return_value = None options = decryptor.get_options(encrypted_str, image_url) expect(options).to_be_null()
def setUp(self, *args, **kw): super(CryptoTestCase, self).setUp(*args, **kw) self.cryptor = Cryptor(security_key="something") self.cryptor.context = mock.Mock( config=mock.Mock(STORES_CRYPTO_KEY_FOR_EACH_IMAGE=False), )
class CryptoTestCase(TestCase): def setUp(self, *args, **kw): super(CryptoTestCase, self).setUp(*args, **kw) self.cryptor = Cryptor(security_key="something") self.cryptor.context = mock.Mock( config=mock.Mock(STORES_CRYPTO_KEY_FOR_EACH_IMAGE=False), ) def test_can_create_crypto_with_key(self): cryptor = Cryptor("key") expect(cryptor).not_to_be_null() expect(cryptor.security_key).to_equal("keykeykeykeykeyk") def test_can_encrypt(self): encrypted = self.cryptor.encrypt(width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image="/some/image.jpg") encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" expect(encrypted).to_equal(encrypted_str) def test_can_decrypt(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" decrypted = self.cryptor.decrypt(encrypted_str) expect(decrypted).not_to_be_null() expect(decrypted).not_to_be_an_error() expect(decrypted).not_to_be_empty() expect(decrypted['width']).to_equal(300) expect(decrypted['height']).to_equal(300) expect(decrypted['smart']).to_be_true() expect(decrypted['fit_in']).to_be_false() expect(decrypted['horizontal_flip']).to_be_true() expect(decrypted['vertical_flip']).to_be_true() expect(decrypted['halign']).to_equal('center') expect(decrypted['valign']).to_equal('middle') expect(decrypted['crop']['left']).to_equal(10) expect(decrypted['crop']['top']).to_equal(11) expect(decrypted['crop']['right']).to_equal(12) expect(decrypted['crop']['bottom']).to_equal(13) expect(decrypted['filters']).to_equal('some_filter()') image_hash = hashlib.md5('/some/image.jpg').hexdigest() expect(decrypted['image_hash']).to_equal(image_hash) def test_decrypting_with_wrong_key(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" cryptor = Cryptor(security_key="simething") wrong = cryptor.decrypt(encrypted_str) right = self.cryptor.decrypt(encrypted_str) expect(wrong['image_hash']).not_to_equal(str(right['image_hash'])) def test_get_options(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" image_url = "/some/image.jpg" expected_options = { 'trim': 'trim', 'full': False, 'halign': 'center', 'fit_in': False, 'vertical_flip': True, 'image': '/some/image.jpg', 'crop': { 'top': 11, 'right': 12, 'bottom': 13, 'left': 10 }, 'height': 300, 'width': 300, 'meta': False, 'horizontal_flip': True, 'filters': 'some_filter()', 'valign': 'middle', 'debug': False, 'hash': 'e2baf424fa420b73a97476956dfb858f', 'adaptive': False, 'smart': True } options = self.cryptor.get_options(encrypted_str, image_url) expect(options).to_be_like(expected_options) def test_get_options_returns_null_if_invalid(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" image_url = "/some/image.jpg" options = self.cryptor.get_options(encrypted_str, image_url) expect(options).to_be_null() def test_get_options_returns_null_if_different_path(self): encrypted_str = "hELdyDzyYtjXU5GhGxJVHjRvGrSP_iYKnIQbq_MuVq86rSObCeJvo2iXFRUjLgs" \ "U9wDzhqK9J_SHmpxDJHW_rBD8eilO26x2M_hzJfGB-V9cGF65GO_7CgJXI8Ktw188" image_url = "/some/image2.jpg" options = self.cryptor.get_options(encrypted_str, image_url) expect(options).to_be_null() def test_get_options_from_storage(self): image_url = "/some/image.jpg" custom_security_key = "custom-sec" cryptor = Cryptor(security_key=custom_security_key) decryptor = Cryptor(security_key="something") expected_options = dict( width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image=image_url, ) encrypted_str = cryptor.encrypt(**expected_options) mock_storage = mock.Mock() decryptor.context = mock.Mock( config=mock.Mock(STORES_CRYPTO_KEY_FOR_EACH_IMAGE=True), modules=mock.Mock(storage=mock_storage), ) mock_storage.get_crypto.return_value = custom_security_key options = decryptor.get_options(encrypted_str, image_url) expect(options).not_to_be_null() expected_options = { 'trim': 'trim', 'full': False, 'halign': 'center', 'fit_in': False, 'vertical_flip': True, 'image': '/some/image.jpg', 'crop': { 'top': 11, 'right': 12, 'bottom': 13, 'left': 10 }, 'height': 300, 'width': 300, 'meta': False, 'horizontal_flip': True, 'filters': 'some_filter()', 'valign': 'middle', 'debug': False, 'hash': 'e2baf424fa420b73a97476956dfb858f', 'adaptive': False, 'smart': True } expect(options).to_be_like(expected_options) def test_get_options_from_storage_returns_null_if_key_not_found(self): image_url = "/some/image.jpg" custom_security_key = "custom-sec" cryptor = Cryptor(security_key=custom_security_key) decryptor = Cryptor(security_key="something") expected_options = dict( width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image=image_url, ) encrypted_str = cryptor.encrypt(**expected_options) mock_storage = mock.Mock() decryptor.context = mock.Mock( config=mock.Mock(STORES_CRYPTO_KEY_FOR_EACH_IMAGE=True), modules=mock.Mock(storage=mock_storage), ) mock_storage.get_crypto.return_value = None options = decryptor.get_options(encrypted_str, image_url) expect(options).to_be_null()
def test_get_options_from_storage(self): image_url = "/some/image.jpg" custom_security_key = "custom-sec" cryptor = Cryptor(security_key=custom_security_key) decryptor = Cryptor(security_key="something") expected_options = dict( width=300, height=300, smart=True, adaptive=False, full=False, fit_in=False, flip_horizontal=True, flip_vertical=True, halign="center", valign="middle", trim=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13, filters='some_filter()', image=image_url, ) encrypted_str = cryptor.encrypt(**expected_options) mock_storage = mock.Mock() decryptor.context = mock.Mock( config=mock.Mock(STORES_CRYPTO_KEY_FOR_EACH_IMAGE=True), modules=mock.Mock(storage=mock_storage), ) mock_storage.get_crypto.return_value = custom_security_key options = decryptor.get_options(encrypted_str, image_url) expect(options).not_to_be_null() expected_options = { 'trim': 'trim', 'full': False, 'halign': 'center', 'fit_in': False, 'vertical_flip': True, 'image': '/some/image.jpg', 'crop': { 'top': 11, 'right': 12, 'bottom': 13, 'left': 10 }, 'height': 300, 'width': 300, 'meta': False, 'horizontal_flip': True, 'filters': 'some_filter()', 'valign': 'middle', 'debug': False, 'hash': 'e2baf424fa420b73a97476956dfb858f', 'adaptive': False, 'smart': True } expect(options).to_be_like(expected_options)
def decrypt_in_thumbor(key, encrypted): """Uses thumbor to decrypt libthumbor's encrypted URL""" crypto = Cryptor(key) return crypto.decrypt(encrypted)
def check_image(self, kw): if self.context.config.MAX_ID_LENGTH > 0: # Check if an image with an uuid exists in storage exists = yield gen.maybe_future( self.context.modules.storage.exists( kw['image'][:self.context.config.MAX_ID_LENGTH])) if exists: kw['image'] = kw['image'][:self.context.config.MAX_ID_LENGTH] url = self.request.uri if not self.validate(kw['image']): self._error(400, 'No original image was specified in the given URL') return kw['request'] = self.request self.context.request = RequestParameters(**kw) has_none = not self.context.request.unsafe and not self.context.request.hash has_both = self.context.request.unsafe and self.context.request.hash if has_none or has_both: self._error( 400, 'URL does not have hash or unsafe, or has both: %s' % url) return if self.context.request.unsafe and not self.context.config.ALLOW_UNSAFE_URL: self._error( 400, 'URL has unsafe but unsafe is not allowed by the config: %s' % url) return if self.context.config.USE_BLACKLIST: blacklist = yield self.get_blacklist_contents() if self.context.request.image_url in blacklist: self._error( 400, 'Source image url has been blacklisted: %s' % self.context.request.image_url) return url_signature = self.context.request.hash if url_signature: signer = self.context.modules.url_signer( self.context.server.security_key) url_to_validate = Url.encode_url(url).replace( '/%s/' % self.context.request.hash, '') valid = signer.validate(url_signature, url_to_validate) if not valid and self.context.config.STORES_CRYPTO_KEY_FOR_EACH_IMAGE: # Retrieves security key for this image if it has been seen before security_key = yield gen.maybe_future( self.context.modules.storage.get_crypto( self.context.request.image_url)) if security_key is not None: signer = self.context.modules.url_signer(security_key) valid = signer.validate(url_signature, url_to_validate) if not valid: is_valid = True if self.context.config.ALLOW_OLD_URLS: cr = Cryptor(self.context.server.security_key) options = cr.get_options(self.context.request.hash, self.context.request.image_url) if options is None: is_valid = False else: options['request'] = self.request self.context.request = RequestParameters(**options) logger.warning( 'OLD FORMAT URL DETECTED!!! This format of URL will be discontinued in ' + 'upcoming versions. Please start using the new format as soon as possible. ' + 'More info at https://github.com/thumbor/thumbor/wiki/3.0.0-release-changes' ) else: is_valid = False if not is_valid: self._error(400, 'Malformed URL: %s' % url) return self.execute_image_operations()
def main(arguments=None): '''Converts a given url with the specified arguments.''' if arguments is None: arguments = sys.argv[1:] parser = optparse.OptionParser(usage='thumbor-url [options] imageurl or type thumbor-url -h (--help) for help', description=__doc__, version=__version__) parser.add_option('-l', '--key_file', dest='key_file', default=None, help='The file to read the security key from [default: %default].') parser.add_option('-k', '--key', dest='key', default=None, help='The security key to encrypt the url with [default: %default].') parser.add_option('-w', '--width', dest='width', type='int', default=0, help='The target width for the image [default: %default].') parser.add_option('-e', '--height', dest='height', type='int', default=0, help='The target height for the image [default: %default].') parser.add_option('-n', '--fitin', dest='fitin', action='store_true', default=False, help='Indicates that fit-in resizing should be performed.') parser.add_option('-m', '--meta', dest='meta', action='store_true', default=False, help='Indicates that meta information should be retrieved.') parser.add_option('', '--adaptive', action='store_true', dest='adaptive', default=False, help='Indicates that adaptive fit-in cropping should be used.') parser.add_option('', '--full', action='store_true', dest='full', default=False, help='Indicates that fit-full cropping should be used.') parser.add_option('-s', '--smart', action='store_true', dest='smart', default=False, help='Indicates that smart cropping should be used.') parser.add_option('-t', '--trim', action='store_true', default=False, help='Indicate that surrounding whitespace should be trimmed.') parser.add_option('-f', '--horizontal-flip', action='store_true', dest='horizontal_flip', default=False, help='Indicates that the image should be horizontally flipped.') parser.add_option('-v', '--vertical-flip', action='store_true', dest='vertical_flip', default=False, help='Indicates that the image should be vertically flipped.') parser.add_option('-a', '--halign', dest='halign', default='center', help='The horizontal alignment to use for cropping [default: %default].') parser.add_option('-i', '--valign', dest='valign', default='middle', help='The vertical alignment to use for cropping [default: %default].') parser.add_option('', '--filters', dest='filters', default='', help='Filters to be applied to the image, e.g. brightness(10) [default: %default].') parser.add_option('-o', '--old-format', dest='old', action='store_true', default=False, help='Indicates that thumbor should generate old-format urls [default: %default].') parser.add_option('-c', '--crop', dest='crop', default=None, help='The coordinates of the points to manual cropping in the format leftxtop:rightxbottom (100x200:400x500) [default: %default].') (parsed_options, arguments) = parser.parse_args(arguments) if not arguments: print 'Error: The image argument is mandatory. For more information type thumbor-url -h' return image_url = arguments[0] if image_url.startswith('/'): image_url = image_url[1:] try: config = Config.load(None) except: config = None if not parsed_options.key and not config: print 'Error: The -k or --key argument is mandatory. For more information type thumbor-url -h' return if parsed_options.key_file: f = open(parsed_options.key_file) security_key = f.read().strip() f.close() else: security_key = config.SECURITY_KEY if not parsed_options.key else parsed_options.key crop_left = crop_top = crop_right = crop_bottom = 0 if parsed_options.crop: crops = parsed_options.crop.split(':') crop_left, crop_top = crops[0].split('x') crop_right, crop_bottom = crops[1].split('x') if parsed_options.old: crypt = Cryptor(security_key) opt = crypt.encrypt(parsed_options.width, parsed_options.height, parsed_options.smart, parsed_options.adaptive, parsed_options.full, parsed_options.fitin, parsed_options.horizontal_flip, parsed_options.vertical_flip, parsed_options.halign, parsed_options.valign, parsed_options.trim, crop_left, crop_top, crop_right, crop_bottom, parsed_options.filters, image_url) url = '/%s/%s' % (opt, image_url) print 'Encrypted URL:' else: signer = Signer(security_key) url = Url.generate_options( width=parsed_options.width, height=parsed_options.height, smart=parsed_options.smart, meta=parsed_options.meta, adaptive=parsed_options.adaptive, full=parsed_options.full, fit_in=parsed_options.fitin, horizontal_flip=parsed_options.horizontal_flip, vertical_flip=parsed_options.vertical_flip, halign=parsed_options.halign, valign=parsed_options.valign, trim=parsed_options.trim, crop_left=crop_left, crop_top=crop_top, crop_right=crop_right, crop_bottom=crop_bottom, filters=parsed_options.filters ) url = '%s/%s' % (url, image_url) url = url.lstrip('/') signature = signer.signature(url) url = '/%s/%s' % (signature, url) print 'Signed URL:' print url return url
def get(self, **kw): # Check if an image with an uuid exists in storage if self.context.modules.storage.exists(kw['image'][:32]): kw['image'] = kw['image'][:32] url = self.request.uri if not self.validate(kw['image']): self._error(404, 'No original image was specified in the given URL') return self.context.request = RequestParameters(**kw) self.context.request.unsafe = self.context.request.unsafe == 'unsafe' if (self.request.query): self.context.request.image_url += '?%s' % self.request.query self.context.request.image_url = self.encode_url(self.context.request.image_url.encode('utf-8')) has_none = not self.context.request.unsafe and not self.context.request.hash has_both = self.context.request.unsafe and self.context.request.hash if has_none or has_both: self._error(404, 'URL does not have hash or unsafe, or has both: %s' % url) return if self.context.request.unsafe and not self.context.config.ALLOW_UNSAFE_URL: self._error(404, 'URL has unsafe but unsafe is not allowed by the config: %s' % url) return url_signature = self.context.request.hash if url_signature: signer = Signer(self.context.server.security_key) url_to_validate = self.encode_url(url).replace('/%s/' % self.context.request.hash, '') valid = signer.validate(url_signature, url_to_validate) if not valid and self.context.config.STORES_CRYPTO_KEY_FOR_EACH_IMAGE: # Retrieves security key for this image if it has been seen before security_key = self.context.modules.storage.get_crypto(self.context.request.image_url) if security_key is not None: signer = Signer(security_key) valid = signer.validate(url_signature, url_to_validate) if not valid: is_valid = True if self.context.config.ALLOW_OLD_URLS: cr = Cryptor(self.context.server.security_key) options = cr.get_options(self.context.request.hash, self.context.request.image_url) if options is None: is_valid = False else: self.context.request = RequestParameters(**options) logger.warning('OLD FORMAT URL DETECTED!!! This format of URL will be discontinued in upcoming versions. Please start using the new format as soon as possible. More info at https://github.com/globocom/thumbor/wiki/3.0.0-release-changes') else: is_valid = False if not is_valid: self._error(404, 'Malformed URL: %s' % url) return return self.execute_image_operations()
def decrypt_in_thumbor(url): '''Uses thumbor to decrypt libthumbor's encrypted URL''' encrypted = url.split('/')[1] cryptor = Cryptor(KEY) return cryptor.decrypt(encrypted)
def check_image(self, kw): if self.context.config.MAX_ID_LENGTH > 0: # Check if an image with an uuid exists in storage exists = yield gen.maybe_future(self.context.modules.storage.exists(kw['image'][:self.context.config.MAX_ID_LENGTH])) if exists: kw['image'] = kw['image'][:self.context.config.MAX_ID_LENGTH] url = self.request.uri if not self.validate(kw['image']): self._error(400, 'No original image was specified in the given URL') return kw['request'] = self.request self.context.request = RequestParameters(**kw) has_none = not self.context.request.unsafe and not self.context.request.hash has_both = self.context.request.unsafe and self.context.request.hash if has_none or has_both: self._error(400, 'URL does not have hash or unsafe, or has both: %s' % url) return if self.context.request.unsafe and not self.context.config.ALLOW_UNSAFE_URL: self._error(400, 'URL has unsafe but unsafe is not allowed by the config: %s' % url) return if self.context.config.USE_BLACKLIST: blacklist = yield self.get_blacklist_contents() if self.context.request.image_url in blacklist: self._error(400, 'Source image url has been blacklisted: %s' % self.context.request.image_url ) return url_signature = self.context.request.hash if url_signature: signer = Signer(self.context.server.security_key) url_to_validate = Url.encode_url(url).replace('/%s/' % self.context.request.hash, '') valid = signer.validate(url_signature, url_to_validate) if not valid and self.context.config.STORES_CRYPTO_KEY_FOR_EACH_IMAGE: # Retrieves security key for this image if it has been seen before security_key = yield gen.maybe_future(self.context.modules.storage.get_crypto(self.context.request.image_url)) if security_key is not None: signer = Signer(security_key) valid = signer.validate(url_signature, url_to_validate) if not valid: is_valid = True if self.context.config.ALLOW_OLD_URLS: cr = Cryptor(self.context.server.security_key) options = cr.get_options(self.context.request.hash, self.context.request.image_url) if options is None: is_valid = False else: options['request'] = self.request self.context.request = RequestParameters(**options) logger.warning( 'OLD FORMAT URL DETECTED!!! This format of URL will be discontinued in ' + 'upcoming versions. Please start using the new format as soon as possible. ' + 'More info at https://github.com/globocom/thumbor/wiki/3.0.0-release-changes' ) else: is_valid = False if not is_valid: self._error(400, 'Malformed URL: %s' % url) return self.execute_image_operations()
def decrypt_in_thumbor(key, encrypted): '''Uses thumbor to decrypt libthumbor's encrypted URL''' crypto = Cryptor(key) return crypto.decrypt(encrypted)