add authentication app

This commit is contained in:
j3d1 2023-06-15 01:16:52 +02:00
parent 5f68e12f82
commit 12191369b7
12 changed files with 882 additions and 0 deletions

View file

@ -0,0 +1 @@
from .helpers import *

View file

@ -0,0 +1,84 @@
import json
from django.test import TestCase, Client
from nacl.encoding import HexEncoder
from authentication.models import ToolshedUser, KnownIdentity
from nacl.signing import SigningKey
class DummyExternalUser:
def __init__(self, username, domain, known=True):
self.username = username
self.domain = domain
self.__signing_key = SigningKey.generate()
self.public_identity, _ = KnownIdentity.objects.get_or_create(
username=username,
domain=domain,
public_key=self.public_key()) if known else None
def __str__(self):
return self.username + '@' + self.domain
def sign(self, message):
return self.__signing_key.sign(message.encode('utf-8'), encoder=HexEncoder).signature.decode('utf-8')
def public_key(self):
return self.__signing_key.verify_key.encode(encoder=HexEncoder).decode('utf-8')
@property
def friends(self):
return self.public_identity.friends
class SignatureAuthClient:
base = Client(SERVER_NAME='testserver')
def get(self, target, user, **kwargs):
signature = user.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(user) + ':' + signature}
return self.base.get(target, **header, **kwargs)
def post(self, target, user, data, **kwargs):
json_data = json.dumps(data, separators=(',', ':'))
signature = user.sign("http://testserver" + target + json_data)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(user) + ':' + signature}
return self.base.post(target, json_data, **header, content_type='application/json', **kwargs)
def put(self, target, user, data, **kwargs):
json_data = json.dumps(data, separators=(',', ':'))
signature = user.sign("http://testserver" + target + json_data)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(user) + ':' + signature}
return self.base.put(target, json_data, **header, content_type='application/json', **kwargs)
def patch(self, target, user, data, **kwargs):
json_data = json.dumps(data, separators=(',', ':'))
signature = user.sign("http://testserver" + target + json_data)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(user) + ':' + signature}
return self.base.patch(target, json_data, **header, content_type='application/json', **kwargs)
def delete(self, target, user, **kwargs):
signature = user.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(user) + ':' + signature}
return self.base.delete(target, **header, **kwargs)
class UserTestCase(TestCase):
ext_user1 = None
ext_user2 = None
local_user1 = None
local_user2 = None
def setUp(self):
admin = ToolshedUser.objects.create_superuser('admin', 'admin@localhost', '')
admin.set_password('testpassword')
admin.save()
example_com = type('obj', (object,), {'name': 'example.com'})
self.local_user1 = ToolshedUser.objects.create_user('testuser', 'test@abc.de', '', domain=example_com.name)
self.local_user1.set_password('testpassword2')
self.local_user1.save()
self.local_user2 = ToolshedUser.objects.create_user('testuser2', 'test2@abc.de', '', domain=example_com.name)
self.local_user2.set_password('testpassword3')
self.local_user2.save()
self.ext_user1 = DummyExternalUser('extuser1', 'external.org')
self.ext_user2 = DummyExternalUser('extuser2', 'external.org')

View file

@ -0,0 +1,379 @@
from django.test import TestCase, Client
from nacl.encoding import HexEncoder
from nacl.signing import SigningKey
from authentication.models import ToolshedUser, KnownIdentity
from authentication.tests import UserTestCase, SignatureAuthClient
class KnownIdentityTestCase(TestCase):
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(UserTestCase):
def setUp(self):
super().setUp()
def test_admin(self):
user = ToolshedUser.objects.get(username='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, 'admin@localhost')
self.assertEqual(user.username, 'admin')
def test_user(self):
user = ToolshedUser.objects.get(username='testuser')
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, 'test@abc.de')
self.assertEqual(user.username, 'testuser')
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, 'testuser')
self.assertEqual(len(user.public_identity.public_key), 64)
def test_create_existing_user(self):
with self.assertRaises(ValueError):
ToolshedUser.objects.create_user('testuser', 'test3@abc.de', '', domain='localhost')
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', 'test@abc.de', '', domain='localhost')
def test_create_user_invalid_private_key(self):
with self.assertRaises(TypeError):
ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='localhost',
private_key=b'0123456789abcdef0123456789abcdef')
with self.assertRaises(ValueError):
ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='localhost',
private_key='7005c4097')
with self.assertRaises(ValueError):
ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='localhost',
private_key='0123456789abcdef0123456789abcdef'
'Z123456789abcdef0123456789abcdef')
def test_signature(self):
user = self.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.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.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.local_user1
user2 = self.local_user2
message = 'some message'
signature = user1.sign(message)
self.assertFalse(user2.public_identity.verify(message, signature))
def test_signature_fail4(self):
user = self.local_user1
message = 'some message'
with self.assertRaises(TypeError):
user.sign(message.encode('utf-8'))
class UserApiTestCase(UserTestCase):
def setUp(self):
super().setUp()
self.anonymous_client = Client(SERVER_NAME='testserver')
self.client = SignatureAuthClient()
def test_user_info(self):
reply = self.client.get('/auth/user/', self.local_user1)
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json()['username'], 'testuser')
self.assertEqual(reply.json()['domain'], 'example.com')
self.assertEqual(reply.json()['email'], 'test@abc.de')
def test_user_info2(self):
target = "/auth/user/"
signature = self.local_user1.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.local_user1) + ':' + signature}
reply = self.anonymous_client.get(target, **header)
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json()['username'], 'testuser')
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.ext_user1)
self.assertEqual(reply.status_code, 403)
def test_user_info_fail3(self):
target = "/auth/user/"
signature = self.local_user1.sign("http://testserver2" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.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.local_user1.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Auth ' + str(self.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.local_user1.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.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.local_user1.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.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.local_user1.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + self.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.local_user1.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + self.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.local_user1.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Signature @' + self.local_user1.domain + ':' + signature}
reply = self.anonymous_client.get(target, **header)
self.assertEqual(reply.status_code, 403)
class LoginApiTestCase(UserTestCase):
user = None
client = Client(SERVER_NAME='testserver')
def setUp(self):
super().setUp()
self.user = self.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(TestCase):
client = Client(SERVER_NAME='testserver')
def setUp(self):
admin = ToolshedUser.objects.create_superuser('admin', 'admin@localhost', '')
admin.set_password('testpassword')
admin.save()
example_com = type('obj', (object,), {'name': 'localhost'})
user2 = ToolshedUser.objects.create_user('testuser2', 'test2@abc.de', '', domain=example_com.name)
user2.set_password('testpassword3')
user2.save()
def test_registration(self):
self.assertEqual(ToolshedUser.objects.all().count(), 2)
reply = self.client.post('/auth/register/',
{'username': 'testuser', 'password': 'testpassword2', 'domain': 'localhost',
'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, 'localhost')
self.assertTrue(user.check_password('testpassword2'))
self.assertEqual(ToolshedUser.objects.all().count(), 3)
def test_registration_fail(self):
reply = self.client.post('/auth/register/',
{'username': '', 'password': 'testpassword2', 'domain': 'localhost',
'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(), 2)
def test_registration_fail2(self):
reply = self.client.post('/auth/register/',
{'password': 'testpassword2', 'domain': 'localhost', '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(), 2)
def test_registration_fail3(self):
reply = self.client.post('/auth/register/',
{'username': 'testuser', 'password': '', 'domain': 'localhost',
'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(), 2)
def test_registration_fail4(self):
reply = self.client.post('/auth/register/',
{'username': 'testuser', 'domain': 'localhost', '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(), 2)
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(), 2)
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(), 2)
def test_registration_fail7(self):
reply = self.client.post('/auth/register/',
{'username': 'testuser', 'password': 'testpassword2', 'domain': 'localhost',
'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(), 2)
def test_registration_fail8(self):
reply = self.client.post('/auth/register/',
{'username': 'testuser', 'password': 'testpassword2', 'domain': 'localhost'})
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(), 2)
def test_registration_existing_user(self):
reply = self.client.post('/auth/register/',
{'username': 'testuser2', 'password': 'testpassword2', 'domain': 'localhost',
'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(), 2)
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(), 2)
def test_registration_reuse_email(self):
reply = self.client.post('/auth/register/',
{'username': 'testuser', 'password': 'testpassword2', 'domain': 'localhost',
'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(), 2)