""" Tests for the LTI user management functionality """ import string from unittest.mock import MagicMock, PropertyMock, patch import pytest from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.exceptions import PermissionDenied from django.test import TestCase from django.test.client import RequestFactory from common.djangoapps.student.tests.factories import UserFactory from .. import users from ..models import LtiConsumer, LtiUser class UserManagementHelperTest(TestCase): """ Tests for the helper functions in users.py """ def setUp(self): super().setUp() self.request = RequestFactory().post('/') self.old_user = UserFactory.create() self.new_user = UserFactory.create() self.new_user.save() self.request.user = self.old_user self.lti_consumer = LtiConsumer( consumer_name='TestConsumer', consumer_key='TestKey', consumer_secret='TestSecret' ) self.lti_consumer.save() self.lti_user = LtiUser( lti_user_id='lti_user_id', edx_user=self.new_user ) @patch('django.contrib.auth.authenticate', return_value=None) def test_permission_denied_for_unknown_user(self, _authenticate_mock): with pytest.raises(PermissionDenied): users.switch_user(self.request, self.lti_user, self.lti_consumer) @patch('lms.djangoapps.lti_provider.users.login') def test_authenticate_called(self, _login_mock): with patch('lms.djangoapps.lti_provider.users.authenticate', return_value=self.new_user) as authenticate: users.switch_user(self.request, self.lti_user, self.lti_consumer) authenticate.assert_called_with( username=self.new_user.username, lti_user_id=self.lti_user.lti_user_id, lti_consumer=self.lti_consumer ) @patch('lms.djangoapps.lti_provider.users.login') def test_login_called(self, login_mock): with patch('lms.djangoapps.lti_provider.users.authenticate', return_value=self.new_user): users.switch_user(self.request, self.lti_user, self.lti_consumer) login_mock.assert_called_with(self.request, self.new_user) def test_random_username_generator(self): for _idx in range(1000): username = users.generate_random_edx_username() assert len(username) <= 30, 'Username too long' # Check that the username contains only allowable characters for char in range(len(username)): # lint-amnesty, pylint: disable=consider-using-enumerate assert username[char] in (string.ascii_letters + string.digits), \ "Username has forbidden character '{}'".format(username[char]) @patch('lms.djangoapps.lti_provider.users.switch_user', autospec=True) @patch('lms.djangoapps.lti_provider.users.create_lti_user', autospec=True) class AuthenticateLtiUserTest(TestCase): """ Tests for the authenticate_lti_user function in users.py """ def setUp(self): super().setUp() self.lti_consumer = LtiConsumer( consumer_name='TestConsumer', consumer_key='TestKey', consumer_secret='TestSecret' ) self.lti_consumer.save() self.lti_user_id = 'lti_user_id' self.edx_user_id = 'edx_user_id' self.old_user = UserFactory.create() self.request = RequestFactory().post('/') self.request.user = self.old_user def create_lti_user_model(self): """ Generate and save a User and an LTI user model """ edx_user = User(username=self.edx_user_id) edx_user.save() lti_user = LtiUser( lti_consumer=self.lti_consumer, lti_user_id=self.lti_user_id, edx_user=edx_user ) lti_user.save() return lti_user def test_authentication_with_new_user(self, _create_user, switch_user): lti_user = MagicMock() lti_user.edx_user_id = self.edx_user_id with patch('lms.djangoapps.lti_provider.users.create_lti_user', return_value=lti_user) as create_user: users.authenticate_lti_user(self.request, self.lti_user_id, self.lti_consumer) create_user.assert_called_with(self.lti_user_id, self.lti_consumer) switch_user.assert_called_with(self.request, lti_user, self.lti_consumer) def test_authentication_with_authenticated_user(self, create_user, switch_user): lti_user = self.create_lti_user_model() self.request.user = lti_user.edx_user assert self.request.user.is_authenticated users.authenticate_lti_user(self.request, self.lti_user_id, self.lti_consumer) assert not create_user.called assert not switch_user.called def test_authentication_with_unauthenticated_user(self, create_user, switch_user): lti_user = self.create_lti_user_model() self.request.user = lti_user.edx_user with patch('django.contrib.auth.models.User.is_authenticated', new_callable=PropertyMock) as mock_is_auth: mock_is_auth.return_value = False users.authenticate_lti_user(self.request, self.lti_user_id, self.lti_consumer) assert not create_user.called switch_user.assert_called_with(self.request, lti_user, self.lti_consumer) def test_authentication_with_wrong_user(self, create_user, switch_user): lti_user = self.create_lti_user_model() self.request.user = self.old_user assert self.request.user.is_authenticated users.authenticate_lti_user(self.request, self.lti_user_id, self.lti_consumer) assert not create_user.called switch_user.assert_called_with(self.request, lti_user, self.lti_consumer) class CreateLtiUserTest(TestCase): """ Tests for the create_lti_user function in users.py """ def setUp(self): super().setUp() self.lti_consumer = LtiConsumer( consumer_name='TestConsumer', consumer_key='TestKey', consumer_secret='TestSecret' ) self.lti_consumer.save() def test_create_lti_user_creates_auth_user_model(self): users.create_lti_user('lti_user_id', self.lti_consumer) assert User.objects.count() == 1 @patch('uuid.uuid4', return_value='random_uuid') @patch('lms.djangoapps.lti_provider.users.generate_random_edx_username', return_value='edx_id') def test_create_lti_user_creates_correct_user(self, uuid_mock, _username_mock): users.create_lti_user('lti_user_id', self.lti_consumer) assert User.objects.count() == 1 user = User.objects.get(username='edx_id') assert user.email == 'edx_id@lti.example.com' uuid_mock.assert_called_with() @patch('lms.djangoapps.lti_provider.users.generate_random_edx_username', side_effect=['edx_id', 'new_edx_id']) def test_unique_username_created(self, username_mock): User(username='edx_id').save() users.create_lti_user('lti_user_id', self.lti_consumer) assert username_mock.call_count == 2 assert User.objects.count() == 2 user = User.objects.get(username='new_edx_id') assert user.email == 'new_edx_id@lti.example.com' class LtiBackendTest(TestCase): """ Tests for the authentication backend that authenticates LTI users. """ def setUp(self): super().setUp() self.edx_user = UserFactory.create() self.edx_user.save() self.lti_consumer = LtiConsumer( consumer_key="Consumer Key", consumer_secret="Consumer Secret" ) self.lti_consumer.save() self.lti_user_id = 'LTI User ID' LtiUser( lti_consumer=self.lti_consumer, lti_user_id=self.lti_user_id, edx_user=self.edx_user ).save() self.old_user = UserFactory.create() self.request = RequestFactory().post('/') self.request.user = self.old_user def test_valid_user_authenticates(self): user = users.LtiBackend().authenticate( self.request, username=self.edx_user.username, lti_user_id=self.lti_user_id, lti_consumer=self.lti_consumer ) assert user == self.edx_user def test_missing_user_returns_none(self): user = users.LtiBackend().authenticate( self.request, username=self.edx_user.username, lti_user_id='Invalid Username', lti_consumer=self.lti_consumer ) assert user is None def test_non_lti_user_returns_none(self): non_edx_user = UserFactory.create() non_edx_user.save() user = users.LtiBackend().authenticate( self.request, username=non_edx_user.username, ) assert user is None def test_missing_lti_id_returns_null(self): user = users.LtiBackend().authenticate( self.request, username=self.edx_user.username, lti_consumer=self.lti_consumer ) assert user is None def test_missing_lti_consumer_returns_null(self): user = users.LtiBackend().authenticate( self.request, username=self.edx_user.username, lti_user_id=self.lti_user_id, ) assert user is None def test_existing_user_returned_by_get_user(self): user = users.LtiBackend().get_user(self.edx_user.id) assert user == self.edx_user def test_get_user_returns_none_for_invalid_user(self): user = users.LtiBackend().get_user(-1) assert user is None