import json from django.test import Client, RequestFactory from nacl.encoding import HexEncoder from nacl.signing import SigningKey from authentication.models import ToolshedUser, KnownIdentity from authentication.tests import UserTestMixin, SignatureAuthClient, DummyExternalUser, ToolshedTestCase from hostadmin.models import Domain class AuthorizationTestCase(ToolshedTestCase): def setUp(self): self.client = Client() self.factory = RequestFactory() from nacl.signing import SigningKey self.key = SigningKey.generate() self.signature = self.key.sign("test".encode('utf-8'), encoder=HexEncoder).signature.decode('utf-8') self.data = json.dumps({'a': 'b'}) self.signature_with_data = self.key.sign( ("test" + self.data).encode('utf-8'), encoder=HexEncoder).signature.decode('utf-8') def test_parse_auth_header(self): request = self.factory.get('/test') from authentication.signature_auth import verify_request with self.assertRaises(ValueError): verify_request(request, "") def test_parse_auth_header2(self): request = self.factory.get('/test', HTTP_AUTHORIZATION="Signature ") from authentication.signature_auth import verify_request with self.assertRaises(ValueError): verify_request(request, "") def test_parse_auth_header3(self): request = self.factory.get('/test', HTTP_AUTHORIZATION="Signature author@domain") from authentication.signature_auth import verify_request with self.assertRaises(ValueError): verify_request(request, "") def test_parse_auth_header4(self): from authentication.signature_auth import verify_request request = self.factory.get('/test', HTTP_AUTHORIZATION="Signature author@domain:" + self.signature) username, domain, signed_data, signature_bytes_hex = verify_request(request, "") self.assertEqual(username, "author") self.assertEqual(domain, "domain") self.assertEqual(signed_data, "http://testserver/test") self.assertEqual(signature_bytes_hex, self.signature) def test_parse_auth_header5(self): from authentication.signature_auth import verify_request request = self.factory.post('/test', self.data, content_type="application/json", HTTP_AUTHORIZATION="Signature author@domain:" + self.signature_with_data) username, domain, signed_data, signature_bytes_hex = verify_request(request, request.body.decode('utf-8')) self.assertEqual(username, "author") self.assertEqual(domain, "domain") self.assertEqual(signed_data, "http://testserver/test" + self.data) self.assertEqual(signature_bytes_hex, self.signature_with_data) def test_parse_auth_header6(self): from authentication.signature_auth import verify_request request = self.factory.put('/test', self.data, content_type="application/json", HTTP_AUTHORIZATION="Signature author@domain:" + self.signature_with_data) username, domain, signed_data, signature_bytes_hex = verify_request(request, request.body.decode('utf-8')) self.assertEqual(username, "author") self.assertEqual(domain, "domain") self.assertEqual(signed_data, "http://testserver/test" + self.data) self.assertEqual(signature_bytes_hex, self.signature_with_data) def test_parse_auth_header7(self): from authentication.signature_auth import verify_request request = self.factory.delete('/test', HTTP_AUTHORIZATION="Signature author@domain:" + self.signature) username, domain, signed_data, signature_bytes_hex = verify_request(request, request.body.decode('utf-8')) self.assertEqual(username, "author") self.assertEqual(domain, "domain") self.assertEqual(signed_data, "http://testserver/test") self.assertEqual(signature_bytes_hex, self.signature) def test_parse_auth_header8(self): from authentication.signature_auth import verify_request request = self.factory.patch('/test', self.data, content_type="application/json", HTTP_AUTHORIZATION="Signature author@domain:" + self.signature_with_data) username, domain, signed_data, signature_bytes_hex = verify_request(request, request.body.decode('utf-8')) self.assertEqual(username, "author") self.assertEqual(domain, "domain") self.assertEqual(signed_data, "http://testserver/test" + self.data) self.assertEqual(signature_bytes_hex, self.signature_with_data) class KnownIdentityTestCase(ToolshedTestCase): key = None def setUp(self): self.key = SigningKey.generate() KnownIdentity.objects.create(username="testuser", domain='external.com', public_key=self.key.verify_key.encode(encoder=HexEncoder).decode('utf-8')) def test_known_identity(self): identity = KnownIdentity.objects.get(username="testuser", domain='external.com') self.assertEqual(identity.username, "testuser") self.assertEqual(identity.domain, "external.com") self.assertEqual(identity.public_key, self.key.verify_key.encode(encoder=HexEncoder).decode('utf-8')) self.assertEqual(str(identity), "testuser@external.com") self.assertTrue(identity.is_authenticated()) def test_known_identity_verify(self): identity = KnownIdentity.objects.get(username="testuser", domain='external.com') message = "Hello world, this is a test message." signed = self.key.sign(message.encode('utf-8'), encoder=HexEncoder).signature self.assertTrue(identity.verify(message, signed.decode('utf-8'))) def test_known_identity_verify_fail(self): identity = KnownIdentity.objects.get(username="testuser", domain='external.com') message = "Hello world, this is a test message." signed = self.key.sign(message.encode('utf-8'), encoder=HexEncoder).signature self.assertFalse(identity.verify(message + "x", signed.decode('utf-8'))) def test_known_identity_verify_fail2(self): identity = KnownIdentity.objects.get(username="testuser", domain='external.com') message = "Hello world, this is a test message." signed = self.key.sign(message.encode('utf-8'), encoder=HexEncoder).signature with self.assertRaises(TypeError): identity.verify(message.encode('utf-8'), signed.decode('utf-8')) def test_known_identity_verify_fail3(self): identity = KnownIdentity.objects.get(username="testuser", domain='external.com') message = "Hello world, this is a test message." signed = self.key.sign(message.encode('utf-8'), encoder=HexEncoder).signature with self.assertRaises(TypeError): identity.verify(message, signed) def test_known_identity_verify_fail4(self): identity = KnownIdentity.objects.get(username="testuser", domain='external.com') message = "Hello world, this is a test message." signed = self.key.sign(message.encode('utf-8'), encoder=HexEncoder).signature with self.assertRaises(TypeError): identity.verify(message, bytes.fromhex(signed.decode('utf-8'))) class UserModelTestCase(UserTestMixin, ToolshedTestCase): def setUp(self): super().setUp() self.prepare_users() def test_admin(self): user = self.f['admin'] self.assertTrue(user.is_superuser) self.assertTrue(user.is_staff) self.assertTrue(user.is_active) self.assertEqual(user.domain, 'localhost') self.assertEqual(user.email, 'testadmin@localhost') self.assertEqual(user.username, 'testadmin') def test_user(self): user = self.f['local_user1'] self.assertFalse(user.is_superuser) self.assertFalse(user.is_staff) self.assertTrue(user.is_active) self.assertEqual(user.domain, 'example.com') self.assertEqual(user.email, 'test1@abc.de') self.assertEqual(user.username, 'testuser1') self.assertEqual(len(user.private_key), 64) self.assertEqual(type(user.public_identity), KnownIdentity) self.assertEqual(user.public_identity.domain, 'example.com') self.assertEqual(user.public_identity.username, 'testuser1') self.assertEqual(len(user.public_identity.public_key), 64) def test_create_existing_user(self): with self.assertRaises(ValueError): ToolshedUser.objects.create_user('testuser1', 'test3@abc.de', '', domain='example.com') def test_create_existing_user2(self): key = SigningKey.generate() KnownIdentity.objects.create(username="testuser3", domain='localhost', public_key=key.verify_key.encode(encoder=HexEncoder).decode('utf-8')) with self.assertRaises(ValueError): ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='localhost') def test_create_reuse_email(self): with self.assertRaises(ValueError): ToolshedUser.objects.create_user('testuser3', 'test1@abc.de', '', domain='example.com') def test_create_user_invalid_private_key(self): with self.assertRaises(TypeError): ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='example.com', private_key=b'0123456789abcdef0123456789abcdef') with self.assertRaises(ValueError): ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='example.com', private_key='7005c4097') with self.assertRaises(ValueError): ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='example.com', private_key='0123456789abcdef0123456789abcdef' 'Z123456789abcdef0123456789abcdef') def test_signature(self): user = self.f['local_user1'] message = 'some message' signature = user.sign(message) self.assertEqual(len(signature), 128) self.assertTrue(user.public_identity.verify(message, signature)) def test_signature_fail(self): user = self.f['local_user1'] message = 'some message' signature = user.sign(message) self.assertFalse(user.public_identity.verify(message + 'x', signature)) def test_signature_fail2(self): user = self.f['local_user1'] message = 'some message' signature = user.sign(message) signature = signature[:-2] + 'ee' self.assertFalse(user.public_identity.verify(message, signature)) def test_signature_fail3(self): user1 = self.f['local_user1'] user2 = self.f['local_user2'] message = 'some message' signature = user1.sign(message) self.assertFalse(user2.public_identity.verify(message, signature)) def test_signature_fail4(self): user = self.f['local_user1'] message = 'some message' with self.assertRaises(TypeError): user.sign(message.encode('utf-8')) class UserApiTestCase(UserTestMixin, ToolshedTestCase): def setUp(self): super().setUp() self.prepare_users() self.anonymous_client = Client(SERVER_NAME='testserver') self.client = SignatureAuthClient() def test_user_info(self): reply = self.client.get('/auth/user/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(reply.json()['username'], 'testuser1') self.assertEqual(reply.json()['domain'], 'example.com') self.assertEqual(reply.json()['email'], 'test1@abc.de') def test_user_info2(self): target = "/auth/user/" signature = self.f['local_user1'].sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.f['local_user1']) + ':' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 200) self.assertEqual(reply.json()['username'], 'testuser1') self.assertEqual(reply.json()['domain'], 'example.com') def test_user_info_fail(self): reply = self.anonymous_client.get('/auth/user/') self.assertEqual(reply.status_code, 403) def test_user_info_fail2(self): reply = self.client.get('/auth/user/', self.f['ext_user1']) self.assertEqual(reply.status_code, 403) def test_user_info_fail3(self): target = "/auth/user/" signature = self.f['local_user1'].sign("http://testserver2" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.f['local_user1']) + ':' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) def test_user_info_fail4(self): target = "/auth/user/" signature = self.f['local_user1'].sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Auth ' + str(self.f['local_user1']) + ':' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) def test_user_info_fail5(self): target = "/auth/user/" signature = self.f['local_user1'].sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.f['local_user1'])} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) def test_user_info_fail6(self): target = "/auth/user/" signature = self.f['local_user1'].sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.f['local_user1']) + ':' + signature + 'f'} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) def test_user_info_fail7(self): target = "/auth/user/" signature = self.f['local_user1'].sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + self.f['local_user1'].username + ':' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) def test_user_info_fail8(self): target = "/auth/user/" signature = self.f['local_user1'].sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + self.f['local_user1'].username + '@:' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) def test_user_info_fail9(self): target = "/auth/user/" signature = self.f['local_user1'].sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Signature @' + self.f['local_user1'].domain + ':' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) class FriendApiTestCase(UserTestMixin, ToolshedTestCase): def setUp(self): super().setUp() self.prepare_users() self.f['local_user1'].friends.add(self.f['local_user2'].public_identity) self.f['local_user1'].friends.add(self.f['ext_user1'].public_identity) self.f['ext_user1'].friends.add(self.f['local_user1'].public_identity) self.anonymous_client = Client(SERVER_NAME='testserver') self.client = SignatureAuthClient() def test_friend_local(self): reply = self.client.get('/api/friends/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) def test_friend_external(self): reply = self.client.get('/api/friends/', self.f['ext_user1']) self.assertEqual(reply.status_code, 200) def test_friend_fail(self): reply = self.anonymous_client.get('/api/friends/') self.assertEqual(reply.status_code, 403) def test_friend_fail2(self): target = "/api/friends/" signature = self.f['local_user1'].sign("http://testserver2" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.f['local_user1']) + ':' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) def test_friend_fail3(self): target = "/api/friends/" unknown_user = DummyExternalUser('extuser3', 'external.org', False) signature = unknown_user.sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + str(unknown_user) + ':' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) def test_friend_fail4(self): target = "/api/friends/" signature = self.f['local_user1'].sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Auth ' + str(self.f['local_user1']) + ':' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) class LoginApiTestCase(UserTestMixin, ToolshedTestCase): user = None client = Client(SERVER_NAME='testserver') def setUp(self): super().setUp() self.prepare_users() self.user = self.f['local_user1'] def test_login(self): reply = self.client.post('/auth/token/', {'username': self.user.username + '@' + self.user.domain, 'password': 'testpassword2'}) self.assertEqual(reply.status_code, 200) self.assertTrue('token' in reply.json()) self.assertTrue(len(reply.json()['token']) == 40) self.assertTrue('key' in reply.json()) self.assertEqual(len(reply.json()['key']), 64) def test_login_fail(self): reply = self.client.post('/auth/token/', {'username': self.user.username + '@' + self.user.domain, 'password': 'testpassword3'}) self.assertEqual(reply.status_code, 400) self.assertTrue('token' not in reply.json()) self.assertTrue('error' in reply.json()) def test_login_fail2(self): reply = self.client.post('/auth/token/', {'username': self.user.username, 'password': 'testpassword2'}) self.assertEqual(reply.status_code, 400) self.assertTrue('token' not in reply.json()) self.assertTrue('error' in reply.json()) class RegistrationApiTestCase(UserTestMixin, ToolshedTestCase): client = Client(SERVER_NAME='testserver') def setUp(self): super().setUp() self.prepare_users() def test_registration(self): self.assertEqual(ToolshedUser.objects.all().count(), 3) reply = self.client.post('/auth/register/', {'username': 'testuser', 'password': 'testpassword2', 'domain': 'example.com', 'email': 'test@abc.de'}) self.assertEqual(reply.status_code, 200) self.assertTrue('username' in reply.json()) self.assertTrue('domain' in reply.json()) user = ToolshedUser.objects.get(username='testuser') self.assertEqual(user.email, 'test@abc.de') self.assertEqual(user.domain, 'example.com') self.assertTrue(user.check_password('testpassword2')) self.assertEqual(ToolshedUser.objects.all().count(), 4) def test_registration_fail(self): reply = self.client.post('/auth/register/', {'username': '', 'password': 'testpassword2', 'domain': 'example.com', 'email': 'test@abc.de'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('username' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_fail2(self): reply = self.client.post('/auth/register/', {'password': 'testpassword2', 'domain': 'example.com', 'email': 'test@abc.de'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('username' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_fail3(self): reply = self.client.post('/auth/register/', {'username': 'testuser', 'password': '', 'domain': 'example.com', 'email': 'test@abc.de'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('password' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_fail4(self): reply = self.client.post('/auth/register/', {'username': 'testuser', 'domain': 'example.com', 'email': 'test@abc.de'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('password' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_fail5(self): reply = self.client.post('/auth/register/', {'username': 'testuser', 'password': 'testpassword2', 'domain': '', 'email': 'test@abc.de'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('domain' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_fail6(self): reply = self.client.post('/auth/register/', {'username': 'testuser', 'password': 'testpassword2', 'email': 'test@abc.de'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('domain' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_fail7(self): reply = self.client.post('/auth/register/', {'username': 'testuser', 'password': 'testpassword2', 'domain': 'example.com', 'email': ''}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('email' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_fail8(self): reply = self.client.post('/auth/register/', {'username': 'testuser', 'password': 'testpassword2', 'domain': 'example.com'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('email' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_existing_user(self): reply = self.client.post('/auth/register/', {'username': 'testuser2', 'password': 'testpassword2', 'domain': 'example.com', 'email': 'test3@abc.de'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) # TODO: check for sensible error message self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_foreign_domain(self): reply = self.client.post('/auth/register/', {'username': 'testuser', 'password': 'testpassword2', 'domain': 'example.org', 'email': 'test@abc.de'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('domain' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3) def test_registration_reuse_email(self): reply = self.client.post('/auth/register/', {'username': 'testuser', 'password': 'testpassword2', 'domain': 'example.com', 'email': 'test2@abc.de'}) self.assertEqual(reply.status_code, 400) self.assertTrue('errors' in reply.json()) self.assertTrue('email' in reply.json()['errors']) self.assertEqual(ToolshedUser.objects.all().count(), 3)