toolshed/backend/authentication/tests/test_auth.py

504 lines
24 KiB
Python
Raw Permalink Normal View History

2023-06-16 15:30:12 +00:00
import json
from django.test import Client, RequestFactory
2023-06-14 23:16:52 +00:00
from nacl.encoding import HexEncoder
from nacl.signing import SigningKey
from authentication.models import ToolshedUser, KnownIdentity
from authentication.tests import UserTestMixin, SignatureAuthClient, DummyExternalUser, ToolshedTestCase
2023-06-14 23:16:52 +00:00
class AuthorizationTestCase(ToolshedTestCase):
2023-06-16 15:30:12 +00:00
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):
2023-06-14 23:16:52 +00:00
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):
2023-06-14 23:16:52 +00:00
def setUp(self):
super().setUp()
self.prepare_users()
2023-06-14 23:16:52 +00:00
def test_admin(self):
user = self.f['admin']
2023-06-14 23:16:52 +00:00
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')
2023-06-14 23:16:52 +00:00
def test_user(self):
user = self.f['local_user1']
2023-06-14 23:16:52 +00:00
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')
2023-06-14 23:16:52 +00:00
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')
2023-06-14 23:16:52 +00:00
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', 'testpassword', domain='example.com')
2023-06-14 23:16:52 +00:00
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', 'testpassword', domain='localhost')
2023-06-14 23:16:52 +00:00
def test_create_reuse_email(self):
with self.assertRaises(ValueError):
ToolshedUser.objects.create_user('testuser3', 'test1@abc.de', 'testpassword', domain='example.com')
2023-06-14 23:16:52 +00:00
def test_create_user_invalid_private_key(self):
with self.assertRaises(TypeError):
2023-06-15 18:55:33 +00:00
ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='example.com',
2023-06-14 23:16:52 +00:00
private_key=b'0123456789abcdef0123456789abcdef')
with self.assertRaises(ValueError):
2023-06-15 18:55:33 +00:00
ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='example.com',
2023-06-14 23:16:52 +00:00
private_key='7005c4097')
with self.assertRaises(ValueError):
2023-06-15 18:55:33 +00:00
ToolshedUser.objects.create_user('testuser3', 'test3@abc.de', '', domain='example.com',
2023-06-14 23:16:52 +00:00
private_key='0123456789abcdef0123456789abcdef'
'Z123456789abcdef0123456789abcdef')
def test_signature(self):
user = self.f['local_user1']
2023-06-14 23:16:52 +00:00
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']
2023-06-14 23:16:52 +00:00
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']
2023-06-14 23:16:52 +00:00
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']
2023-06-14 23:16:52 +00:00
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']
2023-06-14 23:16:52 +00:00
message = 'some message'
with self.assertRaises(TypeError):
user.sign(message.encode('utf-8'))
class UserApiTestCase(UserTestMixin, ToolshedTestCase):
2023-06-14 23:16:52 +00:00
def setUp(self):
super().setUp()
self.prepare_users()
2023-06-14 23:16:52 +00:00
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'])
2023-06-14 23:16:52 +00:00
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json()['username'], 'testuser1')
2023-06-14 23:16:52 +00:00
self.assertEqual(reply.json()['domain'], 'example.com')
self.assertEqual(reply.json()['email'], 'test1@abc.de')
2023-06-14 23:16:52 +00:00
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}
2023-06-14 23:16:52 +00:00
reply = self.anonymous_client.get(target, **header)
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json()['username'], 'testuser1')
2023-06-14 23:16:52 +00:00
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'])
2023-06-14 23:16:52 +00:00
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}
2023-06-14 23:16:52 +00:00
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}
2023-06-14 23:16:52 +00:00
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'])}
2023-06-14 23:16:52 +00:00
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'}
2023-06-14 23:16:52 +00:00
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}
2023-06-14 23:16:52 +00:00
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}
2023-06-14 23:16:52 +00:00
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}
2023-06-14 23:16:52 +00:00
reply = self.anonymous_client.get(target, **header)
self.assertEqual(reply.status_code, 403)
class FriendApiTestCase(UserTestMixin, ToolshedTestCase):
2023-06-22 00:14:52 +00:00
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)
2023-06-22 00:14:52 +00:00
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'])
2023-06-22 00:14:52 +00:00
self.assertEqual(reply.status_code, 200)
def test_friend_external(self):
reply = self.client.get('/api/friends/', self.f['ext_user1'])
2023-06-22 00:14:52 +00:00
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}
2023-06-22 00:14:52 +00:00
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}
2023-06-22 00:14:52 +00:00
reply = self.anonymous_client.get(target, **header)
self.assertEqual(reply.status_code, 403)
class LoginApiTestCase(UserTestMixin, ToolshedTestCase):
2023-06-14 23:16:52 +00:00
user = None
client = Client(SERVER_NAME='testserver')
def setUp(self):
super().setUp()
self.prepare_users()
self.user = self.f['local_user1']
2023-06-14 23:16:52 +00:00
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):
2023-06-14 23:16:52 +00:00
client = Client(SERVER_NAME='testserver')
def setUp(self):
super().setUp()
self.prepare_users()
2023-06-14 23:16:52 +00:00
def test_registration(self):
self.assertEqual(ToolshedUser.objects.all().count(), 3)
2023-06-14 23:16:52 +00:00
reply = self.client.post('/auth/register/',
2023-06-15 18:55:33 +00:00
{'username': 'testuser', 'password': 'testpassword2', 'domain': 'example.com',
2023-06-14 23:16:52 +00:00
'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')
2023-06-15 18:55:33 +00:00
self.assertEqual(user.domain, 'example.com')
2023-06-14 23:16:52 +00:00
self.assertTrue(user.check_password('testpassword2'))
self.assertEqual(ToolshedUser.objects.all().count(), 4)
2023-06-14 23:16:52 +00:00
def test_registration_fail(self):
reply = self.client.post('/auth/register/',
2023-06-15 18:55:33 +00:00
{'username': '', 'password': 'testpassword2', 'domain': 'example.com',
2023-06-14 23:16:52 +00:00
'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)
2023-06-14 23:16:52 +00:00
def test_registration_fail2(self):
reply = self.client.post('/auth/register/',
2023-06-15 18:55:33 +00:00
{'password': 'testpassword2', 'domain': 'example.com', 'email': 'test@abc.de'})
2023-06-14 23:16:52 +00:00
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)
2023-06-14 23:16:52 +00:00
def test_registration_fail3(self):
reply = self.client.post('/auth/register/',
2023-06-15 18:55:33 +00:00
{'username': 'testuser', 'password': '', 'domain': 'example.com',
2023-06-14 23:16:52 +00:00
'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)
2023-06-14 23:16:52 +00:00
def test_registration_fail4(self):
reply = self.client.post('/auth/register/',
2023-06-15 18:55:33 +00:00
{'username': 'testuser', 'domain': 'example.com', 'email': 'test@abc.de'})
2023-06-14 23:16:52 +00:00
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)
2023-06-14 23:16:52 +00:00
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)
2023-06-14 23:16:52 +00:00
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)
2023-06-14 23:16:52 +00:00
def test_registration_fail7(self):
reply = self.client.post('/auth/register/',
2023-06-15 18:55:33 +00:00
{'username': 'testuser', 'password': 'testpassword2', 'domain': 'example.com',
2023-06-14 23:16:52 +00:00
'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)
2023-06-14 23:16:52 +00:00
def test_registration_fail8(self):
reply = self.client.post('/auth/register/',
2023-06-15 18:55:33 +00:00
{'username': 'testuser', 'password': 'testpassword2', 'domain': 'example.com'})
2023-06-14 23:16:52 +00:00
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)
2023-06-14 23:16:52 +00:00
def test_registration_existing_user(self):
reply = self.client.post('/auth/register/',
2023-06-15 18:55:33 +00:00
{'username': 'testuser2', 'password': 'testpassword2', 'domain': 'example.com',
2023-06-14 23:16:52 +00:00
'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)
2023-06-14 23:16:52 +00:00
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)
2023-06-14 23:16:52 +00:00
def test_registration_reuse_email(self):
reply = self.client.post('/auth/register/',
2023-06-15 18:55:33 +00:00
{'username': 'testuser', 'password': 'testpassword2', 'domain': 'example.com',
2023-06-14 23:16:52 +00:00
'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)