import base64 import pytest from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.utils.encoding import force_str import tests.utils from passerelle.apps.cryptor.models import CryptedFile, Cryptor from passerelle.base.models import AccessRight, ApiUser PUBLIC_KEY = '''-----BEGIN CERTIFICATE----- MIICyjCCAbKgAwIBAgIUQQzM2eFYF+LpUR3t2euAjZAwLCEwDQYJKoZIhvcNAQEL BQAwDzENMAsGA1UEAwwEemVwbzAeFw0xOTA2MjIyMjMxMTVaFw0yOTA2MTkyMjMx MTVaMA8xDTALBgNVBAMMBHplcG8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQC8BM3xDylze0bOm76IjidyhmFqJlnRvcpbeZVTM7r3qYOHqXFG7/GZL4yd 2bW5eL6TCUT3gLEgegGYGPwCkGPd1cq9h+2M7zvolGToRCvrBpxH5Vu6iRkEYyWN yPhc02EUqYlz1FBBYRgyYHQ4jy0vsPH55g536OKLI4rVykszjwD9p0Kh+T2I1D9Z bHyA6s0C8goUFZG7kvasFRIXTTPgUBGSnEN/VPSD5vM94Oj5K4t6P9GHd32Jo2O6 E5jAHbPR7I4nRBCJuxJEbfpmsaOuMkGQ5rMulk6LXvRZiT9UDCDem1k6uF6tJkZR g+Uh5V4ZLCzP7sSHcRN2ftWZqAr7AgMBAAGjHjAcMAkGA1UdEwQCMAAwDwYDVR0R BAgwBoIEemVwbzANBgkqhkiG9w0BAQsFAAOCAQEANo54TMbOR5Isd4lix87EM0N+ 8kxovCLin/szK4+fGfnr0fCUswkhoZ3y6xnmXFX4S2IGLU8bTl+eQVg04VM/7Gg2 LvBTtiBmGESbiSaC1fS+DbPBjp1NBpfwbiQFEuQfRMS6ejeF1YMS8Oy9PpeujHDT 4cX0kPH2GkqOGtpAdKoOD5XzT3yu5IHv7/AWpl8LZ5hr3f1RbzfIzJ19oC5NDXIR d1XsjuCCHFbCyfnDmuZuQaGCTCm9f4Z8Ynum6hmSSzNvy3YJLRKXEYYLB3+l2V+t SCzQiVzqDeKZnAChvJRditvcKdG36TPHREyCPgkpUTmi0gEjDZDPyBmXhHXHYA== -----END CERTIFICATE----- ''' PRIVATE_KEY = '''-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8BM3xDylze0bO m76IjidyhmFqJlnRvcpbeZVTM7r3qYOHqXFG7/GZL4yd2bW5eL6TCUT3gLEgegGY GPwCkGPd1cq9h+2M7zvolGToRCvrBpxH5Vu6iRkEYyWNyPhc02EUqYlz1FBBYRgy YHQ4jy0vsPH55g536OKLI4rVykszjwD9p0Kh+T2I1D9ZbHyA6s0C8goUFZG7kvas FRIXTTPgUBGSnEN/VPSD5vM94Oj5K4t6P9GHd32Jo2O6E5jAHbPR7I4nRBCJuxJE bfpmsaOuMkGQ5rMulk6LXvRZiT9UDCDem1k6uF6tJkZRg+Uh5V4ZLCzP7sSHcRN2 ftWZqAr7AgMBAAECggEAUQsdHhA0BNQZdEtLuJ7VwBbOfKvlQXQ2enGQ/RkqOUC3 Mk3GRxZ8JFSLnyrNmxHBy61OLgUp1F7iuwXh8tT8Rw21YzbpHTutrhXw3PEtoRPr X04s2N3pi6uU72W2MITorrhZSDU3FsdcX7KVxh9pEcqKsvYIPIWEyQbb/EVDXwhC K4TAsmmhsGaU/BB0WHkbzU5KlZqYQHfnoj5pmTLoeYj81Z8D5T9fceImcyuWLl8t OHscbAjNpkS1X2vYNwMLhCAM7YhE63RjFo2G5fxx64wdgs+mllR0PCc3Nli+JPWE LQ/KmVMPY6JH506WbZkEVgb9Gfuj6yASpu5zJxf3MQKBgQDwktbzKmK3zwSNwLdx zZy4AnQb2PNKuryYts9R+nKqWnE5vJwekBIV9vsASK0Aoh4UWP9cUjQV3gZH3HIR 9xP8nJwpOLU7c/wPP81HUl7nf68MVP5OzemHY/78fYR8T3Jf/uFdW3AOQrCBjGIV Zbjb5SisqCS2ODc7DSuqzliBMwKBgQDIEz7nhXyMilTlgiWw7iRm9RKWykVSjEuR gxHAqPOkg/HxacfDYs/O+qteSdW/V7zzZ2QHMyr52BaL5sEz0lC7W3O1nfTS94VM YK+MakRBImC5uZvpea+30vJbVLODnKskhsWF8aCqLi5KJvkR5aE0OySKXBur7Czq X2/gK6jfGQKBgAbU1J/BE16O1V1FHLBxm0KqZyunRHlZxiM8BbUZPIpT2SU/kttX UfwnsEb4yVjcQahoQpAXkX0RefIuc1rJPlsNA240Owk+KOkx8Z1V3HYMbScXfsU0 Ga6Li2EWG14AT4okTbf98bel8ycqmlprMg2kezwz5h76h674l8XY6DB7AoGBAKJL Aka5gBtchpsZJEvOENc3Spnof60DQrVJVZgrNF+p7BMA1FsIhzsFGQdF603n9MyY fIpelijOgROA3g2UN4qTF1wmQhbzUzxuXVgQR0dyhHWDOxZ7b+8z/QXawjcrWaQq coVBSCtjhIb/8B/1XftJUk2tg4DE9nYzbkOwBq7ZAoGAPF1KSY9TzY7g8gmQzYuY +CCHM3mR9PjSHhJS5VWTwGfw3zZpLptwxzmJAoi1DyrJVhBP17ADVitYK4GquiSB z5aZ2AnUBc/xueO2ixL3ROOXYAeakrRAQ38G13ibYe2dQpv6/CTsZJOttnCErn54 3k2Y/kDV+c1uNbzyPiK2qaM= -----END PRIVATE KEY----- ''' @pytest.fixture def cryptor(db): return Cryptor.objects.create(slug='test', private_key=PRIVATE_KEY, public_key=PUBLIC_KEY) def test_cryptor_bad_keys(db): bad1 = Cryptor(slug='bad1', title='t', description='d', private_key='badkey') with pytest.raises(ValidationError) as e1: bad1.full_clean() assert e1.value.messages == ['invalid RSA key (RSA key format is not supported)'] bad2 = Cryptor(slug='bad2', title='t', description='d', public_key='badkey') with pytest.raises(ValidationError) as e2: bad2.full_clean() assert e2.value.messages == ['invalid RSA key (RSA key format is not supported)'] def test_cryptor_restricted_access(app, cryptor): endpoint = tests.utils.generic_endpoint_url('cryptor', 'file-encrypt', slug=cryptor.slug) assert endpoint == '/cryptor/test/file-encrypt' resp = app.get(endpoint, status=405) resp = app.post_json(endpoint, params={'foo': 'bar'}, status=403) assert resp.json['err'] == 1 assert 'PermissionDenied' in resp.json['err_class'] endpoint = tests.utils.generic_endpoint_url('cryptor', 'file-decrypt', slug=cryptor.slug) + '/uuid' assert endpoint == '/cryptor/test/file-decrypt/uuid' resp = app.post_json(endpoint, params={'foo': 'bar'}, status=405) resp = app.get(endpoint, status=403) assert resp.json['err'] == 1 assert 'PermissionDenied' in resp.json['err_class'] def test_cryptor_bad_requests(app, cryptor): # full opened access api = ApiUser.objects.create(username='all', keytype='', key='') obj_type = ContentType.objects.get_for_model(cryptor) AccessRight.objects.create( codename='can_encrypt', apiuser=api, resource_type=obj_type, resource_pk=cryptor.pk ) AccessRight.objects.create( codename='can_decrypt', apiuser=api, resource_type=obj_type, resource_pk=cryptor.pk ) endpoint = tests.utils.generic_endpoint_url('cryptor', 'file-encrypt', slug=cryptor.slug) for bad_payload in ( 'error', {'foo': 'bar'}, ['not', 'a', 'dict'], {'file': {'filename': 'f', 'content_type': 'ct'}}, {'file': {'filename': 'f', 'content_type': 'ct', 'content': None}}, {'file': {'filename': 'f', 'content_type': 'ct', 'content': 'NotBase64'}}, ): resp = app.post_json(endpoint, params=bad_payload, status=400) assert resp.json['err'] == 1 endpoint = tests.utils.generic_endpoint_url('cryptor', 'file-decrypt', slug=cryptor.slug) endpoint = endpoint + '/bad-uuid' resp = app.get(endpoint, status=404) def test_cryptor_encrypt_decrypt(app, cryptor): api = ApiUser.objects.create(username='all', keytype='', key='') obj_type = ContentType.objects.get_for_model(cryptor) AccessRight.objects.create( codename='can_encrypt', apiuser=api, resource_type=obj_type, resource_pk=cryptor.pk ) AccessRight.objects.create( codename='can_decrypt', apiuser=api, resource_type=obj_type, resource_pk=cryptor.pk ) # encrypt endpoint = tests.utils.generic_endpoint_url('cryptor', 'file-encrypt', slug=cryptor.slug) content = force_str(base64.b64encode(b'this is foo and bar')) payload = {'file': {'filename': 'foo.txt', 'content_type': 'text/plain', 'content': content}} resp = app.post_json(endpoint, params=payload, status=200) assert resp.json['err'] == 0 assert CryptedFile.objects.count() == 1 cfile = CryptedFile.objects.first() assert resp.json['data']['redirect_url'].endswith( '/cryptor/%s/file-decrypt/%s' % (cryptor.slug, cfile.uuid) ) cfile.delete() # encrypt with another redirect url cryptor.redirect_url_base = 'https://foo/bar/' cryptor.save() resp = app.post_json(endpoint, params=payload, status=200) assert resp.json['err'] == 0 assert CryptedFile.objects.count() == 1 cfile = CryptedFile.objects.first() assert resp.json['data']['redirect_url'] == 'https://foo/bar/%s' % cfile.uuid # remove public key = cannot encrypt cryptor.public_key = '' cryptor.save() resp = app.post_json(endpoint, params=payload, status=200) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'missing public key' # decrypt endpoint = tests.utils.generic_endpoint_url('cryptor', 'file-decrypt', slug=cryptor.slug) endpoint = endpoint + '/' + str(cfile.uuid) resp = app.get(endpoint, status=200) assert resp.content_type == 'text/plain' assert resp.content == b'this is foo and bar' assert resp.headers['Content-Disposition'] == 'inline; filename="foo.txt"' # remove all files does not remove data+metadata(json) files: decrypt still works CryptedFile.objects.all().delete() assert CryptedFile.objects.count() == 0 resp = app.get(endpoint, status=200) assert resp.content_type == 'text/plain' assert resp.content == b'this is foo and bar' assert resp.headers['Content-Disposition'] == 'inline; filename="foo.txt"' # remove private key = cannot decrypt cryptor.private_key = '' cryptor.save() resp = app.get(endpoint, status=200) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'missing private key'