def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAnswersTestData() self.auth_data = ThirdPartyAuthTestData() self.lti_data = LTITestData() self.course = self.data.main_course self.assignment = self.data.assignments[0] self.user = self.data.create_user(SystemRole.instructor) self.data.enrol_user(self.user, self.data.get_course(), CourseRole.instructor) self.global_unique_identifier = 'mock_puid_è_global_unique_identifier'
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)
def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAnswersTestData() self.auth_data = ThirdPartyAuthTestData() self.course = self.data.main_course self.assignment = self.data.assignments[0] self.cas_user_auth = self.auth_data.create_cas_user_auth( SystemRole.instructor) self.cas_user = self.cas_user_auth.user self.data.enrol_user(self.cas_user, self.data.get_course(), CourseRole.instructor) self.saml_user_auth = self.auth_data.create_saml_user_auth( SystemRole.instructor) self.saml_user = self.saml_user_auth.user self.data.enrol_user(self.saml_user, self.data.get_course(), CourseRole.instructor)
def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAnswersTestData() self.auth_data = ThirdPartyAuthTestData() self.course = self.data.main_course self.assignment = self.data.assignments[0] self.cas_user_auth = self.auth_data.create_cas_user_auth(SystemRole.instructor) self.cas_user = self.cas_user_auth.user self.data.enrol_user(self.cas_user, self.data.get_course(), CourseRole.instructor) self.saml_user_auth = self.auth_data.create_saml_user_auth(SystemRole.instructor) self.saml_user = self.saml_user_auth.user self.data.enrol_user(self.saml_user, self.data.get_course(), CourseRole.instructor)
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)
class AccountLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAnswersTestData() self.auth_data = ThirdPartyAuthTestData() self.course = self.data.main_course self.assignment = self.data.assignments[0] self.cas_user_auth = self.auth_data.create_cas_user_auth( SystemRole.instructor) self.cas_user = self.cas_user_auth.user self.data.enrol_user(self.cas_user, self.data.get_course(), CourseRole.instructor) self.saml_user_auth = self.auth_data.create_saml_user_auth( SystemRole.instructor) self.saml_user = self.saml_user_auth.user self.data.enrol_user(self.saml_user, self.data.get_course(), CourseRole.instructor) def test_actor_accounts(self): for user, third_party_auth in [(self.cas_user, self.cas_user_auth), (self.saml_user, self.saml_user_auth)]: # test without homepage set # (should use compair actor account) self.app.config[ 'LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True self.app.config[ 'LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = None expected_actor = self.get_compair_xapi_actor(user) on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_actor) # test with homepage set and global unique identifier not set # (should use compair actor account) self.app.config[ 'LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = "http://third.party.homepage" on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_actor) expected_actor = self.get_compair_xapi_actor(user) # test with homepage set and global unique identifier set # (should use cas/saml actor account with overridden value used for name) user.global_unique_identifier = 'mock_puid_è_' + third_party_auth.third_party_type.value db.session.commit() expected_actor = self.get_unique_identifier_xapi_actor( user, "http://third.party.homepage/", 'mock_puid_è_' + third_party_auth.third_party_type.value) on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_actor) # disabling LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER should skip checking global unique identifer self.app.config[ 'LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False expected_actor = self.get_compair_xapi_actor(user) on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_actor)
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)
class AccountLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAnswersTestData() self.auth_data = ThirdPartyAuthTestData() self.lti_data = LTITestData() self.course = self.data.main_course self.assignment = self.data.assignments[0] self.user = self.data.create_user(SystemRole.instructor) self.data.enrol_user(self.user, self.data.get_course(), CourseRole.instructor) self.global_unique_identifier = 'mock_puid_è_global_unique_identifier' def test_actor_accounts(self): user = self.user # test without homepage set # (should use compair actor account) self.app.config[ 'LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True self.app.config[ 'LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = None expected_caliper_actor = self.get_compair_caliper_actor(user) expected_xapi_actor = self.get_compair_xapi_actor(user) on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0]['actor'], expected_caliper_actor) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_xapi_actor) # test with homepage set and global unique identifier not set # (should use compair actor account) self.app.config[ 'LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = "http://third.party.homepage" on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0]['actor'], expected_caliper_actor) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_xapi_actor) # test with homepage set and global unique identifier set user.global_unique_identifier = self.global_unique_identifier db.session.commit() expected_caliper_actor = self.get_unique_identifier_caliper_actor( user, "http://third.party.homepage/", self.global_unique_identifier) expected_xapi_actor = self.get_unique_identifier_xapi_actor( user, "http://third.party.homepage/", self.global_unique_identifier) on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0]['actor'], expected_caliper_actor) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_xapi_actor) # disabling LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER should skip checking global unique identifer self.app.config[ 'LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False expected_caliper_actor = self.get_compair_caliper_actor(user) expected_xapi_actor = self.get_compair_xapi_actor(user) on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0]['actor'], expected_caliper_actor) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_xapi_actor) # test adding third party auths & lti user links # NOTE: xapi doesn't really add this extra info to actor since there # isn't anywhere to put it cas_auth = self.auth_data.create_third_party_user( user=user, third_party_type=ThirdPartyType.cas) saml_auth = self.auth_data.create_third_party_user( user=user, third_party_type=ThirdPartyType.saml) lti_user = self.lti_data.create_user( self.lti_data.lti_consumer, SystemRole.instructor, user, ) lti_user.student_number = '1234567890' lti_user.global_unique_identifier = self.global_unique_identifier lti_user.lis_person_sourcedid = 'asdfghjkl' db.session.commit() expected_caliper_actor = self.get_compair_caliper_actor( user, third_party_users=[cas_auth, saml_auth], lti_users=[lti_user]) expected_xapi_actor = self.get_compair_xapi_actor( user, third_party_users=[cas_auth, saml_auth], lti_users=[lti_user]) on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) print(events[0]['actor']) self.assertEqual(events[0]['actor'], expected_caliper_actor) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_xapi_actor)
def test_import_groups(self): auth_data = ThirdPartyAuthTestData() url = '/api/courses/' + self.fixtures.course.uuid + '/groups' content = self.fixtures.students[0].username + "," + self.fixtures.groups[0] encoded_content = content.encode() filename = "groups.csv" # test login required uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert401(rv) uploaded_file.close() # test unauthorized user uploaded_file = io.BytesIO(encoded_content) with self.login(self.fixtures.students[0].username): rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert403(rv) uploaded_file.close() uploaded_file = io.BytesIO(encoded_content) with self.login(self.fixtures.ta.username): rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert403(rv) uploaded_file.close() uploaded_file = io.BytesIO(encoded_content) with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert403(rv) uploaded_file.close() with self.login(self.fixtures.instructor.username): # test invalid course id invalid_url = '/api/courses/999/groups' uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(invalid_url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert404(rv) uploaded_file.close() # test invalid file type invalid_file = "groups.png" uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, invalid_file))) self.assert400(rv) uploaded_file.close() # test invalid user identifier uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(url, data=dict(userIdentifier="lastname", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(0, rv.json['success']) self.assertEqual({}, rv.json['invalids'][0]['member']) self.assertEqual("A valid user identifier is not given.", rv.json['invalids'][0]['message']) uploaded_file.close() # test missing user identifier uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert400(rv) uploaded_file.close() # test duplicate users in file duplicate = "".join([content, "\n", content]) uploaded_file = io.BytesIO(duplicate.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = [ '["', self.fixtures.students[0].username, '", "', self.fixtures.groups[0], '"]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("This user already exists in the file.", invalid['message']) uploaded_file.close() # test missing username missing_username = "******" + self.fixtures.groups[0] uploaded_file = io.BytesIO(missing_username.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = ['["", "', self.fixtures.groups[0], '"]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("No user with this ComPAIR username exists.", invalid['message']) uploaded_file.close() # test missing group name missing_group = self.fixtures.students[0].username + "," uploaded_file = io.BytesIO(missing_group.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(0, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = ['["', self.fixtures.students[0].username, '", ""]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("The group name is invalid.", invalid['message']) uploaded_file.close() # test invalid user invalid_user = "******" + self.fixtures.groups[0] uploaded_file = io.BytesIO(invalid_user.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = ['["username9999", "', self.fixtures.groups[0], '"]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("No user with this ComPAIR username exists.", invalid['message']) uploaded_file.close() # test successful import with username with_username = self.fixtures.students[0].username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_username.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close() # test invalid import with username self.app.config['APP_LOGIN_ENABLED'] = False with_username = self.fixtures.students[0].username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_username.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert400(rv) uploaded_file.close() self.app.config['APP_LOGIN_ENABLED'] = True # test successful import with student number with_studentno = self.fixtures.students[0].student_number + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_studentno.encode()) rv = self.client.post(url, data=dict(userIdentifier="student_number", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close() # test successful import with cas username cas_auth = auth_data.create_cas_user_auth(CourseRole.student) cas_user = cas_auth.user self.fixtures.enrol_user(cas_user, self.fixtures.course, CourseRole.student) with_cas_username = cas_auth.unique_identifier + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_cas_username.encode()) rv = self.client.post(url, data=dict(userIdentifier=ThirdPartyType.cas.value, file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close() # test invalid import with cas username self.app.config['CAS_LOGIN_ENABLED'] = False with_cas_username = cas_auth.unique_identifier + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_cas_username.encode()) rv = self.client.post(url, data=dict(userIdentifier=ThirdPartyType.cas.value, file=(uploaded_file, filename))) self.assert400(rv) uploaded_file.close() self.app.config['CAS_LOGIN_ENABLED'] = True # test import user not in course unauthorized_student = self.fixtures.unauthorized_student.username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(unauthorized_student.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = [ '["', self.fixtures.unauthorized_student.username, '", "', self.fixtures.groups[0], '"]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("The user is not enroled in the course", invalid['message']) uploaded_file.close() # test placing instructor in group add_instructor = self.fixtures.instructor.username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(add_instructor.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close() # test placing TA in group add_ta = self.fixtures.ta.username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(add_ta.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close()
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)
class AccountLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAnswersTestData() self.auth_data = ThirdPartyAuthTestData() self.course = self.data.main_course self.assignment = self.data.assignments[0] self.cas_user_auth = self.auth_data.create_cas_user_auth(SystemRole.instructor) self.cas_user = self.cas_user_auth.user self.data.enrol_user(self.cas_user, self.data.get_course(), CourseRole.instructor) self.saml_user_auth = self.auth_data.create_saml_user_auth(SystemRole.instructor) self.saml_user = self.saml_user_auth.user self.data.enrol_user(self.saml_user, self.data.get_course(), CourseRole.instructor) def test_actor_accounts(self): for user, third_party_auth in [(self.cas_user, self.cas_user_auth), (self.saml_user, self.saml_user_auth)]: # test without homepage set # (should use compair actor account) self.app.config['LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True self.app.config['LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = None expected_actor = self.get_compair_xapi_actor(user) on_assignment_modified.send( current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment ) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_actor) # test with homepage set and global unique identifier not set # (should use compair actor account) self.app.config['LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = "http://third.party.homepage" on_assignment_modified.send( current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment ) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_actor) expected_actor = self.get_compair_xapi_actor(user) # test with homepage set and global unique identifier set # (should use cas/saml actor account with overridden value used for name) user.global_unique_identifier = 'mock_puid_è_'+third_party_auth.third_party_type.value db.session.commit() expected_actor = self.get_unique_identifier_xapi_actor( user, "http://third.party.homepage/", 'mock_puid_è_'+third_party_auth.third_party_type.value ) on_assignment_modified.send( current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment ) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_actor) # disabling LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER should skip checking global unique identifer self.app.config['LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False expected_actor = self.get_compair_xapi_actor(user) on_assignment_modified.send( current_app._get_current_object(), event_name=on_assignment_modified.name, user=user, assignment=self.assignment ) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0]['actor'], expected_actor)
def test_update_password(self): url = '/api/users/{}/password' data = { 'oldpassword': '******', 'newpassword': '******' } # test login required rv = self.client.post( url.format(self.data.authorized_instructor.uuid), data=json.dumps(data), content_type='application/json') self.assert401(rv) # test student update password with self.login(self.data.authorized_student.username): # test without old password rv = self.client.post( url.format(self.data.authorized_student.uuid), data=json.dumps({'newpassword': '******'}), content_type='application/json') self.assert403(rv) self.assertEqual( 'The old password is incorrect or you do not have permission to change password.', rv.json['error']) # test incorrect old password invalid_input = data.copy() invalid_input['oldpassword'] = '******' rv = self.client.post( url.format(self.data.authorized_student.uuid), data=json.dumps(invalid_input), content_type='application/json') self.assert403(rv) self.assertEqual( 'The old password is incorrect or you do not have permission to change password.', rv.json['error']) # test with old password rv = self.client.post( url.format(self.data.authorized_student.uuid), data=json.dumps(data), content_type='application/json') self.assert200(rv) self.assertEqual(self.data.get_authorized_student().uuid, rv.json['id']) # test instructor update password with self.login(self.data.get_authorized_instructor().username): rv = self.client.post(url.format(str(999)), data=json.dumps(data), content_type='application/json') self.assert404(rv) # test instructor changes the password of a student in the course rv = self.client.post( url.format(self.data.get_authorized_student().uuid), data=json.dumps(data), content_type='application/json') self.assert200(rv) self.assertEqual(self.data.get_authorized_student().uuid, rv.json['id']) # test changing own password rv = self.client.post( url.format(self.data.get_authorized_instructor().uuid), data=json.dumps(data), content_type='application/json') self.assert200(rv) self.assertEqual(self.data.get_authorized_instructor().uuid, rv.json['id']) # test instructor changes the password of a student not in the course with self.login(self.data.get_unauthorized_instructor().username): rv = self.client.post( url.format(self.data.get_authorized_student().uuid), data=json.dumps(data), content_type='application/json') self.assert403(rv) self.assertEqual( '<p>{} does not have edit access to {}</p>'.format(self.data.get_unauthorized_instructor().username, self.data.get_authorized_student().username), rv.json['message']) # test admin update password with self.login('root'): # test admin changes student password without old password rv = self.client.post( url.format(self.data.get_authorized_student().uuid), data=json.dumps({'newpassword': '******'}), content_type='application/json') self.assert200(rv) self.assertEqual(self.data.get_authorized_student().uuid, rv.json['id']) # test update password of user with no compair login auth_data = ThirdPartyAuthTestData() cas_user_auth = auth_data.create_cas_user_auth(SystemRole.student) user = cas_user_auth.user self.data.enrol_user(user, self.data.get_course(), CourseRole.student) url = 'api/users/' + user.uuid + '/password' # update own password as cas user with self.cas_login(cas_user_auth.unique_identifier): # cannot change password rv = self.client.post(url, data=json.dumps(data), content_type='application/json') self.assert400(rv) self.assertEqual("Cannot update password. User does not use ComPAIR account login authentication method.", rv.json['error'])
def test_edit_user(self): user = self.data.get_authorized_student() url = 'api/users/' + user.uuid expected = { 'id': user.uuid, 'username': user.username, 'student_number': user.student_number, 'system_role': user.system_role.value, 'firstname': user.firstname, 'lastname': user.lastname, 'displayname': user.displayname, 'email': user.email } instructor = self.data.get_authorized_instructor() instructor_url = 'api/users/' + instructor.uuid expected_instructor = { 'id': instructor.uuid, 'username': instructor.username, 'student_number': instructor.student_number, 'system_role': instructor.system_role.value, 'firstname': instructor.firstname, 'lastname': instructor.lastname, 'displayname': instructor.displayname, 'email': instructor.email } # test login required rv = self.client.post(url, data=json.dumps(expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.data.get_unauthorized_instructor().username): rv = self.client.post(url, data=json.dumps(expected), content_type='application/json') self.assert403(rv) # test invalid user id with self.login('root'): rv = self.client.post('/api/users/999', data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test unmatched user's id invalid_url = '/api/users/' + self.data.get_unauthorized_instructor().uuid rv = self.client.post(invalid_url, data=json.dumps(expected), content_type='application/json') self.assert400(rv) # test duplicate username duplicate = expected.copy() duplicate['username'] = self.data.get_unauthorized_student().username rv = self.client.post(url, data=json.dumps(duplicate), content_type='application/json') self.assertStatus(rv, 409) self.assertEqual("This username already exists. Please pick another.", rv.json['error']) # test duplicate student number duplicate = expected.copy() duplicate['student_number'] = self.data.get_unauthorized_student().student_number rv = self.client.post(url, data=json.dumps(duplicate), content_type='application/json') self.assertStatus(rv, 409) self.assertEqual("This student number already exists. Please pick another.", rv.json['error']) # test successful update by admin valid = expected.copy() valid['displayname'] = "displayzzz" rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual("displayzzz", rv.json['displayname']) # test successful update by user (as instructor) with self.login(self.data.get_authorized_instructor().username): valid = expected_instructor.copy() valid['displayname'] = "thebest" rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual("thebest", rv.json['displayname']) # test successful update by user (as student) with self.login(self.data.get_authorized_student().username): valid = expected.copy() valid['displayname'] = "thebest" rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual("thebest", rv.json['displayname']) # test updating username, student number, usertype for system - instructor with self.login(instructor.username): # for student valid = expected.copy() valid['username'] = "******" rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual(user.username, rv.json['username']) valid = expected.copy() valid['student_number'] = "999999999999" rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual(user.student_number, rv.json['student_number']) valid = expected.copy() valid['system_role'] = SystemRole.student.value rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual(user.system_role.value, rv.json['system_role']) # for instructor valid = expected_instructor.copy() valid['username'] = "******" rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual(instructor.username, rv.json['username']) ignored = expected_instructor.copy() ignored['student_number'] = "999999999999" rv = self.client.post(instructor_url, data=json.dumps(ignored), content_type='application/json') self.assert200(rv) self.assertIsNone(rv.json['student_number']) self.assertEqual(instructor.student_number, rv.json['student_number']) valid = expected_instructor.copy() valid['system_role'] = SystemRole.student.value rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual(instructor.system_role.value, rv.json['system_role']) # test updating username, student number, usertype for system - admin with self.login('root'): # for student valid = expected.copy() valid['username'] = '******' rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual('newUsername', rv.json['username']) valid = expected.copy() valid['student_number'] = '99999999' rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual('99999999', rv.json['student_number']) valid = expected.copy() valid['system_role'] = SystemRole.student.value rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual(user.system_role.value, rv.json['system_role']) # for instructor valid = expected_instructor.copy() valid['username'] = "******" rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual(instructor.username, rv.json['username']) ignored = expected_instructor.copy() ignored['student_number'] = "999999999999" rv = self.client.post(instructor_url, data=json.dumps(ignored), content_type='application/json') self.assert200(rv) self.assertIsNone(rv.json['student_number']) self.assertEqual(instructor.student_number, rv.json['student_number']) valid = expected_instructor.copy() valid['system_role'] = SystemRole.student.value rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) self.assertEqual(instructor.system_role.value, rv.json['system_role']) # test edit user with no compair login auth_data = ThirdPartyAuthTestData() cas_user_auth = auth_data.create_cas_user_auth(SystemRole.student) user = cas_user_auth.user self.data.enrol_user(user, self.data.get_course(), CourseRole.student) url = 'api/users/' + user.uuid expected = { 'id': user.uuid, 'username': user.username, 'student_number': user.student_number, 'system_role': user.system_role.value, 'firstname': user.firstname, 'lastname': user.lastname, 'displayname': user.displayname, 'email': user.email } # edit own profile as cas user with self.cas_login(cas_user_auth.unique_identifier): # cannot change username (must be None) valid = expected.copy() valid['username'] = "******" rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) user = User.query.filter_by(uuid=rv.json['id']).one() self.assertIsNone(user.username) # test updating username as instructor with self.login(instructor.username): # cannot change username (must be None) valid = expected.copy() valid['username'] = "******" rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) user = User.query.filter_by(uuid=rv.json['id']).one() self.assertIsNone(user.username) # test updating username as system admin with self.login('root'): # admin can optionally change username valid = expected.copy() valid['username'] = '' rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) user = User.query.filter_by(uuid=rv.json['id']).one() self.assertIsNone(user.username) valid = expected.copy() valid['username'] = "******" rv = self.client.post(url, data=json.dumps(valid), content_type='application/json') self.assert200(rv) user = User.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(user.username, "valid_username")