2020-05-08 20:43:54 +02:00
|
|
|
import re
|
2016-10-25 01:21:08 +02:00
|
|
|
import time
|
2013-10-05 23:12:31 +02:00
|
|
|
import unittest
|
2017-05-11 06:40:24 +02:00
|
|
|
import uuid
|
2013-10-05 23:12:31 +02:00
|
|
|
from unittest import TestCase
|
2022-05-17 19:57:35 +02:00
|
|
|
from unittest import mock
|
2024-03-29 23:47:42 +01:00
|
|
|
from urllib.parse import quote
|
2022-05-17 20:10:58 +02:00
|
|
|
from urllib.parse import unquote
|
2013-10-05 23:12:31 +02:00
|
|
|
|
2017-05-11 06:40:24 +02:00
|
|
|
from cryptography.fernet import Fernet
|
2020-05-08 20:43:54 +02:00
|
|
|
from freezegun import freeze_time
|
2016-07-18 22:36:06 +02:00
|
|
|
from werkzeug.exceptions import BadRequest
|
2019-08-09 17:37:03 +02:00
|
|
|
from fakeredis import FakeStrictRedis
|
2013-10-05 23:12:31 +02:00
|
|
|
|
2016-07-18 20:52:37 +02:00
|
|
|
# noinspection PyPep8Naming
|
2013-10-05 23:12:31 +02:00
|
|
|
import snappass.main as snappass
|
|
|
|
|
|
|
|
__author__ = 'davedash'
|
|
|
|
|
|
|
|
|
|
|
|
class SnapPassTestCase(TestCase):
|
|
|
|
|
2022-05-17 19:57:35 +02:00
|
|
|
@mock.patch('redis.client.StrictRedis', FakeStrictRedis)
|
2013-10-05 23:12:31 +02:00
|
|
|
def test_get_password(self):
|
|
|
|
password = "melatonin overdose 1337!$"
|
|
|
|
key = snappass.set_password(password, 30)
|
|
|
|
self.assertEqual(password, snappass.get_password(key))
|
|
|
|
# Assert that we can't look this up a second time.
|
2018-05-08 03:20:54 +02:00
|
|
|
self.assertIsNone(snappass.get_password(key))
|
2013-10-05 23:12:31 +02:00
|
|
|
|
2017-05-11 06:40:24 +02:00
|
|
|
def test_password_is_not_stored_in_plaintext(self):
|
|
|
|
password = "trustno1"
|
|
|
|
token = snappass.set_password(password, 30)
|
|
|
|
redis_key = token.split(snappass.TOKEN_SEPARATOR)[0]
|
|
|
|
stored_password_text = snappass.redis_client.get(redis_key).decode('utf-8')
|
2018-05-08 03:20:54 +02:00
|
|
|
self.assertNotIn(password, stored_password_text)
|
2017-05-11 06:40:24 +02:00
|
|
|
|
|
|
|
def test_returned_token_format(self):
|
|
|
|
password = "trustsome1"
|
|
|
|
token = snappass.set_password(password, 30)
|
|
|
|
token_fragments = token.split(snappass.TOKEN_SEPARATOR)
|
|
|
|
self.assertEqual(2, len(token_fragments))
|
|
|
|
redis_key, encryption_key = token_fragments
|
2018-07-01 19:19:56 +02:00
|
|
|
self.assertEqual(32 + len(snappass.REDIS_PREFIX), len(redis_key))
|
2017-05-11 06:40:24 +02:00
|
|
|
try:
|
|
|
|
Fernet(encryption_key.encode('utf-8'))
|
|
|
|
except ValueError:
|
|
|
|
self.fail('the encryption key is not valid')
|
|
|
|
|
|
|
|
def test_encryption_key_is_returned(self):
|
|
|
|
password = "trustany1"
|
|
|
|
token = snappass.set_password(password, 30)
|
|
|
|
token_fragments = token.split(snappass.TOKEN_SEPARATOR)
|
|
|
|
redis_key, encryption_key = token_fragments
|
|
|
|
stored_password = snappass.redis_client.get(redis_key)
|
|
|
|
fernet = Fernet(encryption_key.encode('utf-8'))
|
|
|
|
decrypted_password = fernet.decrypt(stored_password).decode('utf-8')
|
|
|
|
self.assertEqual(password, decrypted_password)
|
|
|
|
|
|
|
|
def test_unencrypted_passwords_still_work(self):
|
|
|
|
unencrypted_password = "trustevery1"
|
|
|
|
storage_key = uuid.uuid4().hex
|
|
|
|
snappass.redis_client.setex(storage_key, 30, unencrypted_password)
|
|
|
|
retrieved_password = snappass.get_password(storage_key)
|
|
|
|
self.assertEqual(unencrypted_password, retrieved_password)
|
|
|
|
|
2016-08-12 03:50:37 +02:00
|
|
|
def test_password_is_decoded(self):
|
|
|
|
password = "correct horse battery staple"
|
|
|
|
key = snappass.set_password(password, 30)
|
|
|
|
self.assertFalse(isinstance(snappass.get_password(key), bytes))
|
|
|
|
|
2013-10-05 23:12:31 +02:00
|
|
|
def test_clean_input(self):
|
|
|
|
# Test Bad Data
|
|
|
|
with snappass.app.test_request_context(
|
|
|
|
"/", data={'password': 'foo', 'ttl': 'bar'}, method='POST'):
|
2016-07-18 22:36:06 +02:00
|
|
|
self.assertRaises(BadRequest, snappass.clean_input)
|
2013-10-05 23:12:31 +02:00
|
|
|
|
|
|
|
# No Password
|
|
|
|
with snappass.app.test_request_context(
|
|
|
|
"/", method='POST'):
|
2016-07-18 22:36:06 +02:00
|
|
|
self.assertRaises(BadRequest, snappass.clean_input)
|
2013-10-05 23:12:31 +02:00
|
|
|
|
|
|
|
# No TTL
|
|
|
|
with snappass.app.test_request_context(
|
|
|
|
"/", data={'password': 'foo'}, method='POST'):
|
2016-07-18 22:36:06 +02:00
|
|
|
self.assertRaises(BadRequest, snappass.clean_input)
|
2013-10-05 23:12:31 +02:00
|
|
|
|
|
|
|
with snappass.app.test_request_context(
|
|
|
|
"/", data={'password': 'foo', 'ttl': 'hour'}, method='POST'):
|
|
|
|
self.assertEqual((3600, 'foo'), snappass.clean_input())
|
|
|
|
|
2016-10-25 01:21:08 +02:00
|
|
|
def test_password_before_expiration(self):
|
|
|
|
password = 'fidelio'
|
|
|
|
key = snappass.set_password(password, 1)
|
|
|
|
self.assertEqual(password, snappass.get_password(key))
|
|
|
|
|
|
|
|
def test_password_after_expiration(self):
|
|
|
|
password = 'open sesame'
|
|
|
|
key = snappass.set_password(password, 1)
|
|
|
|
time.sleep(1.5)
|
2018-05-08 03:20:54 +02:00
|
|
|
self.assertIsNone(snappass.get_password(key))
|
2016-10-25 01:21:08 +02:00
|
|
|
|
2013-10-05 23:12:31 +02:00
|
|
|
|
|
|
|
class SnapPassRoutesTestCase(TestCase):
|
2016-07-18 20:52:37 +02:00
|
|
|
# noinspection PyPep8Naming
|
2013-10-05 23:12:31 +02:00
|
|
|
def setUp(self):
|
|
|
|
snappass.app.config['TESTING'] = True
|
|
|
|
self.app = snappass.app.test_client()
|
|
|
|
|
2024-02-03 01:14:51 +01:00
|
|
|
def test_health_check(self):
|
|
|
|
response = self.app.get('/_/_/health')
|
|
|
|
self.assertEqual('200 OK', response.status)
|
|
|
|
self.assertEqual('{}', response.get_data(as_text=True).strip())
|
|
|
|
|
2019-03-05 16:47:07 +01:00
|
|
|
def test_preview_password(self):
|
2013-10-05 23:12:31 +02:00
|
|
|
password = "I like novelty kitten statues!"
|
|
|
|
key = snappass.set_password(password, 30)
|
2016-08-13 00:38:33 +02:00
|
|
|
rv = self.app.get('/{0}'.format(key))
|
2019-03-05 16:47:07 +01:00
|
|
|
self.assertNotIn(password, rv.get_data(as_text=True))
|
2013-10-05 23:12:31 +02:00
|
|
|
|
2019-03-05 16:47:07 +01:00
|
|
|
def test_show_password(self):
|
|
|
|
password = "I like novelty kitten statues!"
|
2016-12-20 06:08:56 +01:00
|
|
|
key = snappass.set_password(password, 30)
|
2019-03-05 16:47:07 +01:00
|
|
|
rv = self.app.post('/{0}'.format(key))
|
|
|
|
self.assertIn(password, rv.get_data(as_text=True))
|
2016-12-20 06:08:56 +01:00
|
|
|
|
2019-08-09 23:07:49 +02:00
|
|
|
def test_url_prefix(self):
|
|
|
|
password = "I like novelty kitten statues!"
|
2020-05-08 20:43:54 +02:00
|
|
|
snappass.URL_PREFIX = "/test/prefix"
|
2019-08-09 23:07:49 +02:00
|
|
|
rv = self.app.post('/', data={'password': password, 'ttl': 'hour'})
|
|
|
|
self.assertIn("localhost/test/prefix/", rv.get_data(as_text=True))
|
2013-10-05 23:12:31 +02:00
|
|
|
|
2020-05-08 20:43:54 +02:00
|
|
|
def test_set_password(self):
|
|
|
|
with freeze_time("2020-05-08 12:00:00") as frozen_time:
|
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post('/', data={'password': password, 'ttl': 'two weeks'})
|
|
|
|
|
|
|
|
html_content = rv.data.decode("ascii")
|
|
|
|
key = re.search(r'id="password-link" value="https://localhost/([^"]+)', html_content).group(1)
|
|
|
|
key = unquote(key)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 11:59:59")
|
|
|
|
self.assertEqual(snappass.get_password(key), password)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 12:00:00")
|
|
|
|
self.assertIsNone(snappass.get_password(key))
|
2022-05-17 20:41:01 +02:00
|
|
|
|
2022-04-11 21:37:19 +02:00
|
|
|
def test_set_password_json(self):
|
|
|
|
with freeze_time("2020-05-08 12:00:00") as frozen_time:
|
|
|
|
password = 'my name is my passport. verify me.'
|
2022-05-17 20:41:01 +02:00
|
|
|
rv = self.app.post(
|
|
|
|
'/',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
data={'password': password, 'ttl': 'two weeks'},
|
|
|
|
)
|
2022-04-11 21:37:19 +02:00
|
|
|
|
|
|
|
json_content = rv.get_json()
|
|
|
|
key = re.search(r'https://localhost/([^"]+)', json_content['link']).group(1)
|
|
|
|
key = unquote(key)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 11:59:59")
|
|
|
|
self.assertEqual(snappass.get_password(key), password)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 12:00:00")
|
|
|
|
self.assertIsNone(snappass.get_password(key))
|
2020-05-08 20:43:54 +02:00
|
|
|
|
2024-02-27 00:22:40 +01:00
|
|
|
def test_set_password_api(self):
|
|
|
|
with freeze_time("2020-05-08 12:00:00") as frozen_time:
|
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post(
|
|
|
|
'/api/set_password/',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': password, 'ttl': '1209600'},
|
|
|
|
)
|
|
|
|
|
|
|
|
json_content = rv.get_json()
|
|
|
|
key = re.search(r'https://localhost/([^"]+)', json_content['link']).group(1)
|
|
|
|
key = unquote(key)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 11:59:59")
|
|
|
|
self.assertEqual(snappass.get_password(key), password)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 12:00:00")
|
|
|
|
self.assertIsNone(snappass.get_password(key))
|
|
|
|
|
|
|
|
def test_set_password_api_default_ttl(self):
|
|
|
|
with freeze_time("2020-05-08 12:00:00") as frozen_time:
|
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post(
|
|
|
|
'/api/set_password/',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': password},
|
|
|
|
)
|
|
|
|
|
|
|
|
json_content = rv.get_json()
|
|
|
|
key = re.search(r'https://localhost/([^"]+)', json_content['link']).group(1)
|
|
|
|
key = unquote(key)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 11:59:59")
|
|
|
|
self.assertEqual(snappass.get_password(key), password)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 12:00:00")
|
|
|
|
self.assertIsNone(snappass.get_password(key))
|
|
|
|
|
2024-03-29 11:46:56 +01:00
|
|
|
def test_set_password_api_v2(self):
|
|
|
|
with freeze_time("2020-05-08 12:00:00") as frozen_time:
|
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post(
|
2024-03-29 23:47:42 +01:00
|
|
|
'/api/v2/passwords',
|
2024-03-29 11:46:56 +01:00
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': password, 'ttl': '1209600'},
|
|
|
|
)
|
|
|
|
|
|
|
|
json_content = rv.get_json()
|
2024-03-29 23:47:42 +01:00
|
|
|
key = unquote(json_content['token'])
|
2024-03-29 11:46:56 +01:00
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 11:59:59")
|
|
|
|
self.assertEqual(snappass.get_password(key), password)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 12:00:00")
|
|
|
|
self.assertIsNone(snappass.get_password(key))
|
|
|
|
|
|
|
|
def test_set_password_api_v2_default_ttl(self):
|
|
|
|
with freeze_time("2020-05-08 12:00:00") as frozen_time:
|
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post(
|
2024-03-29 23:47:42 +01:00
|
|
|
'/api/v2/passwords',
|
2024-03-29 11:46:56 +01:00
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': password},
|
|
|
|
)
|
|
|
|
|
|
|
|
json_content = rv.get_json()
|
2024-03-29 23:47:42 +01:00
|
|
|
key = unquote(json_content['token'])
|
2024-03-29 11:46:56 +01:00
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 11:59:59")
|
|
|
|
self.assertEqual(snappass.get_password(key), password)
|
|
|
|
|
|
|
|
frozen_time.move_to("2020-05-22 12:00:00")
|
|
|
|
self.assertIsNone(snappass.get_password(key))
|
|
|
|
|
|
|
|
def test_set_password_api_v2_no_password(self):
|
2024-03-29 23:59:15 +01:00
|
|
|
rv = self.app.post(
|
|
|
|
'/api/v2/passwords',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': ''},
|
|
|
|
)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
self.assertEqual(rv.status_code, 400)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
json_content = rv.get_json()
|
|
|
|
invalid_params = json_content['invalid-params']
|
|
|
|
self.assertEqual(len(invalid_params), 1)
|
|
|
|
bad_password = invalid_params[0]
|
|
|
|
self.assertEqual(bad_password['name'], 'password')
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
def test_set_password_api_v2_too_big_ttl(self):
|
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post(
|
|
|
|
'/api/v2/passwords',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': password, 'ttl': '1209600000'},
|
|
|
|
)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
self.assertEqual(rv.status_code, 400)
|
2024-03-29 23:47:42 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
json_content = rv.get_json()
|
|
|
|
invalid_params = json_content['invalid-params']
|
|
|
|
self.assertEqual(len(invalid_params), 1)
|
|
|
|
bad_ttl = invalid_params[0]
|
|
|
|
self.assertEqual(bad_ttl['name'], 'ttl')
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
def test_set_password_api_v2_no_password_and_too_big_ttl(self):
|
|
|
|
rv = self.app.post(
|
|
|
|
'/api/v2/passwords',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': '', 'ttl': '1209600000'},
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEqual(rv.status_code, 400)
|
|
|
|
|
|
|
|
json_content = rv.get_json()
|
|
|
|
invalid_params = json_content['invalid-params']
|
|
|
|
self.assertEqual(len(invalid_params), 2)
|
|
|
|
bad_password = invalid_params[0]
|
|
|
|
self.assertEqual(bad_password['name'], 'password')
|
|
|
|
bad_ttl = invalid_params[1]
|
|
|
|
self.assertEqual(bad_ttl['name'], 'ttl')
|
2024-03-29 11:46:56 +01:00
|
|
|
|
|
|
|
def test_check_password_api_v2(self):
|
2024-03-29 23:59:15 +01:00
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post(
|
|
|
|
'/api/v2/passwords',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': password},
|
|
|
|
)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
json_content = rv.get_json()
|
|
|
|
key = unquote(json_content['token'])
|
|
|
|
|
|
|
|
rvc = self.app.head('/api/v2/passwords/' + quote(key))
|
|
|
|
self.assertEqual(rvc.status_code, 200)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
|
|
|
def test_check_password_api_v2_bad_keys(self):
|
2024-03-29 23:59:15 +01:00
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post(
|
|
|
|
'/api/v2/passwords',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': password},
|
|
|
|
)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
json_content = rv.get_json()
|
|
|
|
key = unquote(json_content['token'])
|
|
|
|
|
|
|
|
rvc = self.app.head('/api/v2/passwords/' + quote(key[::-1]))
|
|
|
|
self.assertEqual(rvc.status_code, 404)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
|
|
|
def test_retrieve_password_api_v2(self):
|
2024-03-29 23:59:15 +01:00
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post(
|
|
|
|
'/api/v2/passwords',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': password},
|
|
|
|
)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
json_content = rv.get_json()
|
|
|
|
key = unquote(json_content['token'])
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
rvc = self.app.get('/api/v2/passwords/' + quote(key))
|
|
|
|
self.assertEqual(rv.status_code, 200)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
json_content_retrieved = rvc.get_json()
|
|
|
|
retrieved_password = json_content_retrieved['password']
|
|
|
|
self.assertEqual(retrieved_password, password)
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2024-03-29 23:59:15 +01:00
|
|
|
def test_retrieve_password_api_v2_bad_keys(self):
|
|
|
|
password = 'my name is my passport. verify me.'
|
|
|
|
rv = self.app.post(
|
|
|
|
'/api/v2/passwords',
|
|
|
|
headers={'Accept': 'application/json'},
|
|
|
|
json={'password': password},
|
|
|
|
)
|
|
|
|
|
|
|
|
json_content = rv.get_json()
|
|
|
|
key = unquote(json_content['token'])
|
|
|
|
|
|
|
|
rvc = self.app.get('/api/v2/passwords/' + quote(key[::-1]))
|
|
|
|
self.assertEqual(rvc.status_code, 404)
|
|
|
|
|
|
|
|
json_content_retrieved = rvc.get_json()
|
|
|
|
invalid_params = json_content_retrieved['invalid-params']
|
|
|
|
self.assertEqual(len(invalid_params), 1)
|
|
|
|
bad_token = invalid_params[0]
|
|
|
|
self.assertEqual(bad_token['name'], 'token')
|
2024-03-29 11:46:56 +01:00
|
|
|
|
2020-05-08 20:43:54 +02:00
|
|
|
|
2013-10-05 23:12:31 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
unittest.main()
|