""" Unit tests for the Deis api app. Run the tests with "./manage.py test api" """ from django.contrib.auth.models import User from django.test.utils import override_settings from rest_framework.authtoken.models import Token from unittest import mock from api.tests import TEST_ROOT, DeisTestCase from api.models import Certificate class AuthTest(DeisTestCase): fixtures = ['test_auth.json'] """Tests user registration, authentication and authorization""" def setUp(self): self.admin = User.objects.get(username='autotest') self.admin_token = Token.objects.get(user=self.admin).key self.user1 = User.objects.get(username='autotest2') self.user1_token = Token.objects.get(user=self.user1).key self.user2 = User.objects.get(username='autotest3') self.user2_token = Token.objects.get(user=self.user2).key def test_auth(self): """ Test that a user can register using the API, login, whoami and logout """ # test registration workflow username, password = 'newuser', 'password' first_name, last_name = 'Otto', 'Test' email = 'autotest@deis.io' submit = { 'username': username, 'password': password, 'first_name': first_name, 'last_name': last_name, 'email': email, # try to abuse superuser/staff level perms (not the first signup!) 'is_superuser': True, 'is_staff': True, } url = '/v2/auth/register' response = self.client.post(url, submit) self.assertEqual(response.status_code, 201, response.data) for key in response.data: self.assertIn(key, ['id', 'last_login', 'is_superuser', 'username', 'first_name', 'last_name', 'email', 'is_active', 'is_superuser', 'is_staff', 'date_joined', 'groups', 'user_permissions']) expected = { 'username': username, 'email': email, 'first_name': first_name, 'last_name': last_name, 'is_active': True, 'is_superuser': False, 'is_staff': False } self.assertDictContainsSubset(expected, response.data) # test login response = self.client.login(username=username, password=password) self.assertEqual(response, True) user = User.objects.get(username=username) token = Token.objects.get(user=user).key url = '/v2/auth/whoami' response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(response.status_code, 200) for key in response.data: self.assertIn(key, ['id', 'last_login', 'is_superuser', 'username', 'first_name', 'last_name', 'email', 'is_active', 'is_superuser', 'is_staff', 'date_joined', 'groups', 'user_permissions']) expected = { 'username': username, 'email': email, 'first_name': first_name, 'last_name': last_name, 'is_active': True, 'is_superuser': False, 'is_staff': False } self.assertDictContainsSubset(expected, response.data) @override_settings(REGISTRATION_MODE="disabled") def test_auth_registration_disabled(self): """test that a new user cannot register when registration is disabled.""" url = '/v2/auth/register' submit = { 'username': 'testuser', 'password': 'password', 'first_name': 'test', 'last_name': 'user', 'email': 'test@user.com', 'is_superuser': False, 'is_staff': False, } response = self.client.post(url, submit) self.assertEqual(response.status_code, 403) @override_settings(REGISTRATION_MODE="admin_only") def test_auth_registration_admin_only_fails_if_not_admin(self): """test that a non superuser cannot register when registration is admin only.""" url = '/v2/auth/register' submit = { 'username': 'testuser', 'password': 'password', 'first_name': 'test', 'last_name': 'user', 'email': 'test@user.com', 'is_superuser': False, 'is_staff': False, } response = self.client.post(url, submit) self.assertEqual(response.status_code, 403) @override_settings(REGISTRATION_MODE="admin_only") def test_auth_registration_admin_only_works(self): """test that a superuser can register when registration is admin only.""" url = '/v2/auth/register' username, password = 'newuser_by_admin', 'password' first_name, last_name = 'Otto', 'Test' email = 'autotest@deis.io' submit = { 'username': username, 'password': password, 'first_name': first_name, 'last_name': last_name, 'email': email, # try to abuse superuser/staff level perms (not the first signup!) 'is_superuser': True, 'is_staff': True, } response = self.client.post(url, submit, HTTP_AUTHORIZATION='token {}'.format(self.admin_token)) self.assertEqual(response.status_code, 201, response.data) for key in response.data: self.assertIn(key, ['id', 'last_login', 'is_superuser', 'username', 'first_name', 'last_name', 'email', 'is_active', 'is_superuser', 'is_staff', 'date_joined', 'groups', 'user_permissions']) expected = { 'username': username, 'email': email, 'first_name': first_name, 'last_name': last_name, 'is_active': True, 'is_superuser': False, 'is_staff': False } self.assertDictContainsSubset(expected, response.data) # test login response = self.client.login(username=username, password=password) self.assertEqual(response, True) @override_settings(REGISTRATION_MODE="not_a_mode") def test_auth_registration_fails_with_nonexistant_mode(self): """test that a registration should fail with a nonexistant mode""" url = '/v2/auth/register' submit = { 'username': 'testuser', 'password': 'password', 'first_name': 'test', 'last_name': 'user', 'email': 'test@user.com', 'is_superuser': False, 'is_staff': False, } try: self.client.post(url, submit) except Exception as e: self.assertEqual(str(e), 'not_a_mode is not a valid registation mode') def test_cancel(self): """Test that a registered user can cancel her account.""" # test registration workflow username, password = 'newuser', 'password' submit = { 'username': username, 'password': password, 'first_name': 'Otto', 'last_name': 'Test', 'email': 'autotest@deis.io', # try to abuse superuser/staff level perms 'is_superuser': True, 'is_staff': True, } other_username, other_password = 'newuser2', 'password' other_submit = { 'username': other_username, 'password': other_password, 'first_name': 'Test', 'last_name': 'Tester', 'email': 'autotest-2@deis.io', 'is_superuser': False, 'is_staff': False, } url = '/v2/auth/register' response = self.client.post(url, submit) self.assertEqual(response.status_code, 201, response.data) # cancel the account url = '/v2/auth/cancel' user = User.objects.get(username=username) token = Token.objects.get(user=user).key response = self.client.delete(url, HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(response.status_code, 204, response.data) url = '/v2/auth/register' response = self.client.post(url, other_submit) self.assertEqual(response.status_code, 201, response.data) # normal user can't delete another user url = '/v2/auth/cancel' other_user = User.objects.get(username=other_username) other_token = Token.objects.get(user=other_user).key response = self.client.delete(url, {'username': self.admin.username}, HTTP_AUTHORIZATION='token {}'.format(other_token)) self.assertEqual(response.status_code, 403) # admin can delete another user response = self.client.delete(url, {'username': other_username}, HTTP_AUTHORIZATION='token {}'.format(self.admin_token)) self.assertEqual(response.status_code, 204, response.data) # user can not be deleted if it has an app attached to it response = self.client.post( '/v2/apps', HTTP_AUTHORIZATION='token {}'.format(self.admin_token) ) self.assertEqual(response.status_code, 201, response.data) app_id = response.data['id'] # noqa self.assertIn('id', response.data) response = self.client.delete(url, {'username': str(self.admin)}, HTTP_AUTHORIZATION='token {}'.format(self.admin_token)) self.assertEqual(response.status_code, 409) # user can not be deleted if it has a downstream object owned by them, like a certificate domain_name = 'foo.com' with open('{}/certs/{}.key'.format(TEST_ROOT, domain_name)) as f: key = f.read() with open('{}/certs/{}.cert'.format(TEST_ROOT, domain_name)) as f: cert = f.read() Certificate.objects.create(owner=self.admin, certificate=cert, key=key) response = self.client.delete(url, {'username': str(self.admin)}, HTTP_AUTHORIZATION='token {}'.format(self.admin_token)) self.assertEqual(response.status_code, 409, response.data) def test_passwd(self): """Test that a registered user can change the password.""" # test registration workflow username, password = 'newuser', 'password' first_name, last_name = 'Otto', 'Test' email = 'autotest@deis.io' submit = { 'username': username, 'password': password, 'first_name': first_name, 'last_name': last_name, 'email': email, } url = '/v2/auth/register' response = self.client.post(url, submit) self.assertEqual(response.status_code, 201, response.data) # change password without new password url = '/v2/auth/passwd' user = User.objects.get(username=username) token = Token.objects.get(user=user).key response = self.client.post(url, {}, HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(response.status_code, 400, response.data) self.assertEqual(response.data, {'detail': 'new_password is a required field'}) # change password without password field response = self.client.post(url, {'new_password': 'test'}, HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(response.status_code, 400, response.data) self.assertEqual(response.data, {'detail': 'password is a required field'}) # change password submit = { 'password': 'password2', 'new_password': password, } response = self.client.post(url, submit, HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(response.status_code, 401, response.data) self.assertEqual(response.data, {'detail': 'Current password does not match'}) self.assertEqual(response.get('content-type'), 'application/json') submit = { 'password': password, 'new_password': 'password2', } response = self.client.post(url, submit, HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(response.status_code, 200, response.data) # test login with old password response = self.client.login(username=username, password=password) self.assertEqual(response, False) # test login with new password response = self.client.login(username=username, password='password2') self.assertEqual(response, True) def test_change_user_passwd(self): """ Test that an administrator can change a user's password, while a regular user cannot. """ # change password url = '/v2/auth/passwd' old_password = self.user1.password new_password = 'password' submit = { 'username': self.user1.username, 'new_password': new_password, } response = self.client.post(url, submit, HTTP_AUTHORIZATION='token {}'.format(self.admin_token)) self.assertEqual(response.status_code, 200, response.data) # test login with old password response = self.client.login(username=self.user1.username, password=old_password) self.assertEqual(response, False) # test login with new password response = self.client.login(username=self.user1.username, password=new_password) self.assertEqual(response, True) # Non-admins can't change another user's password submit['password'], submit['new_password'] = submit['new_password'], old_password url = '/v2/auth/passwd' response = self.client.post(url, submit, HTTP_AUTHORIZATION='token {}'.format(self.user2_token)) self.assertEqual(response.status_code, 403) # change back password with a regular user response = self.client.post(url, submit, HTTP_AUTHORIZATION='token {}'.format(self.user1_token)) self.assertEqual(response.status_code, 200, response.data) # test login with new password response = self.client.login(username=self.user1.username, password=old_password) self.assertEqual(response, True) def test_regenerate(self): """ Test that token regeneration works""" url = '/v2/auth/tokens/' self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.admin_token) response = self.client.post(url, {}) self.assertEqual(response.status_code, 200, response.data) self.assertNotEqual(response.data['token'], self.admin_token) self.admin_token = Token.objects.get(user=self.admin).key self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.admin_token) response = self.client.post(url, {"username": "autotest2"}) self.assertEqual(response.status_code, 200, response.data) self.assertNotEqual(response.data['token'], self.user1_token) response = self.client.post(url, {"all": "true"}) self.assertEqual(response.status_code, 200, response.data) response = self.client.post(url, {}) self.assertEqual(response.status_code, 401, response.data) @mock.patch('django_auth_ldap.backend.logger') def test_auth_no_ldap_by_default(self, mock_logger): """Ensure that LDAP authentication is disabled by default.""" self.test_auth() # NOTE(bacongobbler): Using https://github.com/deis/controller/issues/1189 as a test case mock_logger.warning.assert_not_called()