260 lines
11 KiB
Python
260 lines
11 KiB
Python
import os
|
|
|
|
with open('addons/education_lms/tests/test_education_lms.py', 'a') as f:
|
|
f.write('''
|
|
# ==========================================
|
|
# PHASE H SECURITY TESTS
|
|
# ==========================================
|
|
|
|
# 37. Faculty A cannot read/modify Faculty B courses/content
|
|
def test_37_faculty_isolation(self):
|
|
# Faculty B course
|
|
course_b = self.env['education.lms.course'].with_user(self.user_faculty_b).create({
|
|
'title': 'Course B',
|
|
'faculty_id': self.user_faculty_b.id,
|
|
'program_id': self.program.id,
|
|
'academic_year_id': self.academic_year.id,
|
|
})
|
|
# Faculty A tries to read -> AccessError
|
|
with self.assertRaises(AccessError):
|
|
course_b.with_user(self.user_faculty_a).read(['title'])
|
|
|
|
# Faculty A tries to modify
|
|
with self.assertRaises(AccessError):
|
|
course_b.with_user(self.user_faculty_a).write({'title': 'Hacked'})
|
|
|
|
# Faculty A tries to delete
|
|
with self.assertRaises(AccessError):
|
|
course_b.with_user(self.user_faculty_a).unlink()
|
|
|
|
# Faculty A tries to create section under Course B -> AccessError
|
|
with self.assertRaises(AccessError):
|
|
self.env['education.lms.section'].with_user(self.user_faculty_a).create({
|
|
'title': 'Sec B',
|
|
'course_id': course_b.id
|
|
})
|
|
|
|
# 38. Student Isolation (cannot read/modify other student submission)
|
|
def test_38_student_isolation(self):
|
|
self.course_faculty_a.state = 'published'
|
|
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
|
|
'title': 'Assign H',
|
|
'course_id': self.course_faculty_a.id,
|
|
'due_date': fields.Date.today() + timedelta(days=5),
|
|
'state': 'published'
|
|
})
|
|
submission_a = self.env['education.lms.submission'].with_user(self.user_student_a).create({
|
|
'assignment_id': assignment.id,
|
|
'student_id': self.student_a.id,
|
|
'text_submission': 'Student A Work',
|
|
})
|
|
# Student B tries to read
|
|
with self.assertRaises(AccessError):
|
|
submission_a.with_user(self.user_student_b).read(['text_submission'])
|
|
|
|
# Student B tries to write
|
|
with self.assertRaises(AccessError):
|
|
submission_a.with_user(self.user_student_b).write({'text_submission': 'Hacked'})
|
|
|
|
# Student B tries to create submission for Student A
|
|
with self.assertRaises(AccessError):
|
|
self.env['education.lms.submission'].with_user(self.user_student_b).create({
|
|
'assignment_id': assignment.id,
|
|
'student_id': self.student_a.id,
|
|
'text_submission': 'Student B Work',
|
|
})
|
|
|
|
# 39. Course Eligibility Security
|
|
def test_39_course_eligibility(self):
|
|
# Course A uses Program, Year, Term A, Batch A. Student A matches all.
|
|
# Course B uses no Term -> Student A matches.
|
|
course_b = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
|
|
'title': 'Course No Term',
|
|
'faculty_id': self.user_faculty_a.id,
|
|
'program_id': self.program.id,
|
|
'academic_year_id': self.academic_year.id,
|
|
'state': 'published'
|
|
})
|
|
# Student A can read course_b
|
|
res = course_b.with_user(self.user_student_a).read(['title'])
|
|
self.assertTrue(len(res) > 0)
|
|
|
|
# Course C uses Term B -> Student A does not match.
|
|
term_b = self.env['education.academic.term'].create({
|
|
'name': 'Term B',
|
|
'code': 'TB',
|
|
'academic_year_id': self.academic_year.id,
|
|
})
|
|
course_c = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
|
|
'title': 'Course Diff Term',
|
|
'faculty_id': self.user_faculty_a.id,
|
|
'program_id': self.program.id,
|
|
'academic_year_id': self.academic_year.id,
|
|
'term_id': term_b.id,
|
|
'state': 'published'
|
|
})
|
|
# Wait, the rule should block Student A
|
|
with self.assertRaises(AccessError):
|
|
course_c.with_user(self.user_student_a).read(['title'])
|
|
|
|
# 40. Assignment access and states
|
|
def test_40_assignment_access(self):
|
|
# Draft assignment -> Student A cannot read
|
|
self.course_faculty_a.state = 'published'
|
|
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
|
|
'title': 'Assign Draft',
|
|
'course_id': self.course_faculty_a.id,
|
|
'due_date': fields.Date.today() + timedelta(days=5),
|
|
'state': 'draft'
|
|
})
|
|
with self.assertRaises(AccessError):
|
|
assignment.with_user(self.user_student_a).read(['title'])
|
|
|
|
# Published -> Student A can read
|
|
assignment.with_user(self.user_faculty_a).action_publish()
|
|
res = assignment.with_user(self.user_student_a).read(['title'])
|
|
self.assertTrue(len(res) > 0)
|
|
|
|
# Closed -> Student A can read, but cannot submit
|
|
assignment.with_user(self.user_faculty_a).action_close()
|
|
res = assignment.with_user(self.user_student_a).read(['title'])
|
|
self.assertTrue(len(res) > 0)
|
|
|
|
with self.assertRaises(ValidationError):
|
|
self.env['education.lms.submission'].with_user(self.user_student_a).create({
|
|
'assignment_id': assignment.id,
|
|
'student_id': self.student_a.id,
|
|
'text_submission': 'Student A Work',
|
|
})
|
|
|
|
# 41. Submission edge cases
|
|
def test_41_submission_security_edge_cases(self):
|
|
self.course_faculty_a.state = 'published'
|
|
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
|
|
'title': 'Assign H2',
|
|
'course_id': self.course_faculty_a.id,
|
|
'due_date': fields.Date.today() + timedelta(days=5),
|
|
'state': 'published'
|
|
})
|
|
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
|
|
'assignment_id': assignment.id,
|
|
'student_id': self.student_a.id,
|
|
'text_submission': 'Student A Work',
|
|
})
|
|
|
|
# change student_id -> AccessError/ValidationError?
|
|
with self.assertRaises(AccessError):
|
|
submission.with_user(self.user_student_a).write({'student_id': self.student_b.id})
|
|
|
|
# change assignment_id
|
|
assignment_2 = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
|
|
'title': 'Assign H3',
|
|
'course_id': self.course_faculty_a.id,
|
|
'due_date': fields.Date.today() + timedelta(days=5),
|
|
'state': 'published'
|
|
})
|
|
with self.assertRaises(AccessError):
|
|
# student a might not have access to change assignment if it violates unique rule or is blocked.
|
|
# actually students can edit assignment_id if in draft, but let's test.
|
|
# In Phase F we blocked changing assignment if submission is not draft. But it is draft here.
|
|
# wait, they can change it if it's draft and they own the new assignment's course.
|
|
# But the prompt says "assignment_id changed after submission -> REJECT".
|
|
pass
|
|
|
|
submission.with_user(self.user_student_a).action_submit()
|
|
|
|
with self.assertRaises(AccessError):
|
|
submission.with_user(self.user_student_a).write({'assignment_id': assignment_2.id})
|
|
|
|
# change marks
|
|
with self.assertRaises(AccessError):
|
|
submission.with_user(self.user_student_a).write({'marks': 100})
|
|
|
|
# change feedback
|
|
with self.assertRaises(AccessError):
|
|
submission.with_user(self.user_student_a).write({'feedback': 'Good job me'})
|
|
|
|
# 42. Attachment Security Direct Test
|
|
import base64
|
|
def test_42_attachment_security(self):
|
|
self.course_faculty_a.state = 'published'
|
|
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
|
|
'title': 'Assign H Attachments',
|
|
'course_id': self.course_faculty_a.id,
|
|
'due_date': fields.Date.today() + timedelta(days=5),
|
|
'state': 'published'
|
|
})
|
|
|
|
# Student A submission
|
|
sub_a = self.env['education.lms.submission'].with_user(self.user_student_a).create({
|
|
'assignment_id': assignment.id,
|
|
'student_id': self.student_a.id,
|
|
'text_submission': 'A',
|
|
})
|
|
attach_a = self.env['ir.attachment'].with_user(self.user_student_a).create({
|
|
'name': 'file_a.txt',
|
|
'datas': base64.b64encode(b'student A file'),
|
|
'res_model': 'education.lms.submission',
|
|
'res_id': sub_a.id,
|
|
})
|
|
|
|
sub_b = self.env['education.lms.submission'].with_user(self.user_student_b).create({
|
|
'assignment_id': assignment.id,
|
|
'student_id': self.student_b.id,
|
|
'text_submission': 'B',
|
|
})
|
|
attach_b = self.env['ir.attachment'].with_user(self.user_student_b).create({
|
|
'name': 'file_b.txt',
|
|
'datas': base64.b64encode(b'student B file'),
|
|
'res_model': 'education.lms.submission',
|
|
'res_id': sub_b.id,
|
|
})
|
|
|
|
# Verify A can read attach A
|
|
self.assertTrue(attach_a.with_user(self.user_student_a).read(['name']))
|
|
|
|
# Verify A cannot read attach B
|
|
with self.assertRaises(AccessError):
|
|
attach_b.with_user(self.user_student_a).read(['name'])
|
|
|
|
# Verify Faculty A can read both
|
|
self.assertTrue(attach_a.with_user(self.user_faculty_a).read(['name']))
|
|
self.assertTrue(attach_b.with_user(self.user_faculty_a).read(['name']))
|
|
|
|
# Verify Faculty B cannot read either
|
|
with self.assertRaises(AccessError):
|
|
attach_a.with_user(self.user_faculty_b).read(['name'])
|
|
|
|
# 43. Admin override
|
|
def test_43_admin_override(self):
|
|
user_admin = self.env['res.users'].create({
|
|
'name': 'Admin User 2',
|
|
'login': 'lms_admin2',
|
|
'groups_id': [(4, self.group_admin.id)]
|
|
})
|
|
# Admin can read faculty B's course
|
|
course_b = self.env['education.lms.course'].with_user(self.user_faculty_b).create({
|
|
'title': 'Course B',
|
|
'faculty_id': self.user_faculty_b.id,
|
|
'program_id': self.program.id,
|
|
'academic_year_id': self.academic_year.id,
|
|
})
|
|
res = course_b.with_user(user_admin).read(['title'])
|
|
self.assertTrue(len(res) > 0)
|
|
|
|
course_b.with_user(user_admin).write({'title': 'Admin changed'})
|
|
self.assertEqual(course_b.title, 'Admin changed')
|
|
|
|
# 44. Portal User Mapping
|
|
def test_44_portal_user_mapping(self):
|
|
# A user without student_id should not see Student A's courses/submissions in LMS Context
|
|
user_no_student = self.env['res.users'].create({
|
|
'name': 'No Student',
|
|
'login': 'no_student',
|
|
'groups_id': [(4, self.group_student.id)]
|
|
})
|
|
self.course_faculty_a.state = 'published'
|
|
with self.assertRaises(AccessError):
|
|
self.course_faculty_a.with_user(user_no_student).read(['title'])
|
|
''')
|