Exemple #1
0
class CoursesLTIAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(CoursesLTIAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.lti_data = LTITestData()

    def test_delete_course(self):
        # test unlinking of lti contexts when course deleted
        course = self.data.get_course()
        url = '/api/courses/' + course.uuid

        lti_consumer = self.lti_data.get_consumer()
        lti_context1 = self.lti_data.create_context(
            lti_consumer, compair_course_id=course.id)
        lti_context2 = self.lti_data.create_context(
            lti_consumer, compair_course_id=course.id)
        lti_resource_link1 = self.lti_data.create_resource_link(
            lti_consumer,
            lti_context=lti_context2,
            compair_assignment=self.data.assignments[0])
        lti_resource_link2 = self.lti_data.create_resource_link(
            lti_consumer,
            lti_context=lti_context2,
            compair_assignment=self.data.assignments[1])

        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(course.uuid, rv.json['id'])

            self.assertIsNone(lti_context1.compair_course_id)
            self.assertIsNone(lti_context2.compair_course_id)
            self.assertIsNone(lti_resource_link1.compair_assignment_id)
            self.assertIsNone(lti_resource_link2.compair_assignment_id)
Exemple #2
0
    def setUp(self):
        super(ImpersonationAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.course_base_url = '/api/courses'
        self.assignment_base_url = self.course_base_url + '/' + self.data.get_course(
        ).uuid + '/assignments'
        self.impersonate_base_url = '/api/impersonate'
        self.profile_base_url = '/api/users'
        self.session_base_url = '/api/session'

        self.assignment_main_course = self.data.get_assignments()[0]
        self.student_main_course = self.data.get_authorized_student()
        self.student_second_course = self.data.get_unauthorized_student()
        self.instructor_main_course = self.data.get_authorized_instructor()
        self.instructor_second_course = self.data.get_unauthorized_instructor(
        )
Exemple #3
0
class CoursesLTIAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(CoursesLTIAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.lti_data = LTITestData()

    def test_delete_course(self):
        # test unlinking of lti contexts when course deleted
        course = self.data.get_course()
        url = '/api/courses/' + course.uuid

        lti_consumer = self.lti_data.get_consumer()
        lti_context1 = self.lti_data.create_context(
            lti_consumer,
            compair_course_id=course.id
        )
        lti_context2 = self.lti_data.create_context(
            lti_consumer,
            compair_course_id=course.id
        )
        lti_resource_link1 = self.lti_data.create_resource_link(
            lti_consumer,
            lti_context=lti_context2,
            compair_assignment=self.data.assignments[0]
        )
        lti_resource_link2 = self.lti_data.create_resource_link(
            lti_consumer,
            lti_context=lti_context2,
            compair_assignment=self.data.assignments[1]
        )

        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(course.uuid, rv.json['id'])

            self.assertIsNone(lti_context1.compair_course_id)
            self.assertIsNone(lti_context2.compair_course_id)
            self.assertIsNone(lti_resource_link1.compair_assignment_id)
            self.assertIsNone(lti_resource_link2.compair_assignment_id)
Exemple #4
0
    def setUp(self):
        super(ImpersonationAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.course_base_url = '/api/courses'
        self.assignment_base_url = self.course_base_url + '/' + self.data.get_course().uuid + '/assignments'
        self.impersonate_base_url = '/api/impersonate'
        self.profile_base_url = '/api/users'
        self.session_base_url = '/api/session'

        self.assignment_main_course = self.data.get_assignments()[0]
        self.student_main_course = self.data.get_authorized_student();
        self.student_second_course = self.data.get_unauthorized_student();
        self.instructor_main_course = self.data.get_authorized_instructor();
        self.instructor_second_course = self.data.get_unauthorized_instructor();
Exemple #5
0
class LoginAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(LoginAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()

    def test_app_login(self):
        params = {'username': '******', 'password': '******'}
        # test app login disabled
        self.app.config['APP_LOGIN_ENABLED'] = False
        rv = self.client.post('/api/login',
                              data=json.dumps(params),
                              content_type='application/json',
                              follow_redirects=True)
        self.assert403(rv)

        # test app login enabled
        self.app.config['APP_LOGIN_ENABLED'] = True
        rv = self.client.post('/api/login',
                              data=json.dumps(params),
                              content_type='application/json',
                              follow_redirects=True)
        self.assert200(rv)

    def test_cas_login(self):
        auth_data = ThirdPartyAuthTestData()
        user = self.data.create_user(SystemRole.instructor)
        third_party_user = auth_data.create_third_party_user(user=user)

        response_mock = mock.MagicMock()
        response_mock.success = True
        response_mock.user = third_party_user.unique_identifier
        response_mock.attributes = None

        with mock.patch('compair.api.login.validate_cas_ticket',
                        return_value=response_mock):
            # test cas login disabled
            self.app.config['CAS_LOGIN_ENABLED'] = False
            rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                 follow_redirects=True)
            self.assert403(rv)

            # test cas login enabled
            self.app.config['CAS_LOGIN_ENABLED'] = True
            rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                 follow_redirects=True)
            self.assert200(rv)
Exemple #6
0
class LoginAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(LoginAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()

    def test_app_login(self):
        params = {
            'username': '******',
            'password': '******'
        }
        # test app login disabled
        self.app.config['APP_LOGIN_ENABLED'] = False
        rv = self.client.post('/api/login', data=json.dumps(params), content_type='application/json', follow_redirects=True)
        self.assert403(rv)

        # test app login enabled
        self.app.config['APP_LOGIN_ENABLED'] = True
        rv = self.client.post('/api/login', data=json.dumps(params), content_type='application/json', follow_redirects=True)
        self.assert200(rv)

    def test_cas_login(self):
        auth_data = ThirdPartyAuthTestData()
        user = self.data.create_user(SystemRole.instructor)
        third_party_user = auth_data.create_third_party_user(user=user)

        with mock.patch('flask_cas.CAS.username', new_callable=mock.PropertyMock) as mocked_cas_username:
            # test cas login disabled
            self.app.config['CAS_LOGIN_ENABLED'] = False
            mocked_cas_username.return_value = third_party_user.unique_identifier
            rv = self.client.get('/api/auth/cas', data={}, content_type='application/json', follow_redirects=True)
            self.assert403(rv)

            # test cas login enabled
            self.app.config['CAS_LOGIN_ENABLED'] = True
            mocked_cas_username.return_value = third_party_user.unique_identifier
            rv = self.client.get('/api/auth/cas', data={}, content_type='application/json', follow_redirects=True)
            self.assert200(rv)
Exemple #7
0
class ImpersonationAPITests(ComPAIRAPITestCase):

    def setUp(self):
        super(ImpersonationAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.course_base_url = '/api/courses'
        self.assignment_base_url = self.course_base_url + '/' + self.data.get_course().uuid + '/assignments'
        self.impersonate_base_url = '/api/impersonate'
        self.profile_base_url = '/api/users'
        self.session_base_url = '/api/session'

        self.assignment_main_course = self.data.get_assignments()[0]
        self.student_main_course = self.data.get_authorized_student();
        self.student_second_course = self.data.get_unauthorized_student();
        self.instructor_main_course = self.data.get_authorized_instructor();
        self.instructor_second_course = self.data.get_unauthorized_instructor();

    def _start_impersonation(self, pretending):
        return self.client.post(self.impersonate_base_url + '/' + pretending.uuid)

    def _end_impersonation(self):
        return self.client.delete(self.impersonate_base_url)

    def _verify_impersonation(self, original=None, pretending=None):
        rv = self.client.get(self.impersonate_base_url)
        self.assert200(rv)
        self.assertEquals(rv.json.get('impersonating'), True)
        if original:
            self.assertEquals(rv.json.get('original_user').get('id'), original.uuid)
        if pretending:
            rv = self.client.get(self.session_base_url)
            self.assert200(rv)
            self.assertEqual(rv.json.get('id'), pretending.uuid)

    def _is_not_impersonating(self):
        rv = self.client.get(self.impersonate_base_url)
        self.assert200(rv)
        self.assertEquals(rv.json.get('impersonating'), False)

    def test_impersonation_requires_login(self):
        self.assert401(self._start_impersonation(self.instructor_main_course))
        self.assert401(self._start_impersonation(self.student_main_course))

    def test_student_cannot_impersonate(self):
        with self.login(self.student_main_course.username):
            self.assert403(self._start_impersonation(self.instructor_main_course))
            self.assert403(self._start_impersonation(self.student_second_course))

    def test_instructor_can_impersonate_own_students(self):
        with self.impersonate(self.instructor_main_course, self.student_main_course):
            self._verify_impersonation(original=self.instructor_main_course, pretending=self.student_main_course)

    def test_instructor_cannot_impersonate_student_not_in_his_course(self):
        with self.login(self.instructor_main_course.username):
            self.assert403(self._start_impersonation(self.student_second_course))
        with self.login(self.instructor_second_course.username):
            self.assert403(self._start_impersonation(self.student_main_course))

    def test_instructor_cannot_impersonate_admin(self):
        with self.login(self.instructor_main_course.username):
            self.assert403(self._start_impersonation(DefaultFixture.ROOT_USER))

    def test_admin_can_impersonate_students_but_not_instructors(self):
        with self.login(DefaultFixture.ROOT_USER.username):
            self.assert200(self._start_impersonation(self.student_main_course))
            self.assert403(self._start_impersonation(self.instructor_main_course))

    def test_end_impersonation(self):
        # Impersonation will block non-GET traffic, but should allow end impersonation (a DELETE call)
        with self.login(self.instructor_main_course.username):
            self.assert200(self._start_impersonation(self.student_main_course))
            self._verify_impersonation(original=self.instructor_main_course, pretending=self.student_main_course)

            self.assert200(self._end_impersonation())
            self._is_not_impersonating()

    def test_cannot_logout_during_impersonation(self):
        with self.impersonate(self.instructor_main_course, self.student_main_course):
            # explicit logout should fail
            self.assert403(self.client.delete('/api/logout', follow_redirects=True))
            # still logged in and impersonating
            self._verify_impersonation(original=self.instructor_main_course, pretending=self.student_main_course)

    def test_access_right_after_impersonation(self):
        comparisons_of_main_course_assignment_url = \
            self.assignment_base_url + '/' + self.assignment_main_course.uuid + '/users/comparisons?page=1&perPage=5'

        with self.login(self.instructor_main_course.username):
            self.assert200(self._start_impersonation(self.student_main_course))
            # no access when impersonating as student
            self.assert403(self.client.get(comparisons_of_main_course_assignment_url))
            self._end_impersonation()
            # resume access after impersonation
            self.assert200(self.client.get(comparisons_of_main_course_assignment_url))
Exemple #8
0
class LoginAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(LoginAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()

    def test_app_login(self):
        params = {
            'username': '******',
            'password': '******'
        }
        # test app login disabled
        self.app.config['APP_LOGIN_ENABLED'] = False
        rv = self.client.post('/api/login', data=json.dumps(params), content_type='application/json', follow_redirects=True)
        self.assert403(rv)

        # test app login enabled
        self.app.config['APP_LOGIN_ENABLED'] = True
        rv = self.client.post('/api/login', data=json.dumps(params), content_type='application/json', follow_redirects=True)
        self.assert200(rv)

    def test_logout(self):
        with self.login('root'):
            url = '/api/logout'
            rv = self.client.delete(url)
            self.assert200(rv)

        # can't logout during impersonation
        with self.impersonate(DefaultFixture.ROOT_USER, self.data.get_authorized_student()):
            url = '/api/logout'
            rv = self.client.delete(url)
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

    def test_cas_login(self):
        auth_data = ThirdPartyAuthTestData()

        # test login new user
        for system_role in [SystemRole.student, SystemRole.instructor, SystemRole.sys_admin]:
            unique_identifier = system_role.value + "_no_attributes"
            response_mock = mock.MagicMock()
            response_mock.success = True
            response_mock.user = unique_identifier
            response_mock.attributes = None

            self.app.config['CAS_ATTRIBUTE_FIRST_NAME'] = None
            self.app.config['CAS_ATTRIBUTE_LAST_NAME'] = None
            self.app.config['CAS_ATTRIBUTE_STUDENT_NUMBER'] = None
            self.app.config['CAS_ATTRIBUTE_EMAIL'] = None
            self.app.config['CAS_ATTRIBUTE_USER_ROLE'] = None
            self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {}
            self.app.config['CAS_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = None

            with mock.patch('compair.api.login.validate_cas_ticket', return_value=response_mock):
                # test cas login disabled
                self.app.config['CAS_LOGIN_ENABLED'] = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert403(rv)

                # test cas login enabled with unsuccessful login
                self.app.config['CAS_LOGIN_ENABLED'] = True
                response_mock.success = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)

                # check session
                with self.client.session_transaction() as sess:
                    # check that user is logged in
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_TYPE'), 'CAS')
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_MSG'), 'Login Failed. CAS ticket was invalid.')

                # test cas login enabled with unsuccessful login
                response_mock.success = True
                response_mock.user = None
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)

                # check session
                with self.client.session_transaction() as sess:
                    # check that user is logged in
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_TYPE'), 'CAS')
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_MSG'), 'Login Failed. Expecting CAS username to be set.')

                # test cas login enabled with successful login
                response_mock.user = unique_identifier
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)

                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.cas)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role, SystemRole.student)
                self.assertIsNone(third_party_user.user.firstname)
                self.assertIsNone(third_party_user.user.lastname)
                six.assertRegex(self, third_party_user.user.displayname, r"^Student_\d{8}")
                self.assertIsNone(third_party_user.user.email)
                self.assertIsNone(third_party_user.user.student_number)
                self.assertIsNone(third_party_user.user.global_unique_identifier)

                with self.client.session_transaction() as sess:
                    self.assertEqual(sess.get('user_id'), str(third_party_user.user.id))

                # unused attributes
                unique_identifier = system_role.value + "_with_unused_attributes"
                response_mock.user = unique_identifier
                response_mock.attributes = {
                    'firstName': 'f_name',
                    'lastName': 'l_name',
                    'studentNumber': "student1" if system_role == SystemRole.student else None,
                    'email': '*****@*****.**',
                    'system_role_field': system_role.value,
                    'puid': system_role.value+"_puid",
                }

                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.cas)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role, SystemRole.student)
                self.assertIsNone(third_party_user.user.firstname)
                self.assertIsNone(third_party_user.user.lastname)
                six.assertRegex(self, third_party_user.user.displayname, r"^Student_\d{8}")
                self.assertIsNone(third_party_user.user.email)
                self.assertIsNone(third_party_user.user.student_number)
                self.assertIsNone(third_party_user.user.global_unique_identifier)

                # used attributes and no valid instructor values
                self.app.config['CAS_ATTRIBUTE_FIRST_NAME'] = 'firstName'
                self.app.config['CAS_ATTRIBUTE_LAST_NAME'] = 'lastName'
                self.app.config['CAS_ATTRIBUTE_STUDENT_NUMBER'] = 'studentNumber'
                self.app.config['CAS_ATTRIBUTE_EMAIL'] = 'email'
                self.app.config['CAS_ATTRIBUTE_USER_ROLE'] = 'system_role_field'
                self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {}
                self.app.config['CAS_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = 'puid'
                unique_identifier = system_role.value + "_with_used_attributes"
                response_mock.user = unique_identifier

                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.cas) \
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role, SystemRole.student)
                self.assertEqual(third_party_user.user.firstname, 'f_name')
                self.assertEqual(third_party_user.user.lastname, 'l_name')
                self.assertEqual(third_party_user.user.email, '*****@*****.**')
                self.assertEqual(third_party_user.user.global_unique_identifier, system_role.value+"_puid")
                if system_role == SystemRole.student:
                    self.assertEqual(third_party_user.user.student_number, 'student1')
                else:
                    self.assertIsNone(third_party_user.user.student_number)
                six.assertRegex(self, third_party_user.user.displayname, r"^Student_\d{8}")

                # used attributes and valid instructor values
                unique_identifier = system_role.value + "_with_used_attributes2"
                response_mock.user = unique_identifier
                if system_role == SystemRole.student:
                    response_mock.attributes['studentNumber'] = "student2"
                response_mock.attributes['puid'] = system_role.value+"_puid2"
                self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {SystemRole.sys_admin.value, SystemRole.instructor.value}

                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.cas)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.firstname, 'f_name')
                self.assertEqual(third_party_user.user.lastname, 'l_name')
                self.assertEqual(third_party_user.user.email, '*****@*****.**')
                self.assertEqual(third_party_user.user.global_unique_identifier, system_role.value+"_puid2")
                if system_role == SystemRole.student:
                    self.assertEqual(third_party_user.user.system_role, SystemRole.student)
                    six.assertRegex(self, third_party_user.user.displayname, r"^Student_\d{8}")
                    self.assertEqual(third_party_user.user.student_number, 'student2')
                else:
                    self.assertEqual(third_party_user.user.system_role, SystemRole.instructor)
                    self.assertEqual(third_party_user.user.displayname, "f_name l_name")
                    self.assertIsNone(third_party_user.user.student_number)

        # test login existing user
        for system_role in [SystemRole.student, SystemRole.instructor, SystemRole.sys_admin]:
            self.app.config['CAS_ATTRIBUTE_FIRST_NAME'] = None
            self.app.config['CAS_ATTRIBUTE_LAST_NAME'] = None
            self.app.config['CAS_ATTRIBUTE_STUDENT_NUMBER'] = None
            self.app.config['CAS_ATTRIBUTE_EMAIL'] = None
            self.app.config['CAS_ATTRIBUTE_USER_ROLE'] = None
            self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {}
            self.app.config['CAS_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = None

            user = self.data.create_user(system_role)
            third_party_user = auth_data.create_third_party_user(user=user, third_party_type=ThirdPartyType.cas)

            original_firstname = user.firstname
            original_lastname = user.lastname
            original_email = user.email
            original_student_number = user.student_number
            new_student_number = original_student_number+"123" if user.student_number else None

            response_mock = mock.MagicMock()
            response_mock.success = True
            response_mock.user = third_party_user.unique_identifier
            response_mock.attributes = None

            with mock.patch('compair.api.login.validate_cas_ticket', return_value=response_mock):
                # test cas login disabled
                self.app.config['CAS_LOGIN_ENABLED'] = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert403(rv)

                # test cas login enabled
                self.app.config['CAS_LOGIN_ENABLED'] = True
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)

                # test overwrite disabled with no attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = True
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with no attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with no attributes
                self.app.config['CAS_ATTRIBUTE_FIRST_NAME'] = 'firstName'
                self.app.config['CAS_ATTRIBUTE_LAST_NAME'] = 'lastName'
                self.app.config['CAS_ATTRIBUTE_STUDENT_NUMBER'] = 'studentNumber'
                self.app.config['CAS_ATTRIBUTE_EMAIL'] = 'email'
                self.app.config['CAS_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = 'puid'
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                response_mock.attributes = {
                    'firstName': 'f_name',
                    'lastName': 'l_name',
                    'studentNumber': new_student_number,
                    'email': '*****@*****.**',
                    'puid': 'should_be_ignored_since_already_linked'
                }

                # test overwrite disabled with attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = True
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                self.assert200(rv)

                if system_role == SystemRole.student:
                    self.assertEqual(user.firstname, 'f_name')
                    self.assertEqual(user.lastname, 'l_name')
                    self.assertEqual(user.email, '*****@*****.**')
                    self.assertEqual(user.student_number, new_student_number)
                else:
                    self.assertEqual(user.firstname, original_firstname)
                    self.assertEqual(user.lastname, original_lastname)
                    self.assertEqual(user.email, original_email)
                    self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

            self.app.config['CAS_ATTRIBUTE_USER_ROLE'] = 'system_role_field'
            self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {SystemRole.instructor.value}
            # test automatic upgrading of system role for existing accounts
            for third_party_system_role in [SystemRole.student, SystemRole.instructor, SystemRole.sys_admin]:
                user = self.data.create_user(system_role)
                third_party_user = auth_data.create_third_party_user(user=user, third_party_type=ThirdPartyType.cas)
                db.session.commit()

                response_mock.user = third_party_user.unique_identifier
                response_mock.attributes = {
                    'system_role_field': third_party_system_role.value
                }

                with mock.patch('compair.api.login.validate_cas_ticket', return_value=response_mock):
                    rv = self.client.get('/api/cas/auth?ticket=mock_ticket', follow_redirects=True)
                    self.assert200(rv)

                    # compair user system role will upgrade
                    if system_role == SystemRole.student:
                        # cannot upgrade to admin
                        if third_party_system_role == SystemRole.instructor:
                            self.assertEqual(user.system_role, SystemRole.instructor)
                        else:
                            self.assertEqual(user.system_role, SystemRole.student)
                    elif system_role == SystemRole.instructor:
                        # cannot upgrade to admin and shouldn't downgrade to student
                        self.assertEqual(user.system_role, SystemRole.instructor)
                    elif system_role == SystemRole.sys_admin:
                        # shouldn't downgrade
                        self.assertEqual(user.system_role, SystemRole.sys_admin)

    def test_saml_login(self):
        auth_data = ThirdPartyAuthTestData()

        # test login new user
        for system_role in [SystemRole.student, SystemRole.instructor, SystemRole.sys_admin]:
            unique_identifier = system_role.value + "_no_attributes"
            response_mock = mock.MagicMock()
            response_mock.__errors = []
            response_mock.is_authenticated.return_value = True
            response_mock.get_attributes.return_value = {
                'urn:oid:0.9.2342.19200300.100.1.1': [unique_identifier]
            }
            response_mock.get_nameid.return_value = "saml_mock_nameid"
            response_mock.get_session_index.return_value = "saml_session_index"

            self.app.config['SAML_ATTRIBUTE_FIRST_NAME'] = None
            self.app.config['SAML_ATTRIBUTE_LAST_NAME'] = None
            self.app.config['SAML_ATTRIBUTE_STUDENT_NUMBER'] = None
            self.app.config['SAML_ATTRIBUTE_EMAIL'] = None
            self.app.config['SAML_ATTRIBUTE_USER_ROLE'] = None
            self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {}
            self.app.config['SAML_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = None

            with mock.patch('compair.api.login.get_saml_auth_response', return_value=response_mock):
                # test saml login disabled
                self.app.config['SAML_LOGIN_ENABLED'] = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert403(rv)

                # test saml login enabled with unsuccessful login
                self.app.config['SAML_LOGIN_ENABLED'] = True
                response_mock.is_authenticated.return_value = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                # check session
                with self.client.session_transaction() as sess:
                    # check that user is logged in
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_TYPE'), 'SAML')
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_MSG'), 'Login Failed.')

                # test saml login enabled with unsuccessful login
                response_mock.is_authenticated.return_value = True
                response_mock.__errors = ['error1', 'error2']
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                # check session
                with self.client.session_transaction() as sess:
                    # check that user is logged in
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_TYPE'), 'SAML')
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_MSG'), 'Login Failed.')

                # test saml login enabled with successful login
                response_mock.__errors = []
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.saml)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role, SystemRole.student)
                self.assertIsNone(third_party_user.user.firstname)
                self.assertIsNone(third_party_user.user.lastname)
                six.assertRegex(self, third_party_user.user.displayname, r"^Student_\d{8}")
                self.assertIsNone(third_party_user.user.email)
                self.assertIsNone(third_party_user.user.student_number)
                self.assertIsNone(third_party_user.user.global_unique_identifier)

                with self.client.session_transaction() as sess:
                    self.assertEqual(sess.get('user_id'), str(third_party_user.user.id))

                # unused attributes
                unique_identifier = system_role.value + "_with_unused_attributes"
                response_mock.get_attributes.return_value = {
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.9': ['*****@*****.**', '*****@*****.**'],
                    'urn:oid:2.5.4.42': ['f_name'],
                    'urn:oid:0.9.2342.19200300.100.1.1': [unique_identifier],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.1': ['Member'],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.10': [None],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7': [system_role.value],
                    'urn:oid:2.5.4.4': ['l_name'],
                    'urn:oid:2.5.4.3': ['Me Myself And I'],
                    'urn:oid:2.5.4.20': ['555-5555'],
                    'urn:oid:2.16.840.1.113730.3.1.3': ["student1"] if system_role == SystemRole.student else [],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.6': ['*****@*****.**']
                }

                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.saml)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role, SystemRole.student)
                self.assertIsNone(third_party_user.user.firstname)
                self.assertIsNone(third_party_user.user.lastname)
                six.assertRegex(self, third_party_user.user.displayname, r"^Student_\d{8}")
                self.assertIsNone(third_party_user.user.email)
                self.assertIsNone(third_party_user.user.student_number)
                self.assertIsNone(third_party_user.user.global_unique_identifier)

                # used attributes and no valid instructor values
                self.app.config['SAML_ATTRIBUTE_FIRST_NAME'] = 'urn:oid:2.5.4.42'
                self.app.config['SAML_ATTRIBUTE_LAST_NAME'] = 'urn:oid:2.5.4.4'
                self.app.config['SAML_ATTRIBUTE_STUDENT_NUMBER'] = 'urn:oid:2.16.840.1.113730.3.1.3'
                self.app.config['SAML_ATTRIBUTE_EMAIL'] = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6'
                self.app.config['SAML_ATTRIBUTE_USER_ROLE'] = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.7'
                self.app.config['SAML_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = 'puid'
                self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {}
                unique_identifier = system_role.value + "_with_used_attributes"
                response_mock.get_attributes.return_value = {
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.9': ['*****@*****.**', '*****@*****.**'],
                    'urn:oid:2.5.4.42': ['f_name'],
                    'urn:oid:0.9.2342.19200300.100.1.1': [unique_identifier],
                    'puid': [system_role.value+"_puid"],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.1': ['Member'],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.10': [None],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7': [system_role.value],
                    'urn:oid:2.5.4.4': ['l_name'],
                    'urn:oid:2.5.4.3': ['Me Myself And I'],
                    'urn:oid:2.5.4.20': ['555-5555'],
                    'urn:oid:2.16.840.1.113730.3.1.3': ["student1"] if system_role == SystemRole.student else [],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.6': ['*****@*****.**'],
                }

                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.saml) \
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role, SystemRole.student)
                self.assertEqual(third_party_user.user.firstname, 'f_name')
                self.assertEqual(third_party_user.user.lastname, 'l_name')
                self.assertEqual(third_party_user.user.email, '*****@*****.**')
                self.assertEqual(third_party_user.user.global_unique_identifier, system_role.value+"_puid")
                if system_role == SystemRole.student:
                    self.assertEqual(third_party_user.user.student_number, 'student1')
                else:
                    self.assertIsNone(third_party_user.user.student_number)
                six.assertRegex(self, third_party_user.user.displayname, r"^Student_\d{8}")

                # used attributes and valid instructor values
                unique_identifier = system_role.value + "_with_used_attributes2"
                response_mock.get_attributes.return_value = {
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.9': ['*****@*****.**', '*****@*****.**'],
                    'urn:oid:2.5.4.42': ['f_name'],
                    'urn:oid:0.9.2342.19200300.100.1.1': [unique_identifier],
                    'puid': [system_role.value+"_puid2"],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.1': ['Member'],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.10': [None],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7': [system_role.value],
                    'urn:oid:2.5.4.4': ['l_name'],
                    'urn:oid:2.5.4.3': ['Me Myself And I'],
                    'urn:oid:2.5.4.20': ['555-5555'],
                    'urn:oid:2.16.840.1.113730.3.1.3': ["student2"] if system_role == SystemRole.student else [],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.6': ['*****@*****.**']
                }
                self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {SystemRole.sys_admin.value, SystemRole.instructor.value}

                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.saml)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.firstname, 'f_name')
                self.assertEqual(third_party_user.user.lastname, 'l_name')
                self.assertEqual(third_party_user.user.email, '*****@*****.**')
                self.assertEqual(third_party_user.user.global_unique_identifier, system_role.value+"_puid2")
                if system_role == SystemRole.student:
                    self.assertEqual(third_party_user.user.system_role, SystemRole.student)
                    six.assertRegex(self, third_party_user.user.displayname, r"^Student_\d{8}")
                    self.assertEqual(third_party_user.user.student_number, 'student2')
                else:
                    self.assertEqual(third_party_user.user.system_role, SystemRole.instructor)
                    self.assertEqual(third_party_user.user.displayname, "f_name l_name")
                    self.assertIsNone(third_party_user.user.student_number)

        # test login existing user
        for system_role in [SystemRole.student, SystemRole.instructor, SystemRole.sys_admin]:
            self.app.config['SAML_ATTRIBUTE_FIRST_NAME'] = None
            self.app.config['SAML_ATTRIBUTE_LAST_NAME'] = None
            self.app.config['SAML_ATTRIBUTE_STUDENT_NUMBER'] = None
            self.app.config['SAML_ATTRIBUTE_EMAIL'] = None
            self.app.config['SAML_ATTRIBUTE_USER_ROLE'] = None
            self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {}
            self.app.config['SAML_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = None

            user = self.data.create_user(system_role)
            third_party_user = auth_data.create_third_party_user(user=user, third_party_type=ThirdPartyType.saml)

            original_firstname = user.firstname
            original_lastname = user.lastname
            original_email = user.email
            original_student_number = user.student_number
            new_student_number = original_student_number+"123" if user.student_number else None

            response_mock = mock.MagicMock()
            response_mock.__errors = []
            response_mock.is_authenticated.return_value = True
            response_mock.get_attributes.return_value = {
                'urn:oid:0.9.2342.19200300.100.1.1': [third_party_user.unique_identifier]
            }
            response_mock.get_nameid.return_value = "saml_mock_nameid"
            response_mock.get_session_index.return_value = "saml_session_index"

            with mock.patch('compair.api.login.get_saml_auth_response', return_value=response_mock):
                # test saml login disabled
                self.app.config['SAML_LOGIN_ENABLED'] = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert403(rv)

                # test saml login enabled
                self.app.config['SAML_LOGIN_ENABLED'] = True
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                # test overwrite disabled with no attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = True
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with no attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with no attributes
                self.app.config['SAML_ATTRIBUTE_FIRST_NAME'] = 'urn:oid:2.5.4.42'
                self.app.config['SAML_ATTRIBUTE_LAST_NAME'] = 'urn:oid:2.5.4.4'
                self.app.config['SAML_ATTRIBUTE_STUDENT_NUMBER'] = 'urn:oid:2.16.840.1.113730.3.1.3'
                self.app.config['SAML_ATTRIBUTE_EMAIL'] = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6'
                self.app.config['SAML_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = 'puid'
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                response_mock.get_attributes.return_value = {
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.9': ['*****@*****.**', '*****@*****.**'],
                    'urn:oid:2.5.4.42': ['f_name'],
                    'urn:oid:0.9.2342.19200300.100.1.1': [third_party_user.unique_identifier],
                    'puid': ["should_be_ignored_since_already_linked"],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.1': ['Member', 'Staff'],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.10': [None],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7': ['urn:mace:dir:entitlement:common-lib-terms'],
                    'urn:oid:2.5.4.4': ['l_name'],
                    'urn:oid:2.5.4.3': ['Me Myself And I'],
                    'urn:oid:2.5.4.20': ['555-5555'],
                    'urn:oid:2.16.840.1.113730.3.1.3': [new_student_number],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.6': ['*****@*****.**']
                }

                # test overwrite disabled with attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = True
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                if system_role == SystemRole.student:
                    self.assertEqual(user.firstname, 'f_name')
                    self.assertEqual(user.lastname, 'l_name')
                    self.assertEqual(user.email, '*****@*****.**')
                    self.assertEqual(user.student_number, new_student_number)
                else:
                    self.assertEqual(user.firstname, original_firstname)
                    self.assertEqual(user.lastname, original_lastname)
                    self.assertEqual(user.email, original_email)
                    self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

            self.app.config['SAML_ATTRIBUTE_USER_ROLE'] = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.7'
            self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {SystemRole.instructor.value}
            # test automatic upgrading of system role for existing accounts
            for third_party_system_role in [SystemRole.student, SystemRole.instructor, SystemRole.sys_admin]:
                user = self.data.create_user(system_role)
                third_party_user = auth_data.create_third_party_user(user=user, third_party_type=ThirdPartyType.saml)
                db.session.commit()

                response_mock.get_attributes.return_value = {
                    'urn:oid:0.9.2342.19200300.100.1.1': [third_party_user.unique_identifier],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7': [third_party_system_role.value]
                }

                with mock.patch('compair.api.login.get_saml_auth_response', return_value=response_mock):
                    rv = self.client.post('/api/saml/auth', follow_redirects=True)
                    self.assert200(rv)

                    # compair user system role will upgrade
                    if system_role == SystemRole.student:
                        # cannot upgrade to admin
                        if third_party_system_role == SystemRole.instructor:
                            self.assertEqual(user.system_role, SystemRole.instructor)
                        else:
                            self.assertEqual(user.system_role, SystemRole.student)
                    elif system_role == SystemRole.instructor:
                        # cannot upgrade to admin and shouldn't downgrade to student
                        self.assertEqual(user.system_role, SystemRole.instructor)
                    elif system_role == SystemRole.sys_admin:
                        # shouldn't downgrade
                        self.assertEqual(user.system_role, SystemRole.sys_admin)

    def test_saml_metadata(self):
        # test saml login enabled
        self.app.config['SAML_LOGIN_ENABLED'] = True
        self.app.config['SAML_EXPOSE_METADATA_ENDPOINT'] = True
        rv = self.client.get('/api/saml/metadata', headers={'Accept': 'text/xml'})
        self.assert200(rv)

        # test saml login disabled
        self.app.config['SAML_LOGIN_ENABLED'] = False
        self.app.config['SAML_EXPOSE_METADATA_ENDPOINT'] = True
        rv = self.client.get('/api/saml/metadata', headers={'Accept': 'text/xml'})
        self.assert404(rv)

        # test saml metadata disabled
        self.app.config['SAML_LOGIN_ENABLED'] = True
        self.app.config['SAML_EXPOSE_METADATA_ENDPOINT'] = False
        rv = self.client.get('/api/saml/metadata', headers={'Accept': 'text/xml'})
        self.assert404(rv)
Exemple #9
0
 def setUp(self):
     super(LoginAPITests, self).setUp()
     self.data = SimpleAssignmentTestData()
Exemple #10
0
class ImpersonationAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(ImpersonationAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.course_base_url = '/api/courses'
        self.assignment_base_url = self.course_base_url + '/' + self.data.get_course(
        ).uuid + '/assignments'
        self.impersonate_base_url = '/api/impersonate'
        self.profile_base_url = '/api/users'
        self.session_base_url = '/api/session'

        self.assignment_main_course = self.data.get_assignments()[0]
        self.student_main_course = self.data.get_authorized_student()
        self.student_second_course = self.data.get_unauthorized_student()
        self.instructor_main_course = self.data.get_authorized_instructor()
        self.instructor_second_course = self.data.get_unauthorized_instructor(
        )

    def _start_impersonation(self, pretending):
        return self.client.post(self.impersonate_base_url + '/' +
                                pretending.uuid)

    def _end_impersonation(self):
        return self.client.delete(self.impersonate_base_url)

    def _verify_impersonation(self, original=None, pretending=None):
        rv = self.client.get(self.impersonate_base_url)
        self.assert200(rv)
        self.assertEquals(rv.json.get('impersonating'), True)
        if original:
            self.assertEquals(
                rv.json.get('original_user').get('id'), original.uuid)
        if pretending:
            rv = self.client.get(self.session_base_url)
            self.assert200(rv)
            self.assertEqual(rv.json.get('id'), pretending.uuid)

    def _is_not_impersonating(self):
        rv = self.client.get(self.impersonate_base_url)
        self.assert200(rv)
        self.assertEquals(rv.json.get('impersonating'), False)

    def test_impersonation_requires_login(self):
        self.assert401(self._start_impersonation(self.instructor_main_course))
        self.assert401(self._start_impersonation(self.student_main_course))

    def test_student_cannot_impersonate(self):
        with self.login(self.student_main_course.username):
            self.assert403(
                self._start_impersonation(self.instructor_main_course))
            self.assert403(
                self._start_impersonation(self.student_second_course))

    def test_instructor_can_impersonate_own_students(self):
        with self.impersonate(self.instructor_main_course,
                              self.student_main_course):
            self._verify_impersonation(original=self.instructor_main_course,
                                       pretending=self.student_main_course)

    def test_instructor_cannot_impersonate_student_not_in_his_course(self):
        with self.login(self.instructor_main_course.username):
            self.assert403(
                self._start_impersonation(self.student_second_course))
        with self.login(self.instructor_second_course.username):
            self.assert403(self._start_impersonation(self.student_main_course))

    def test_instructor_cannot_impersonate_admin(self):
        with self.login(self.instructor_main_course.username):
            self.assert403(self._start_impersonation(DefaultFixture.ROOT_USER))

    def test_admin_can_impersonate_students_but_not_instructors(self):
        with self.login(DefaultFixture.ROOT_USER.username):
            self.assert200(self._start_impersonation(self.student_main_course))
            self.assert403(
                self._start_impersonation(self.instructor_main_course))

    def test_end_impersonation(self):
        # Impersonation will block non-GET traffic, but should allow end impersonation (a DELETE call)
        with self.login(self.instructor_main_course.username):
            self.assert200(self._start_impersonation(self.student_main_course))
            self._verify_impersonation(original=self.instructor_main_course,
                                       pretending=self.student_main_course)

            self.assert200(self._end_impersonation())
            self._is_not_impersonating()

    def test_cannot_logout_during_impersonation(self):
        with self.impersonate(self.instructor_main_course,
                              self.student_main_course):
            # explicit logout should fail
            self.assert403(
                self.client.delete('/api/logout', follow_redirects=True))
            # still logged in and impersonating
            self._verify_impersonation(original=self.instructor_main_course,
                                       pretending=self.student_main_course)

    def test_access_right_after_impersonation(self):
        comparisons_of_main_course_assignment_url = \
            self.assignment_base_url + '/' + self.assignment_main_course.uuid + '/users/comparisons?page=1&perPage=5'

        with self.login(self.instructor_main_course.username):
            self.assert200(self._start_impersonation(self.student_main_course))
            # no access when impersonating as student
            self.assert403(
                self.client.get(comparisons_of_main_course_assignment_url))
            self._end_impersonation()
            # resume access after impersonation
            self.assert200(
                self.client.get(comparisons_of_main_course_assignment_url))
Exemple #11
0
 def setUp(self):
     super(ComPAIRXAPITestCase, self).setUp()
     self.data = SimpleAssignmentTestData()
     self.user = self.data.authorized_student
     self.assignment = self.data.assignments[0]
Exemple #12
0
class LoginAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(LoginAPITests, self).setUp()
        self.data = SimpleAssignmentTestData()

    def test_app_login(self):
        params = {'username': '******', 'password': '******'}
        # test app login disabled
        self.app.config['APP_LOGIN_ENABLED'] = False
        rv = self.client.post('/api/login',
                              data=json.dumps(params),
                              content_type='application/json',
                              follow_redirects=True)
        self.assert403(rv)

        # test app login enabled
        self.app.config['APP_LOGIN_ENABLED'] = True
        rv = self.client.post('/api/login',
                              data=json.dumps(params),
                              content_type='application/json',
                              follow_redirects=True)
        self.assert200(rv)

    def test_logout(self):
        with self.login('root'):
            url = '/api/logout'
            rv = self.client.delete(url)
            self.assert200(rv)

        # can't logout during impersonation
        with self.impersonate(DefaultFixture.ROOT_USER,
                              self.data.get_authorized_student()):
            url = '/api/logout'
            rv = self.client.delete(url)
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

    def test_cas_login(self):
        auth_data = ThirdPartyAuthTestData()

        # test login new user
        for system_role in [
                SystemRole.student, SystemRole.instructor, SystemRole.sys_admin
        ]:
            unique_identifier = system_role.value + "_no_attributes"
            response_mock = mock.MagicMock()
            response_mock.success = True
            response_mock.user = unique_identifier
            response_mock.attributes = None

            self.app.config['CAS_ATTRIBUTE_FIRST_NAME'] = None
            self.app.config['CAS_ATTRIBUTE_LAST_NAME'] = None
            self.app.config['CAS_ATTRIBUTE_STUDENT_NUMBER'] = None
            self.app.config['CAS_ATTRIBUTE_EMAIL'] = None
            self.app.config['CAS_ATTRIBUTE_USER_ROLE'] = None
            self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {}
            self.app.config['CAS_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = None

            with mock.patch('compair.api.login.validate_cas_ticket',
                            return_value=response_mock):
                # test cas login disabled
                self.app.config['CAS_LOGIN_ENABLED'] = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert403(rv)

                # test cas login enabled with unsuccessful login
                self.app.config['CAS_LOGIN_ENABLED'] = True
                response_mock.success = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)

                # check session
                with self.client.session_transaction() as sess:
                    # check that user is logged in
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_TYPE'),
                                     'CAS')
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_MSG'),
                                     'Login Failed. CAS ticket was invalid.')

                # test cas login enabled with unsuccessful login
                response_mock.success = True
                response_mock.user = None
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)

                # check session
                with self.client.session_transaction() as sess:
                    # check that user is logged in
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_TYPE'),
                                     'CAS')
                    self.assertEqual(
                        sess.get('THIRD_PARTY_AUTH_ERROR_MSG'),
                        'Login Failed. Expecting CAS username to be set.')

                # test cas login enabled with successful login
                response_mock.user = unique_identifier
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)

                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.cas)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role,
                                 SystemRole.student)
                self.assertIsNone(third_party_user.user.firstname)
                self.assertIsNone(third_party_user.user.lastname)
                six.assertRegex(self, third_party_user.user.displayname,
                                r"^Student_\d{8}")
                self.assertIsNone(third_party_user.user.email)
                self.assertIsNone(third_party_user.user.student_number)
                self.assertIsNone(
                    third_party_user.user.global_unique_identifier)

                with self.client.session_transaction() as sess:
                    self.assertEqual(sess.get('_user_id'),
                                     str(third_party_user.user.id))

                # unused attributes
                unique_identifier = system_role.value + "_with_unused_attributes"
                response_mock.user = unique_identifier
                response_mock.attributes = {
                    'firstName':
                    'f_name',
                    'lastName':
                    'l_name',
                    'studentNumber':
                    "student1" if system_role == SystemRole.student else None,
                    'email':
                    '*****@*****.**',
                    'system_role_field':
                    system_role.value,
                    'puid':
                    system_role.value + "_puid",
                }

                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.cas)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role,
                                 SystemRole.student)
                self.assertIsNone(third_party_user.user.firstname)
                self.assertIsNone(third_party_user.user.lastname)
                six.assertRegex(self, third_party_user.user.displayname,
                                r"^Student_\d{8}")
                self.assertIsNone(third_party_user.user.email)
                self.assertIsNone(third_party_user.user.student_number)
                self.assertIsNone(
                    third_party_user.user.global_unique_identifier)

                # used attributes and no valid instructor values
                self.app.config['CAS_ATTRIBUTE_FIRST_NAME'] = 'firstName'
                self.app.config['CAS_ATTRIBUTE_LAST_NAME'] = 'lastName'
                self.app.config[
                    'CAS_ATTRIBUTE_STUDENT_NUMBER'] = 'studentNumber'
                self.app.config['CAS_ATTRIBUTE_EMAIL'] = 'email'
                self.app.config[
                    'CAS_ATTRIBUTE_USER_ROLE'] = 'system_role_field'
                self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {}
                self.app.config['CAS_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = 'puid'
                unique_identifier = system_role.value + "_with_used_attributes"
                response_mock.user = unique_identifier

                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.cas) \
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role,
                                 SystemRole.student)
                self.assertEqual(third_party_user.user.firstname, 'f_name')
                self.assertEqual(third_party_user.user.lastname, 'l_name')
                self.assertEqual(third_party_user.user.email,
                                 '*****@*****.**')
                self.assertEqual(
                    third_party_user.user.global_unique_identifier,
                    system_role.value + "_puid")
                if system_role == SystemRole.student:
                    self.assertEqual(third_party_user.user.student_number,
                                     'student1')
                else:
                    self.assertIsNone(third_party_user.user.student_number)
                six.assertRegex(self, third_party_user.user.displayname,
                                r"^Student_\d{8}")

                # used attributes and valid instructor values
                unique_identifier = system_role.value + "_with_used_attributes2"
                response_mock.user = unique_identifier
                if system_role == SystemRole.student:
                    response_mock.attributes['studentNumber'] = "student2"
                response_mock.attributes['puid'] = system_role.value + "_puid2"
                self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {
                    SystemRole.sys_admin.value, SystemRole.instructor.value
                }

                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.cas)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.firstname, 'f_name')
                self.assertEqual(third_party_user.user.lastname, 'l_name')
                self.assertEqual(third_party_user.user.email,
                                 '*****@*****.**')
                self.assertEqual(
                    third_party_user.user.global_unique_identifier,
                    system_role.value + "_puid2")
                if system_role == SystemRole.student:
                    self.assertEqual(third_party_user.user.system_role,
                                     SystemRole.student)
                    six.assertRegex(self, third_party_user.user.displayname,
                                    r"^Student_\d{8}")
                    self.assertEqual(third_party_user.user.student_number,
                                     'student2')
                else:
                    self.assertEqual(third_party_user.user.system_role,
                                     SystemRole.instructor)
                    self.assertEqual(third_party_user.user.displayname,
                                     "f_name l_name")
                    self.assertIsNone(third_party_user.user.student_number)

        # test login existing user
        for system_role in [
                SystemRole.student, SystemRole.instructor, SystemRole.sys_admin
        ]:
            self.app.config['CAS_ATTRIBUTE_FIRST_NAME'] = None
            self.app.config['CAS_ATTRIBUTE_LAST_NAME'] = None
            self.app.config['CAS_ATTRIBUTE_STUDENT_NUMBER'] = None
            self.app.config['CAS_ATTRIBUTE_EMAIL'] = None
            self.app.config['CAS_ATTRIBUTE_USER_ROLE'] = None
            self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {}
            self.app.config['CAS_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = None

            user = self.data.create_user(system_role)
            third_party_user = auth_data.create_third_party_user(
                user=user, third_party_type=ThirdPartyType.cas)

            original_firstname = user.firstname
            original_lastname = user.lastname
            original_email = user.email
            original_student_number = user.student_number
            new_student_number = original_student_number + "123" if user.student_number else None

            response_mock = mock.MagicMock()
            response_mock.success = True
            response_mock.user = third_party_user.unique_identifier
            response_mock.attributes = None

            with mock.patch('compair.api.login.validate_cas_ticket',
                            return_value=response_mock):
                # test cas login disabled
                self.app.config['CAS_LOGIN_ENABLED'] = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert403(rv)

                # test cas login enabled
                self.app.config['CAS_LOGIN_ENABLED'] = True
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)

                # test overwrite disabled with no attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = True
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with no attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with no attributes
                self.app.config['CAS_ATTRIBUTE_FIRST_NAME'] = 'firstName'
                self.app.config['CAS_ATTRIBUTE_LAST_NAME'] = 'lastName'
                self.app.config[
                    'CAS_ATTRIBUTE_STUDENT_NUMBER'] = 'studentNumber'
                self.app.config['CAS_ATTRIBUTE_EMAIL'] = 'email'
                self.app.config['CAS_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = 'puid'
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                response_mock.attributes = {
                    'firstName': 'f_name',
                    'lastName': 'l_name',
                    'studentNumber': new_student_number,
                    'email': '*****@*****.**',
                    'puid': 'should_be_ignored_since_already_linked'
                }

                # test overwrite disabled with attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = True
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = False
                rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                     follow_redirects=True)
                self.assert200(rv)

                if system_role == SystemRole.student:
                    self.assertEqual(user.firstname, 'f_name')
                    self.assertEqual(user.lastname, 'l_name')
                    self.assertEqual(user.email, '*****@*****.**')
                    self.assertEqual(user.student_number, new_student_number)
                else:
                    self.assertEqual(user.firstname, original_firstname)
                    self.assertEqual(user.lastname, original_lastname)
                    self.assertEqual(user.email, original_email)
                    self.assertEqual(user.student_number,
                                     original_student_number)
                self.assertIsNone(user.global_unique_identifier)

            self.app.config['CAS_ATTRIBUTE_USER_ROLE'] = 'system_role_field'
            self.app.config['CAS_INSTRUCTOR_ROLE_VALUES'] = {
                SystemRole.instructor.value
            }
            # test automatic upgrading of system role for existing accounts
            for third_party_system_role in [
                    SystemRole.student, SystemRole.instructor,
                    SystemRole.sys_admin
            ]:
                user = self.data.create_user(system_role)
                third_party_user = auth_data.create_third_party_user(
                    user=user, third_party_type=ThirdPartyType.cas)
                db.session.commit()

                response_mock.user = third_party_user.unique_identifier
                response_mock.attributes = {
                    'system_role_field': third_party_system_role.value
                }

                with mock.patch('compair.api.login.validate_cas_ticket',
                                return_value=response_mock):
                    rv = self.client.get('/api/cas/auth?ticket=mock_ticket',
                                         follow_redirects=True)
                    self.assert200(rv)

                    # compair user system role will upgrade
                    if system_role == SystemRole.student:
                        # cannot upgrade to admin
                        if third_party_system_role == SystemRole.instructor:
                            self.assertEqual(user.system_role,
                                             SystemRole.instructor)
                        else:
                            self.assertEqual(user.system_role,
                                             SystemRole.student)
                    elif system_role == SystemRole.instructor:
                        # cannot upgrade to admin and shouldn't downgrade to student
                        self.assertEqual(user.system_role,
                                         SystemRole.instructor)
                    elif system_role == SystemRole.sys_admin:
                        # shouldn't downgrade
                        self.assertEqual(user.system_role,
                                         SystemRole.sys_admin)

    def test_saml_login(self):
        auth_data = ThirdPartyAuthTestData()

        # test login new user
        for system_role in [
                SystemRole.student, SystemRole.instructor, SystemRole.sys_admin
        ]:
            unique_identifier = system_role.value + "_no_attributes"
            response_mock = mock.MagicMock()
            response_mock.__errors = []
            response_mock.is_authenticated.return_value = True
            response_mock.get_attributes.return_value = {
                'urn:oid:0.9.2342.19200300.100.1.1': [unique_identifier]
            }
            response_mock.get_nameid.return_value = "saml_mock_nameid"
            response_mock.get_session_index.return_value = "saml_session_index"

            self.app.config['SAML_ATTRIBUTE_FIRST_NAME'] = None
            self.app.config['SAML_ATTRIBUTE_LAST_NAME'] = None
            self.app.config['SAML_ATTRIBUTE_STUDENT_NUMBER'] = None
            self.app.config['SAML_ATTRIBUTE_EMAIL'] = None
            self.app.config['SAML_ATTRIBUTE_USER_ROLE'] = None
            self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {}
            self.app.config['SAML_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = None

            with mock.patch('compair.api.login.get_saml_auth_response',
                            return_value=response_mock):
                # test saml login disabled
                self.app.config['SAML_LOGIN_ENABLED'] = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert403(rv)

                # test saml login enabled with unsuccessful login
                self.app.config['SAML_LOGIN_ENABLED'] = True
                response_mock.is_authenticated.return_value = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                # check session
                with self.client.session_transaction() as sess:
                    # check that user is logged in
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_TYPE'),
                                     'SAML')
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_MSG'),
                                     'Login Failed.')

                # test saml login enabled with unsuccessful login
                response_mock.is_authenticated.return_value = True
                response_mock.__errors = ['error1', 'error2']
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                # check session
                with self.client.session_transaction() as sess:
                    # check that user is logged in
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_TYPE'),
                                     'SAML')
                    self.assertEqual(sess.get('THIRD_PARTY_AUTH_ERROR_MSG'),
                                     'Login Failed.')

                # test saml login enabled with successful login
                response_mock.__errors = []
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.saml)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role,
                                 SystemRole.student)
                self.assertIsNone(third_party_user.user.firstname)
                self.assertIsNone(third_party_user.user.lastname)
                six.assertRegex(self, third_party_user.user.displayname,
                                r"^Student_\d{8}")
                self.assertIsNone(third_party_user.user.email)
                self.assertIsNone(third_party_user.user.student_number)
                self.assertIsNone(
                    third_party_user.user.global_unique_identifier)

                with self.client.session_transaction() as sess:
                    self.assertEqual(sess.get('_user_id'),
                                     str(third_party_user.user.id))

                # unused attributes
                unique_identifier = system_role.value + "_with_unused_attributes"
                response_mock.get_attributes.return_value = {
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.9':
                    ['*****@*****.**', '*****@*****.**'],
                    'urn:oid:2.5.4.42': ['f_name'],
                    'urn:oid:0.9.2342.19200300.100.1.1': [unique_identifier],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.1': ['Member'],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.10': [None],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7': [system_role.value],
                    'urn:oid:2.5.4.4': ['l_name'],
                    'urn:oid:2.5.4.3': ['Me Myself And I'],
                    'urn:oid:2.5.4.20': ['555-5555'],
                    'urn:oid:2.16.840.1.113730.3.1.3':
                    ["student1"] if system_role == SystemRole.student else [],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.6': ['*****@*****.**']
                }

                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.saml)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role,
                                 SystemRole.student)
                self.assertIsNone(third_party_user.user.firstname)
                self.assertIsNone(third_party_user.user.lastname)
                six.assertRegex(self, third_party_user.user.displayname,
                                r"^Student_\d{8}")
                self.assertIsNone(third_party_user.user.email)
                self.assertIsNone(third_party_user.user.student_number)
                self.assertIsNone(
                    third_party_user.user.global_unique_identifier)

                # used attributes and no valid instructor values
                self.app.config[
                    'SAML_ATTRIBUTE_FIRST_NAME'] = 'urn:oid:2.5.4.42'
                self.app.config['SAML_ATTRIBUTE_LAST_NAME'] = 'urn:oid:2.5.4.4'
                self.app.config[
                    'SAML_ATTRIBUTE_STUDENT_NUMBER'] = 'urn:oid:2.16.840.1.113730.3.1.3'
                self.app.config[
                    'SAML_ATTRIBUTE_EMAIL'] = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6'
                self.app.config[
                    'SAML_ATTRIBUTE_USER_ROLE'] = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.7'
                self.app.config['SAML_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = 'puid'
                self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {}
                unique_identifier = system_role.value + "_with_used_attributes"
                response_mock.get_attributes.return_value = {
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.9':
                    ['*****@*****.**', '*****@*****.**'],
                    'urn:oid:2.5.4.42': ['f_name'],
                    'urn:oid:0.9.2342.19200300.100.1.1': [unique_identifier],
                    'puid': [system_role.value + "_puid"],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.1': ['Member'],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.10': [None],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7': [system_role.value],
                    'urn:oid:2.5.4.4': ['l_name'],
                    'urn:oid:2.5.4.3': ['Me Myself And I'],
                    'urn:oid:2.5.4.20': ['555-5555'],
                    'urn:oid:2.16.840.1.113730.3.1.3':
                    ["student1"] if system_role == SystemRole.student else [],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.6': ['*****@*****.**'],
                }

                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.saml) \
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.system_role,
                                 SystemRole.student)
                self.assertEqual(third_party_user.user.firstname, 'f_name')
                self.assertEqual(third_party_user.user.lastname, 'l_name')
                self.assertEqual(third_party_user.user.email,
                                 '*****@*****.**')
                self.assertEqual(
                    third_party_user.user.global_unique_identifier,
                    system_role.value + "_puid")
                if system_role == SystemRole.student:
                    self.assertEqual(third_party_user.user.student_number,
                                     'student1')
                else:
                    self.assertIsNone(third_party_user.user.student_number)
                six.assertRegex(self, third_party_user.user.displayname,
                                r"^Student_\d{8}")

                # used attributes and valid instructor values
                unique_identifier = system_role.value + "_with_used_attributes2"
                response_mock.get_attributes.return_value = {
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.9':
                    ['*****@*****.**', '*****@*****.**'],
                    'urn:oid:2.5.4.42': ['f_name'],
                    'urn:oid:0.9.2342.19200300.100.1.1': [unique_identifier],
                    'puid': [system_role.value + "_puid2"],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.1': ['Member'],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.10': [None],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7': [system_role.value],
                    'urn:oid:2.5.4.4': ['l_name'],
                    'urn:oid:2.5.4.3': ['Me Myself And I'],
                    'urn:oid:2.5.4.20': ['555-5555'],
                    'urn:oid:2.16.840.1.113730.3.1.3':
                    ["student2"] if system_role == SystemRole.student else [],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.6': ['*****@*****.**']
                }
                self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {
                    SystemRole.sys_admin.value, SystemRole.instructor.value
                }

                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                third_party_user = ThirdPartyUser.query \
                    .filter_by(unique_identifier=unique_identifier, third_party_type=ThirdPartyType.saml)\
                    .one()

                self.assertIsNotNone(third_party_user)
                self.assertIsNotNone(third_party_user.user)
                self.assertEqual(third_party_user.user.firstname, 'f_name')
                self.assertEqual(third_party_user.user.lastname, 'l_name')
                self.assertEqual(third_party_user.user.email,
                                 '*****@*****.**')
                self.assertEqual(
                    third_party_user.user.global_unique_identifier,
                    system_role.value + "_puid2")
                if system_role == SystemRole.student:
                    self.assertEqual(third_party_user.user.system_role,
                                     SystemRole.student)
                    six.assertRegex(self, third_party_user.user.displayname,
                                    r"^Student_\d{8}")
                    self.assertEqual(third_party_user.user.student_number,
                                     'student2')
                else:
                    self.assertEqual(third_party_user.user.system_role,
                                     SystemRole.instructor)
                    self.assertEqual(third_party_user.user.displayname,
                                     "f_name l_name")
                    self.assertIsNone(third_party_user.user.student_number)

        # test login existing user
        for system_role in [
                SystemRole.student, SystemRole.instructor, SystemRole.sys_admin
        ]:
            self.app.config['SAML_ATTRIBUTE_FIRST_NAME'] = None
            self.app.config['SAML_ATTRIBUTE_LAST_NAME'] = None
            self.app.config['SAML_ATTRIBUTE_STUDENT_NUMBER'] = None
            self.app.config['SAML_ATTRIBUTE_EMAIL'] = None
            self.app.config['SAML_ATTRIBUTE_USER_ROLE'] = None
            self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {}
            self.app.config['SAML_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = None

            user = self.data.create_user(system_role)
            third_party_user = auth_data.create_third_party_user(
                user=user, third_party_type=ThirdPartyType.saml)

            original_firstname = user.firstname
            original_lastname = user.lastname
            original_email = user.email
            original_student_number = user.student_number
            new_student_number = original_student_number + "123" if user.student_number else None

            response_mock = mock.MagicMock()
            response_mock.__errors = []
            response_mock.is_authenticated.return_value = True
            response_mock.get_attributes.return_value = {
                'urn:oid:0.9.2342.19200300.100.1.1':
                [third_party_user.unique_identifier]
            }
            response_mock.get_nameid.return_value = "saml_mock_nameid"
            response_mock.get_session_index.return_value = "saml_session_index"

            with mock.patch('compair.api.login.get_saml_auth_response',
                            return_value=response_mock):
                # test saml login disabled
                self.app.config['SAML_LOGIN_ENABLED'] = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert403(rv)

                # test saml login enabled
                self.app.config['SAML_LOGIN_ENABLED'] = True
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                # test overwrite disabled with no attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = True
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with no attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with no attributes
                self.app.config[
                    'SAML_ATTRIBUTE_FIRST_NAME'] = 'urn:oid:2.5.4.42'
                self.app.config['SAML_ATTRIBUTE_LAST_NAME'] = 'urn:oid:2.5.4.4'
                self.app.config[
                    'SAML_ATTRIBUTE_STUDENT_NUMBER'] = 'urn:oid:2.16.840.1.113730.3.1.3'
                self.app.config[
                    'SAML_ATTRIBUTE_EMAIL'] = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6'
                self.app.config['SAML_GLOBAL_UNIQUE_IDENTIFIER_FIELD'] = 'puid'
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                response_mock.get_attributes.return_value = {
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.9':
                    ['*****@*****.**', '*****@*****.**'],
                    'urn:oid:2.5.4.42': ['f_name'],
                    'urn:oid:0.9.2342.19200300.100.1.1':
                    [third_party_user.unique_identifier],
                    'puid': ["should_be_ignored_since_already_linked"],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.1': ['Member', 'Staff'],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.10': [None],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7':
                    ['urn:mace:dir:entitlement:common-lib-terms'],
                    'urn:oid:2.5.4.4': ['l_name'],
                    'urn:oid:2.5.4.3': ['Me Myself And I'],
                    'urn:oid:2.5.4.20': ['555-5555'],
                    'urn:oid:2.16.840.1.113730.3.1.3': [new_student_number],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.6': ['*****@*****.**']
                }

                # test overwrite disabled with attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = True
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = True
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)
                self.assertEqual(user.firstname, original_firstname)
                self.assertEqual(user.lastname, original_lastname)
                self.assertEqual(user.email, original_email)
                self.assertEqual(user.student_number, original_student_number)
                self.assertIsNone(user.global_unique_identifier)

                # test overwrite enabled with attributes
                self.app.config['ALLOW_STUDENT_CHANGE_DISPLAY_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_NAME'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'] = False
                self.app.config['ALLOW_STUDENT_CHANGE_EMAIL'] = False
                rv = self.client.post('/api/saml/auth', follow_redirects=True)
                self.assert200(rv)

                if system_role == SystemRole.student:
                    self.assertEqual(user.firstname, 'f_name')
                    self.assertEqual(user.lastname, 'l_name')
                    self.assertEqual(user.email, '*****@*****.**')
                    self.assertEqual(user.student_number, new_student_number)
                else:
                    self.assertEqual(user.firstname, original_firstname)
                    self.assertEqual(user.lastname, original_lastname)
                    self.assertEqual(user.email, original_email)
                    self.assertEqual(user.student_number,
                                     original_student_number)
                self.assertIsNone(user.global_unique_identifier)

            self.app.config[
                'SAML_ATTRIBUTE_USER_ROLE'] = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.7'
            self.app.config['SAML_INSTRUCTOR_ROLE_VALUES'] = {
                SystemRole.instructor.value
            }
            # test automatic upgrading of system role for existing accounts
            for third_party_system_role in [
                    SystemRole.student, SystemRole.instructor,
                    SystemRole.sys_admin
            ]:
                user = self.data.create_user(system_role)
                third_party_user = auth_data.create_third_party_user(
                    user=user, third_party_type=ThirdPartyType.saml)
                db.session.commit()

                response_mock.get_attributes.return_value = {
                    'urn:oid:0.9.2342.19200300.100.1.1':
                    [third_party_user.unique_identifier],
                    'urn:oid:1.3.6.1.4.1.5923.1.1.1.7':
                    [third_party_system_role.value]
                }

                with mock.patch('compair.api.login.get_saml_auth_response',
                                return_value=response_mock):
                    rv = self.client.post('/api/saml/auth',
                                          follow_redirects=True)
                    self.assert200(rv)

                    # compair user system role will upgrade
                    if system_role == SystemRole.student:
                        # cannot upgrade to admin
                        if third_party_system_role == SystemRole.instructor:
                            self.assertEqual(user.system_role,
                                             SystemRole.instructor)
                        else:
                            self.assertEqual(user.system_role,
                                             SystemRole.student)
                    elif system_role == SystemRole.instructor:
                        # cannot upgrade to admin and shouldn't downgrade to student
                        self.assertEqual(user.system_role,
                                         SystemRole.instructor)
                    elif system_role == SystemRole.sys_admin:
                        # shouldn't downgrade
                        self.assertEqual(user.system_role,
                                         SystemRole.sys_admin)

    def test_saml_metadata(self):
        # test saml login enabled
        self.app.config['SAML_LOGIN_ENABLED'] = True
        self.app.config['SAML_EXPOSE_METADATA_ENDPOINT'] = True
        rv = self.client.get('/api/saml/metadata',
                             headers={'Accept': 'text/xml'})
        self.assert200(rv)

        # test saml login disabled
        self.app.config['SAML_LOGIN_ENABLED'] = False
        self.app.config['SAML_EXPOSE_METADATA_ENDPOINT'] = True
        rv = self.client.get('/api/saml/metadata',
                             headers={'Accept': 'text/xml'})
        self.assert404(rv)

        # test saml metadata disabled
        self.app.config['SAML_LOGIN_ENABLED'] = True
        self.app.config['SAML_EXPOSE_METADATA_ENDPOINT'] = False
        rv = self.client.get('/api/saml/metadata',
                             headers={'Accept': 'text/xml'})
        self.assert404(rv)
Exemple #13
0
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.lti_data = LTITestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)
        self.course = self.data.main_course
        self.lti_context = self.lti_data.create_context(
            self.lti_data.lti_consumer,
            compair_course_id=self.course.id,
            lis_course_offering_sourcedid="sis_course_id",
            lis_course_section_sourcedid="sis_section_id",
        )
        self.assignment = self.data.assignments[0]

        self.expected_caliper_course = {
            'academicSession':
            self.course.term,
            'dateCreated':
            self.course.created.replace(
                tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
            'dateModified':
            self.course.modified.replace(
                tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid,
            'name':
            self.course.name,
            'type':
            'CourseOffering',
            'otherIdentifiers': [{
                'identifier': self.lti_context.context_id,
                'identifierType': 'LtiContextId',
                'type': 'SystemIdentifier',
                'extensions': {
                    'lis_course_offering_sourcedid':
                    'sis_course_id',
                    'lis_course_section_sourcedid':
                    'sis_section_id',
                    'oauth_consumer_key':
                    self.lti_data.lti_consumer.oauth_consumer_key,
                },
            }]
        }

        self.expected_caliper_assignment = {
            'name':
            self.assignment.name,
            'type':
            'Assessment',
            'dateCreated':
            self.assignment.created.replace(
                tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
            'dateModified':
            self.assignment.modified.replace(
                tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
            'dateToStartOn':
            self.assignment.answer_start.replace(
                tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
            'description':
            self.assignment.description,
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid,
            'isPartOf':
            self.expected_caliper_course,
            'items': [{
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid + "/question",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/comparison/question/1",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/1",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/2",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/comparison/question/2",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/3",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/4",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/comparison/question/3",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/5",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/6",
                'type':
                'AssessmentItem'
            }],
        }

        self.expected_xapi_course = {
            'id': "https://localhost:8888/app/course/" + self.course.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/course',
                'name': {
                    'en-US': self.course.name
                }
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_assignment = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/assessment',
                'name': {
                    'en-US': self.assignment.name
                },
                'description': {
                    'en-US': self.assignment.description
                },
            },
            'objectType':
            'Activity'
        }
Exemple #14
0
class FileXAPITests(ComPAIRXAPITestCase):
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.user = self.data.authorized_student
        self.assignment = self.data.assignments[0]

    def test_on_get_file(self):
        # not report or attachment
        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="none",
            file_name="some_file"
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test report
        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="report",
            file_name="some_report.csv"
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
        self.assertEqual(statements[0]['verb'], {
            'id': 'http://id.tincanapi.com/verb/downloaded',
            'display': {'en-US': 'downloaded'}
        })
        self.assertEqual(statements[0]['object'], {
            'id': 'https://localhost:8888/app/report/some_report.csv',
            'definition': {'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': 'Report'}},
            'objectType': 'Activity'
        })
        self.assertNotIn('result', statements[0])
        self.assertNotIn('context', statements[0])

        # test attachment without file record
        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="attachment",
            file_name="some_file"
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test attachment file record (not linked to assignments or answers)
        file_record = self.data.create_file(self.user)
        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="attachment",
            file_name=file_record.name
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test attachment file record (assignment)
        self.assignment.file = file_record
        db.session.commit()

        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="attachment",
            file_name=file_record.name
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
        self.assertEqual(statements[0]['verb'], {
            'id': 'http://id.tincanapi.com/verb/downloaded',
            'display': {'en-US': 'downloaded'}
        })
        self.assertEqual(statements[0]['object'], {
            'id': 'https://localhost:8888/app/attachment/'+file_record.name,
            'definition': {'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': 'Assignment attachment'}},
            'objectType': 'Activity'
        })
        self.assertNotIn('result', statements[0])
        self.assertEqual(statements[0]['context'], {
            'contextActivities': {
                'parent': [
                    {'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid, 'objectType': 'Activity'}
                ],
                'grouping': [
                    {'id': 'https://localhost:8888/app/xapi/course/'+self.data.main_course.uuid, 'objectType': 'Activity'}
                ]
            }
        })

        # test attachment file record (answer)
        self.assignment.file = None
        answer = AnswerFactory(
            assignment=self.assignment,
            user=self.user,
            file=file_record
        )
        db.session.commit()

        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="attachment",
            file_name=file_record.name
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
        self.assertEqual(statements[0]['verb'], {
            'id': 'http://id.tincanapi.com/verb/downloaded',
            'display': {'en-US': 'downloaded'}
        })
        self.assertEqual(statements[0]['object'], {
            'id': 'https://localhost:8888/app/attachment/'+file_record.name,
            'definition': {'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': 'Assignment answer attachment'}},
            'objectType': 'Activity'
        })
        self.assertNotIn('result', statements[0])
        self.assertEqual(statements[0]['context'], {
            'contextActivities': {
                'parent': [
                    {'id': 'https://localhost:8888/app/xapi/answer/'+answer.uuid, 'objectType': 'Activity'}
                ],
                'grouping': [
                    {'id': 'https://localhost:8888/app/xapi/course/'+self.data.main_course.uuid, 'objectType': 'Activity'},
                    {'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid, 'objectType': 'Activity'},
                    {'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity'}
                ]
            }
        })
Exemple #15
0
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.lti_data = LTITestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)
        self.course = self.data.main_course
        self.lti_context = self.lti_data.create_context(
            self.lti_data.lti_consumer,
            compair_course_id=self.course.id,
            lis_course_offering_sourcedid="sis_course_id",
            lis_course_section_sourcedid="sis_section_id",
        )
        self.assignment = self.data.assignments[0]
        self.answer = AnswerFactory(assignment=self.assignment, user=self.user)
        db.session.commit()

        self.expected_caliper_course = {
            'academicSession':
            self.course.term,
            'dateCreated':
            self.course.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.course.modified.replace(tzinfo=pytz.utc).isoformat(),
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid,
            'name':
            self.course.name,
            'type':
            'CourseOffering',
            'extensions': {
                'ltiContexts': [{
                    'context_id':
                    self.lti_context.context_id,
                    'oauth_consumer_key':
                    self.lti_data.lti_consumer.oauth_consumer_key,
                    'lis_course_offering_sourcedid':
                    "sis_course_id",
                    'lis_course_section_sourcedid':
                    "sis_section_id",
                }]
            }
        }

        self.expected_caliper_assignment = {
            'name':
            self.assignment.name,
            'type':
            'Assessment',
            'dateCreated':
            self.assignment.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(),
            'dateToStartOn':
            self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(),
            'description':
            self.assignment.description,
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid,
            'isPartOf':
            self.expected_caliper_course,
            'items': [{
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid + "/question",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/comparison/question/1",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/1",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/2",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/comparison/question/2",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/3",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/4",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/comparison/question/3",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/5",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/6",
                'type':
                'AssessmentItem'
            }],
        }

        self.expected_caliper_assignment_question = {
            'name':
            self.assignment.name,
            'type':
            'AssessmentItem',
            'dateCreated':
            self.assignment.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(),
            'dateToStartOn':
            self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(),
            'dateToSubmit':
            self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(),
            'description':
            self.assignment.description,
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/question",
            'isPartOf':
            self.expected_caliper_assignment,
        }

        self.expected_caliper_answer_attempt = {
            'assignable':
            self.expected_caliper_assignment_question,
            'assignee':
            self.get_compair_caliper_actor(self.user),
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/question/attempt/" +
            self.answer.attempt_uuid,
            'duration':
            "PT05M00S",
            'startedAtTime':
            self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(),
            'endedAtTime':
            self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(),
            'type':
            'Attempt'
        }

        self.expected_caliper_answer = {
            'attempt':
            self.expected_caliper_answer_attempt,
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/answer/" +
            self.answer.uuid,
            'type':
            'Response',
            'dateCreated':
            self.answer.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.answer.modified.replace(tzinfo=pytz.utc).isoformat(),
            'extensions': {
                'characterCount': len(self.answer.content),
                'content': self.answer.content,
                'isDraft': False,
                'wordCount': 8,
            }
        }

        self.expected_xapi_course = {
            'id': "https://localhost:8888/app/course/" + self.course.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/course',
                'name': {
                    'en-US': self.course.name
                }
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_course = {
            'id': 'https://localhost:8888/course/' +
            self.lti_context.lis_course_offering_sourcedid,
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_section = {
            'id':
            'https://localhost:8888/course/' +
            self.lti_context.lis_course_offering_sourcedid + '/section/' +
            self.lti_context.lis_course_section_sourcedid,
            'objectType':
            'Activity'
        }

        self.expected_xapi_assignment = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/assessment',
                'name': {
                    'en-US': self.assignment.name
                },
                'description': {
                    'en-US': self.assignment.description
                },
            },
            'objectType':
            'Activity'
        }

        self.expected_xapi_assignment_question = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/question",
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/question',
                'name': {
                    'en-US': self.assignment.name
                },
                'description': {
                    'en-US': self.assignment.description
                },
            },
            'objectType':
            'Activity'
        }

        self.expected_xapi_answer = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/answer/" +
            self.answer.uuid,
            'definition': {
                'type': 'http://id.tincanapi.com/activitytype/solution',
                'extensions': {
                    'http://id.tincanapi.com/extension/isDraft': False
                }
            },
            'objectType':
            'Activity'
        }
Exemple #16
0
 def setUp(self):
     super(LoginAPITests, self).setUp()
     self.data = SimpleAssignmentTestData()
Exemple #17
0
 def setUp(self):
     super(CoursesLTIAPITests, self).setUp()
     self.data = SimpleAssignmentTestData()
     self.lti_data = LTITestData()
Exemple #18
0
class FileXAPITests(ComPAIRXAPITestCase):
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.user = self.data.authorized_student
        self.assignment = self.data.assignments[0]

    def test_on_get_file(self):
        # not report or attachment
        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="none",
                         file_name="some_file")

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test report
        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="report",
                         file_name="some_report.csv")

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'],
                         self.get_compair_actor(self.user))
        self.assertEqual(
            statements[0]['verb'], {
                'id': 'http://id.tincanapi.com/verb/downloaded',
                'display': {
                    'en-US': 'downloaded'
                }
            })
        self.assertEqual(
            statements[0]['object'], {
                'id': 'https://localhost:8888/app/report/some_report.csv',
                'definition': {
                    'type': 'http://activitystrea.ms/schema/1.0/file',
                    'name': {
                        'en-US': 'Report'
                    }
                },
                'objectType': 'Activity'
            })
        self.assertNotIn('result', statements[0])
        self.assertNotIn('context', statements[0])

        # test attachment without file record
        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="attachment",
                         file_name="some_file")

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test attachment file record (not linked to assignments or answers)
        file_record = self.data.create_file(self.user)
        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="attachment",
                         file_name=file_record.name)

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test attachment file record (assignment)
        self.assignment.file = file_record
        db.session.commit()

        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="attachment",
                         file_name=file_record.name)

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'],
                         self.get_compair_actor(self.user))
        self.assertEqual(
            statements[0]['verb'], {
                'id': 'http://id.tincanapi.com/verb/downloaded',
                'display': {
                    'en-US': 'downloaded'
                }
            })
        self.assertEqual(
            statements[0]['object'], {
                'id':
                'https://localhost:8888/app/attachment/' + file_record.name,
                'definition': {
                    'type': 'http://activitystrea.ms/schema/1.0/file',
                    'name': {
                        'en-US': 'Assignment attachment'
                    }
                },
                'objectType': 'Activity'
            })
        self.assertNotIn('result', statements[0])
        self.assertEqual(
            statements[0]['context'], {
                'contextActivities': {
                    'parent': [{
                        'id':
                        'https://localhost:8888/app/xapi/assignment/' +
                        self.assignment.uuid,
                        'objectType':
                        'Activity'
                    }],
                    'grouping': [{
                        'id':
                        'https://localhost:8888/app/xapi/course/' +
                        self.data.main_course.uuid,
                        'objectType':
                        'Activity'
                    }]
                }
            })

        # test attachment file record (answer)
        self.assignment.file = None
        answer = AnswerFactory(assignment=self.assignment,
                               user=self.user,
                               file=file_record)
        db.session.commit()

        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="attachment",
                         file_name=file_record.name)

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'],
                         self.get_compair_actor(self.user))
        self.assertEqual(
            statements[0]['verb'], {
                'id': 'http://id.tincanapi.com/verb/downloaded',
                'display': {
                    'en-US': 'downloaded'
                }
            })
        self.assertEqual(
            statements[0]['object'], {
                'id':
                'https://localhost:8888/app/attachment/' + file_record.name,
                'definition': {
                    'type': 'http://activitystrea.ms/schema/1.0/file',
                    'name': {
                        'en-US': 'Assignment answer attachment'
                    }
                },
                'objectType': 'Activity'
            })
        self.assertNotIn('result', statements[0])
        self.assertEqual(
            statements[0]['context'], {
                'contextActivities': {
                    'parent':
                    [{
                        'id': 'https://localhost:8888/app/xapi/answer/' +
                        answer.uuid,
                        'objectType': 'Activity'
                    }],
                    'grouping': [{
                        'id':
                        'https://localhost:8888/app/xapi/course/' +
                        self.data.main_course.uuid,
                        'objectType':
                        'Activity'
                    }, {
                        'id':
                        'https://localhost:8888/app/xapi/assignment/' +
                        self.assignment.uuid,
                        'objectType':
                        'Activity'
                    }, {
                        'id':
                        'https://localhost:8888/app/xapi/assignment/' +
                        self.assignment.uuid + '/question',
                        'objectType':
                        'Activity'
                    }]
                }
            })
Exemple #19
0
 def setUp(self):
     super(CoursesLTIAPITests, self).setUp()
     self.data = SimpleAssignmentTestData()
     self.lti_data = LTITestData()
Exemple #20
0
class FileLearningRecordTests(ComPAIRLearningRecordTestCase):
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.lti_data = LTITestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)
        self.course = self.data.main_course
        self.lti_context = self.lti_data.create_context(
            self.lti_data.lti_consumer,
            compair_course_id=self.course.id,
            lis_course_offering_sourcedid="sis_course_id",
            lis_course_section_sourcedid="sis_section_id",
        )
        self.assignment = self.data.assignments[0]
        self.answer = AnswerFactory(assignment=self.assignment, user=self.user)
        db.session.commit()

        self.expected_caliper_course = {
            'academicSession':
            self.course.term,
            'dateCreated':
            self.course.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.course.modified.replace(tzinfo=pytz.utc).isoformat(),
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid,
            'name':
            self.course.name,
            'type':
            'CourseOffering',
            'extensions': {
                'ltiContexts': [{
                    'context_id':
                    self.lti_context.context_id,
                    'oauth_consumer_key':
                    self.lti_data.lti_consumer.oauth_consumer_key,
                    'lis_course_offering_sourcedid':
                    "sis_course_id",
                    'lis_course_section_sourcedid':
                    "sis_section_id",
                }]
            }
        }

        self.expected_caliper_assignment = {
            'name':
            self.assignment.name,
            'type':
            'Assessment',
            'dateCreated':
            self.assignment.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(),
            'dateToStartOn':
            self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(),
            'description':
            self.assignment.description,
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid,
            'isPartOf':
            self.expected_caliper_course,
            'items': [{
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid + "/question",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/comparison/question/1",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/1",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/2",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/comparison/question/2",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/3",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/4",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/comparison/question/3",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/5",
                'type':
                'AssessmentItem'
            }, {
                'id':
                "https://localhost:8888/app/course/" + self.course.uuid +
                "/assignment/" + self.assignment.uuid +
                "/evaluation/question/6",
                'type':
                'AssessmentItem'
            }],
        }

        self.expected_caliper_assignment_question = {
            'name':
            self.assignment.name,
            'type':
            'AssessmentItem',
            'dateCreated':
            self.assignment.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(),
            'dateToStartOn':
            self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(),
            'dateToSubmit':
            self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(),
            'description':
            self.assignment.description,
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/question",
            'isPartOf':
            self.expected_caliper_assignment,
        }

        self.expected_caliper_answer_attempt = {
            'assignable':
            self.expected_caliper_assignment_question,
            'assignee':
            self.get_compair_caliper_actor(self.user),
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/question/attempt/" +
            self.answer.attempt_uuid,
            'duration':
            "PT05M00S",
            'startedAtTime':
            self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(),
            'endedAtTime':
            self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(),
            'type':
            'Attempt'
        }

        self.expected_caliper_answer = {
            'attempt':
            self.expected_caliper_answer_attempt,
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/answer/" +
            self.answer.uuid,
            'type':
            'Response',
            'dateCreated':
            self.answer.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.answer.modified.replace(tzinfo=pytz.utc).isoformat(),
            'extensions': {
                'characterCount': len(self.answer.content),
                'content': self.answer.content,
                'isDraft': False,
                'wordCount': 8,
            }
        }

        self.expected_xapi_course = {
            'id': "https://localhost:8888/app/course/" + self.course.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/course',
                'name': {
                    'en-US': self.course.name
                }
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_course = {
            'id': 'https://localhost:8888/course/' +
            self.lti_context.lis_course_offering_sourcedid,
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_section = {
            'id':
            'https://localhost:8888/course/' +
            self.lti_context.lis_course_offering_sourcedid + '/section/' +
            self.lti_context.lis_course_section_sourcedid,
            'objectType':
            'Activity'
        }

        self.expected_xapi_assignment = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/assessment',
                'name': {
                    'en-US': self.assignment.name
                },
                'description': {
                    'en-US': self.assignment.description
                },
            },
            'objectType':
            'Activity'
        }

        self.expected_xapi_assignment_question = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/question",
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/question',
                'name': {
                    'en-US': self.assignment.name
                },
                'description': {
                    'en-US': self.assignment.description
                },
            },
            'objectType':
            'Activity'
        }

        self.expected_xapi_answer = {
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid +
            "/assignment/" + self.assignment.uuid + "/answer/" +
            self.answer.uuid,
            'definition': {
                'type': 'http://id.tincanapi.com/activitytype/solution',
                'extensions': {
                    'http://id.tincanapi.com/extension/isDraft': False
                }
            },
            'objectType':
            'Activity'
        }

    def test_on_get_file(self):
        # not report or attachment
        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="none",
                         file_name="some_file")

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 0)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 0)

        # test report
        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="report",
                         file_name="some_report.csv")

        expected_caliper_object = {
            "id": 'https://localhost:8888/app/report/some_report.csv',
            "type": "Document",
            "name": "some_report.csv",
            "mediaType": "text/csv"
        }

        expected_caliper_event = {
            'action':
            'Viewed',
            'actor':
            self.get_compair_caliper_actor(self.user),
            'object':
            expected_caliper_object,
            'session':
            self.get_caliper_session(self.get_compair_caliper_actor(
                self.user)),
            'type':
            'ViewEvent'
        }

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_object = {
            'id': 'https://localhost:8888/app/report/some_report.csv',
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/file',
                'name': {
                    'en-US': 'some_report.csv'
                },
                'extensions': {
                    'http://id.tincanapi.com/extension/mime-type': "text/csv"
                }
            },
            'objectType': 'Activity'
        }

        expected_xapi_verb = {
            'id': 'http://id.tincanapi.com/verb/downloaded',
            'display': {
                'en-US': 'downloaded'
            }
        }

        expected_xapi_context = {
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_xapi_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": expected_xapi_verb,
            "object": expected_xapi_object,
            "context": expected_xapi_context
        }

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)

        # test attachment without file record
        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="attachment",
                         file_name="some_file")

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 0)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 0)

        # test attachment file record (not linked to assignments or answers)
        file_record = self.data.create_file(self.user)
        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="attachment",
                         file_name=file_record.name)

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 0)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 0)

        # test attachment file record (assignment)
        self.assignment.file_id = file_record.id
        db.session.commit()

        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="attachment",
                         file_name=file_record.name)

        expected_caliper_object = {
            "id":
            'https://localhost:8888/app/attachment/' + file_record.name,
            "type":
            "Document",
            "name":
            file_record.alias,
            "mediaType":
            'application/pdf',
            "isPartOf":
            self.expected_caliper_assignment,
            "dateCreated":
            file_record.created.replace(tzinfo=pytz.utc).isoformat(),
            "dateModified":
            file_record.modified.replace(tzinfo=pytz.utc).isoformat()
        }

        self.expected_caliper_assignment[
            'dateModified'] = self.assignment.modified.replace(
                tzinfo=pytz.utc).isoformat()
        expected_caliper_event['object'] = expected_caliper_object
        expected_caliper_event['membership'] = self.get_caliper_membership(
            self.course, self.user, self.lti_context)

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_object = {
            'id': 'https://localhost:8888/app/attachment/' + file_record.name,
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/file',
                'name': {
                    'en-US': file_record.alias
                },
                'extensions': {
                    'http://id.tincanapi.com/extension/mime-type':
                    'application/pdf'
                }
            },
            'objectType': 'Activity'
        }

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_assignment],
                'grouping': [
                    self.expected_xapi_course, self.expected_xapi_sis_course,
                    self.expected_xapi_sis_section
                ]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_xapi_statement['object'] = expected_xapi_object
        expected_xapi_statement['context'] = expected_xapi_context

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)

        # test attachment file record (answer)
        self.assignment.file_id = None
        self.answer.file_id = file_record.id
        db.session.commit()

        on_get_file.send(current_app._get_current_object(),
                         event_name=on_get_file.name,
                         user=self.user,
                         file_type="attachment",
                         file_name=file_record.name)

        self.expected_caliper_assignment_question[
            'dateModified'] = self.assignment.modified.replace(
                tzinfo=pytz.utc).isoformat()
        self.expected_caliper_assignment[
            'dateModified'] = self.assignment.modified.replace(
                tzinfo=pytz.utc).isoformat()
        self.expected_caliper_answer[
            'dateModified'] = self.answer.modified.replace(
                tzinfo=pytz.utc).isoformat()
        expected_caliper_object["isPartOf"] = self.expected_caliper_answer

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_answer],
                'grouping': [
                    self.expected_xapi_assignment_question,
                    self.expected_xapi_assignment, self.expected_xapi_course,
                    self.expected_xapi_sis_course,
                    self.expected_xapi_sis_section
                ]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_xapi_statement['context'] = expected_xapi_context

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)

    def test_on_attach_file(self):
        file_record = self.data.create_file(self.user)
        self.assignment.file_id = file_record.id
        db.session.commit()

        # attache to assignment
        on_attach_file.send(
            current_app._get_current_object(),
            event_name=on_attach_file.name,
            user=self.user,
            file=file_record,
        )

        expected_caliper_object = {
            "id":
            'https://localhost:8888/app/attachment/' + file_record.name,
            "type":
            "Document",
            "name":
            file_record.alias,
            "mediaType":
            'application/pdf',
            "isPartOf":
            self.expected_caliper_assignment,
            "dateCreated":
            file_record.created.replace(tzinfo=pytz.utc).isoformat(),
            "dateModified":
            file_record.modified.replace(tzinfo=pytz.utc).isoformat()
        }

        self.expected_caliper_assignment[
            'dateModified'] = self.assignment.modified.replace(
                tzinfo=pytz.utc).isoformat()

        expected_caliper_event = {
            'action':
            'Attached',
            'actor':
            self.get_compair_caliper_actor(self.user),
            'membership':
            self.get_caliper_membership(self.course, self.user,
                                        self.lti_context),
            'object':
            expected_caliper_object,
            'session':
            self.get_caliper_session(self.get_compair_caliper_actor(
                self.user)),
            'type':
            'Event'
        }

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_verb = {
            'id': 'http://activitystrea.ms/schema/1.0/attach',
            'display': {
                'en-US': 'attached'
            }
        }

        expected_xapi_object = {
            'id': 'https://localhost:8888/app/attachment/' + file_record.name,
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/file',
                'name': {
                    'en-US': file_record.alias
                },
                'extensions': {
                    'http://id.tincanapi.com/extension/mime-type':
                    'application/pdf'
                }
            },
            'objectType': 'Activity'
        }

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_assignment],
                'grouping': [
                    self.expected_xapi_course, self.expected_xapi_sis_course,
                    self.expected_xapi_sis_section
                ]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_xapi_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": expected_xapi_verb,
            "object": expected_xapi_object,
            "context": expected_xapi_context
        }

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)

        # attach to answer
        self.assignment.file_id = None
        self.answer.file_id = file_record.id
        db.session.commit()

        on_attach_file.send(
            current_app._get_current_object(),
            event_name=on_attach_file.name,
            user=self.user,
            file=file_record,
        )

        self.expected_caliper_assignment_question[
            'dateModified'] = self.assignment.modified.replace(
                tzinfo=pytz.utc).isoformat()
        self.expected_caliper_assignment[
            'dateModified'] = self.assignment.modified.replace(
                tzinfo=pytz.utc).isoformat()
        self.expected_caliper_answer[
            'dateModified'] = self.answer.modified.replace(
                tzinfo=pytz.utc).isoformat()
        expected_caliper_object["isPartOf"] = self.expected_caliper_answer

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_answer],
                'grouping': [
                    self.expected_xapi_assignment_question,
                    self.expected_xapi_assignment, self.expected_xapi_course,
                    self.expected_xapi_sis_course,
                    self.expected_xapi_sis_section
                ]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_xapi_statement['context'] = expected_xapi_context

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)

    def test_on_detach_file(self):
        file_record = self.data.create_file(self.user)
        db.session.commit()

        # attache to assignment
        on_detach_file.send(current_app._get_current_object(),
                            event_name=on_detach_file.name,
                            user=self.user,
                            file=file_record,
                            assignment=self.assignment)

        expected_caliper_object = {
            "id":
            'https://localhost:8888/app/attachment/' + file_record.name,
            "type":
            "Document",
            "name":
            file_record.alias,
            "mediaType":
            'application/pdf',
            "isPartOf":
            self.expected_caliper_assignment,
            "dateCreated":
            file_record.created.replace(tzinfo=pytz.utc).isoformat(),
            "dateModified":
            file_record.modified.replace(tzinfo=pytz.utc).isoformat()
        }

        expected_caliper_event = {
            'action':
            'Removed',
            'actor':
            self.get_compair_caliper_actor(self.user),
            'membership':
            self.get_caliper_membership(self.course, self.user,
                                        self.lti_context),
            'object':
            expected_caliper_object,
            'session':
            self.get_caliper_session(self.get_compair_caliper_actor(
                self.user)),
            'type':
            'Event'
        }

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_verb = {
            'id': 'http://activitystrea.ms/schema/1.0/delete',
            'display': {
                'en-US': 'deleted'
            }
        }

        expected_xapi_object = {
            'id': 'https://localhost:8888/app/attachment/' + file_record.name,
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/file',
                'name': {
                    'en-US': file_record.alias
                },
                'extensions': {
                    'http://id.tincanapi.com/extension/mime-type':
                    'application/pdf'
                }
            },
            'objectType': 'Activity'
        }

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_assignment],
                'grouping': [
                    self.expected_xapi_course, self.expected_xapi_sis_course,
                    self.expected_xapi_sis_section
                ]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_xapi_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": expected_xapi_verb,
            "object": expected_xapi_object,
            "context": expected_xapi_context
        }

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)

        # attach to answer
        on_detach_file.send(current_app._get_current_object(),
                            event_name=on_detach_file.name,
                            user=self.user,
                            file=file_record,
                            answer=self.answer)

        expected_caliper_object["isPartOf"] = self.expected_caliper_answer

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_answer],
                'grouping': [
                    self.expected_xapi_assignment_question,
                    self.expected_xapi_assignment, self.expected_xapi_course,
                    self.expected_xapi_sis_course,
                    self.expected_xapi_sis_section
                ]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info':
                self.get_xapi_session_info()
            }
        }

        expected_xapi_statement['context'] = expected_xapi_context

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)
Exemple #21
0
class FileLearningRecordTests(ComPAIRLearningRecordTestCase):
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.lti_data = LTITestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)
        self.course = self.data.main_course
        self.lti_context = self.lti_data.create_context(
            self.lti_data.lti_consumer,
            compair_course_id=self.course.id,
            lis_course_offering_sourcedid="sis_course_id",
            lis_course_section_sourcedid="sis_section_id",
        )
        self.assignment = self.data.assignments[0]
        self.answer = AnswerFactory(
            assignment=self.assignment,
            user=self.user
        )
        db.session.commit()

        self.expected_caliper_course = {
            'academicSession': self.course.term,
            'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(),
            'id': "https://localhost:8888/app/course/"+self.course.uuid,
            'name': self.course.name,
            'type': 'CourseOffering',
            'extensions': {
                'ltiContexts': [{
                    'context_id': self.lti_context.context_id,
                    'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key,
                    'lis_course_offering_sourcedid': "sis_course_id",
                    'lis_course_section_sourcedid': "sis_section_id",
                }]
            }
        }

        self.expected_caliper_assignment = {
            'name': self.assignment.name,
            'type': 'Assessment',
            'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(),
            'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(),
            'description': self.assignment.description,
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid,
            'isPartOf': self.expected_caliper_course,
            'items': [{
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/1",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/1",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/2",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/2",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/3",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/4",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/3",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/5",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/6",
                'type': 'AssessmentItem'
            }],
        }

        self.expected_caliper_assignment_question = {
            'name': self.assignment.name,
            'type': 'AssessmentItem',
            'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(),
            'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(),
            'dateToSubmit': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(),
            'description': self.assignment.description,
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question",
            'isPartOf': self.expected_caliper_assignment,
        }

        self.expected_caliper_answer_attempt = {
            'assignable': self.expected_caliper_assignment_question,
            'assignee': self.get_compair_caliper_actor(self.user),
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question/attempt/"+self.answer.attempt_uuid,
            'duration': "PT05M00S",
            'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(),
            'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(),
            'type': 'Attempt'
        }

        self.expected_caliper_answer = {
            'attempt': self.expected_caliper_answer_attempt,
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer.uuid,
            'type': 'Response',
            'dateCreated': self.answer.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified': self.answer.modified.replace(tzinfo=pytz.utc).isoformat(),
            'extensions': {
                'characterCount': len(self.answer.content),
                'content': self.answer.content,
                'isDraft': False,
                'wordCount': 8,
            }
        }

        self.expected_xapi_course = {
            'id': "https://localhost:8888/app/course/"+self.course.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/course',
                'name': {'en-US': self.course.name}
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_course = {
            'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid,
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_section = {
            'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid+'/section/'+self.lti_context.lis_course_section_sourcedid,
            'objectType': 'Activity'
        }

        self.expected_xapi_assignment = {
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/assessment',
                'name': {'en-US': self.assignment.name},
                'description': {'en-US': self.assignment.description},
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_assignment_question = {
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question",
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/question',
                'name': {'en-US': self.assignment.name},
                'description': {'en-US': self.assignment.description},
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_answer = {
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer.uuid,
            'definition': {
                'type': 'http://id.tincanapi.com/activitytype/solution',
                'extensions': {
                    'http://id.tincanapi.com/extension/isDraft': False
                }
            },
            'objectType': 'Activity'
        }


    def test_on_get_file(self):
        # not report or attachment
        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="none",
            file_name="some_file"
        )

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 0)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 0)

        # test report
        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="report",
            file_name="some_report.csv"
        )

        expected_caliper_object = {
            "id": 'https://localhost:8888/app/report/some_report.csv',
            "type": "Document",
            "name": "some_report.csv",
            "mediaType": "text/csv"
        }

        expected_caliper_event = {
            'action': 'Viewed',
            'actor': self.get_compair_caliper_actor(self.user),
            'object': expected_caliper_object,
            'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)),
            'type': 'ViewEvent'
        }

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_object = {
            'id': 'https://localhost:8888/app/report/some_report.csv',
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/file',
                'name': {'en-US': 'some_report.csv'},
                'extensions': {
                    'http://id.tincanapi.com/extension/mime-type': "text/csv"
                }
            },
            'objectType': 'Activity'
        }

        expected_xapi_verb = {
            'id': 'http://id.tincanapi.com/verb/downloaded',
            'display': {'en-US': 'downloaded'}
        }

        expected_xapi_context = {
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info()
            }
        }

        expected_xapi_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": expected_xapi_verb,
            "object": expected_xapi_object,
            "context": expected_xapi_context
        }

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)

        # test attachment without file record
        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="attachment",
            file_name="some_file"
        )

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 0)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 0)


        # test attachment file record (not linked to assignments or answers)
        file_record = self.data.create_file(self.user)
        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="attachment",
            file_name=file_record.name
        )

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 0)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 0)

        # test attachment file record (assignment)
        self.assignment.file_id = file_record.id
        db.session.commit()

        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="attachment",
            file_name=file_record.name
        )

        expected_caliper_object = {
            "id": 'https://localhost:8888/app/attachment/'+file_record.name,
            "type": "Document",
            "name": file_record.alias,
            "mediaType": 'application/pdf',
            "isPartOf": self.expected_caliper_assignment,
            "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(),
            "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat()
        }

        self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat()
        expected_caliper_event['object'] = expected_caliper_object
        expected_caliper_event['membership'] = self.get_caliper_membership(self.course, self.user, self.lti_context)

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_object = {
            'id': 'https://localhost:8888/app/attachment/'+file_record.name,
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/file',
                'name': {'en-US': file_record.alias},
                'extensions': {
                    'http://id.tincanapi.com/extension/mime-type': 'application/pdf'
                }
            },
            'objectType': 'Activity'
        }

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_assignment],
                'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info()
            }
        }

        expected_xapi_statement['object'] = expected_xapi_object
        expected_xapi_statement['context'] = expected_xapi_context

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)


        # test attachment file record (answer)
        self.assignment.file_id = None
        self.answer.file_id = file_record.id
        db.session.commit()

        on_get_file.send(
            current_app._get_current_object(),
            event_name=on_get_file.name,
            user=self.user,
            file_type="attachment",
            file_name=file_record.name
        )

        self.expected_caliper_assignment_question['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat()
        self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat()
        self.expected_caliper_answer['dateModified'] = self.answer.modified.replace(tzinfo=pytz.utc).isoformat()
        expected_caliper_object["isPartOf"] = self.expected_caliper_answer

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_answer],
                'grouping': [self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info()
            }
        }

        expected_xapi_statement['context'] = expected_xapi_context

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)


    def test_on_attach_file(self):
        file_record = self.data.create_file(self.user)
        self.assignment.file_id = file_record.id
        db.session.commit()

        # attache to assignment
        on_attach_file.send(
            current_app._get_current_object(),
            event_name=on_attach_file.name,
            user=self.user,
            file=file_record,
        )

        expected_caliper_object = {
            "id": 'https://localhost:8888/app/attachment/'+file_record.name,
            "type": "Document",
            "name": file_record.alias,
            "mediaType": 'application/pdf',
            "isPartOf": self.expected_caliper_assignment,
            "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(),
            "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat()
        }

        self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat()

        expected_caliper_event = {
            'action': 'Attached',
            'actor': self.get_compair_caliper_actor(self.user),
            'membership': self.get_caliper_membership(self.course, self.user, self.lti_context),
            'object': expected_caliper_object,
            'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)),
            'type': 'Event'
        }

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_verb = {
            'id': 'http://activitystrea.ms/schema/1.0/attach',
            'display': {'en-US': 'attached'}
        }

        expected_xapi_object = {
            'id': 'https://localhost:8888/app/attachment/'+file_record.name,
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/file',
                'name': {'en-US': file_record.alias},
                'extensions': {
                    'http://id.tincanapi.com/extension/mime-type': 'application/pdf'
                }
            },
            'objectType': 'Activity'
        }

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_assignment],
                'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info()
            }
        }

        expected_xapi_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": expected_xapi_verb,
            "object": expected_xapi_object,
            "context": expected_xapi_context
        }

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)



        # attach to answer
        self.assignment.file_id = None
        self.answer.file_id = file_record.id
        db.session.commit()

        on_attach_file.send(
            current_app._get_current_object(),
            event_name=on_attach_file.name,
            user=self.user,
            file=file_record,
        )

        self.expected_caliper_assignment_question['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat()
        self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat()
        self.expected_caliper_answer['dateModified'] = self.answer.modified.replace(tzinfo=pytz.utc).isoformat()
        expected_caliper_object["isPartOf"] = self.expected_caliper_answer

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_answer],
                'grouping': [self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info()
            }
        }

        expected_xapi_statement['context'] = expected_xapi_context

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)


    def test_on_detach_file(self):
        file_record = self.data.create_file(self.user)
        db.session.commit()

        # attache to assignment
        on_detach_file.send(
            current_app._get_current_object(),
            event_name=on_detach_file.name,
            user=self.user,
            file=file_record,
            assignment=self.assignment
        )

        expected_caliper_object = {
            "id": 'https://localhost:8888/app/attachment/'+file_record.name,
            "type": "Document",
            "name": file_record.alias,
            "mediaType": 'application/pdf',
            "isPartOf": self.expected_caliper_assignment,
            "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(),
            "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat()
        }

        expected_caliper_event = {
            'action': 'Removed',
            'actor': self.get_compair_caliper_actor(self.user),
            'membership': self.get_caliper_membership(self.course, self.user, self.lti_context),
            'object': expected_caliper_object,
            'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)),
            'type': 'Event'
        }

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_verb = {
            'id': 'http://activitystrea.ms/schema/1.0/delete',
            'display': {'en-US': 'deleted'}
        }

        expected_xapi_object = {
            'id': 'https://localhost:8888/app/attachment/'+file_record.name,
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/file',
                'name': {'en-US': file_record.alias},
                'extensions': {
                    'http://id.tincanapi.com/extension/mime-type': 'application/pdf'
                }
            },
            'objectType': 'Activity'
        }

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_assignment],
                'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info()
            }
        }

        expected_xapi_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": expected_xapi_verb,
            "object": expected_xapi_object,
            "context": expected_xapi_context
        }

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)



        # attach to answer
        on_detach_file.send(
            current_app._get_current_object(),
            event_name=on_detach_file.name,
            user=self.user,
            file=file_record,
            answer=self.answer
        )

        expected_caliper_object["isPartOf"] = self.expected_caliper_answer

        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        expected_xapi_context = {
            'contextActivities': {
                'parent': [self.expected_xapi_answer],
                'grouping': [self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section]
            },
            'extensions': {
                'http://id.tincanapi.com/extension/browser-info': {},
                'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info()
            }
        }

        expected_xapi_statement['context'] = expected_xapi_context

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)
Exemple #22
0
 def setUp(self):
     super(ComPAIRXAPITestCase, self).setUp()
     self.data = SimpleAssignmentTestData()
     self.user = self.data.authorized_student
     self.course = self.data.main_course
     self.assignment = self.data.assignments[0]
Exemple #23
0
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAssignmentTestData()
        self.lti_data = LTITestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)
        self.course = self.data.main_course
        self.lti_context = self.lti_data.create_context(
            self.lti_data.lti_consumer,
            compair_course_id=self.course.id,
            lis_course_offering_sourcedid="sis_course_id",
            lis_course_section_sourcedid="sis_section_id",
        )
        self.assignment = self.data.assignments[0]
        self.answer = AnswerFactory(
            assignment=self.assignment,
            user=self.user
        )
        db.session.commit()

        self.expected_caliper_course = {
            'academicSession': self.course.term,
            'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(),
            'id': "https://localhost:8888/app/course/"+self.course.uuid,
            'name': self.course.name,
            'type': 'CourseOffering',
            'extensions': {
                'ltiContexts': [{
                    'context_id': self.lti_context.context_id,
                    'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key,
                    'lis_course_offering_sourcedid': "sis_course_id",
                    'lis_course_section_sourcedid': "sis_section_id",
                }]
            }
        }

        self.expected_caliper_assignment = {
            'name': self.assignment.name,
            'type': 'Assessment',
            'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(),
            'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(),
            'description': self.assignment.description,
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid,
            'isPartOf': self.expected_caliper_course,
            'items': [{
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/1",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/1",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/2",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/2",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/3",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/4",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/3",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/5",
                'type': 'AssessmentItem'
            }, {
                'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/6",
                'type': 'AssessmentItem'
            }],
        }

        self.expected_caliper_assignment_question = {
            'name': self.assignment.name,
            'type': 'AssessmentItem',
            'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(),
            'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(),
            'dateToSubmit': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(),
            'description': self.assignment.description,
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question",
            'isPartOf': self.expected_caliper_assignment,
        }

        self.expected_caliper_answer_attempt = {
            'assignable': self.expected_caliper_assignment_question,
            'assignee': self.get_compair_caliper_actor(self.user),
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question/attempt/"+self.answer.attempt_uuid,
            'duration': "PT05M00S",
            'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(),
            'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(),
            'type': 'Attempt'
        }

        self.expected_caliper_answer = {
            'attempt': self.expected_caliper_answer_attempt,
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer.uuid,
            'type': 'Response',
            'dateCreated': self.answer.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified': self.answer.modified.replace(tzinfo=pytz.utc).isoformat(),
            'extensions': {
                'characterCount': len(self.answer.content),
                'content': self.answer.content,
                'isDraft': False,
                'wordCount': 8,
            }
        }

        self.expected_xapi_course = {
            'id': "https://localhost:8888/app/course/"+self.course.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/course',
                'name': {'en-US': self.course.name}
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_course = {
            'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid,
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_section = {
            'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid+'/section/'+self.lti_context.lis_course_section_sourcedid,
            'objectType': 'Activity'
        }

        self.expected_xapi_assignment = {
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/assessment',
                'name': {'en-US': self.assignment.name},
                'description': {'en-US': self.assignment.description},
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_assignment_question = {
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question",
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/question',
                'name': {'en-US': self.assignment.name},
                'description': {'en-US': self.assignment.description},
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_answer = {
            'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer.uuid,
            'definition': {
                'type': 'http://id.tincanapi.com/activitytype/solution',
                'extensions': {
                    'http://id.tincanapi.com/extension/isDraft': False
                }
            },
            'objectType': 'Activity'
        }