fix: misc permission issues

This commit is contained in:
Jannat Patel
2026-02-21 12:25:47 +05:30
parent dfb7152aa3
commit c596d1e215
12 changed files with 177 additions and 68 deletions

View File

@@ -86,13 +86,16 @@ after_migrate = [
# -----------
# Permissions evaluated in scripted ways
# permission_query_conditions = {
# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
# }
#
# has_permission = {
# "Event": "frappe.desk.doctype.event.event.has_permission",
# }
permission_query_conditions = {
"LMS Certificate": "lms.lms.doctype.lms_certificate.lms_certificate.get_permission_query_conditions",
}
has_permission = {
"LMS Live Class": "lms.lms.doctype.lms_live_class.lms_live_class.has_permission",
"LMS Batch": "lms.lms.doctype.lms_batch.lms_batch.has_permission",
"LMS Program": "lms.lms.doctype.lms_program.lms_program.has_permission",
"LMS Certificate": "lms.lms.doctype.lms_certificate.lms_certificate.has_permission",
}
# DocType Class
# ---------------

View File

@@ -1722,7 +1722,7 @@ def get_profile_details(username: str):
as_dict=True,
)
roles = frappe.get_roles(details.name)
if not has_lms_role(roles):
if not has_lms_role():
frappe.throw(
_("User does not have permission to access this users profile details."), frappe.PermissionError
)
@@ -1730,7 +1730,8 @@ def get_profile_details(username: str):
return details
def has_lms_role(roles: list):
def has_lms_role():
roles = frappe.get_roles()
lms_roles = set(LMS_ROLES)
user_roles = set(roles)
return not lms_roles.isdisjoint(user_roles)
@@ -2223,7 +2224,7 @@ def get_assessment_from_lesson(course: str, assessmentType: str):
@frappe.whitelist()
def get_badges(member: str):
if not has_lms_role(frappe.get_roles()):
if not has_lms_role():
frappe.throw(_("You do not have permission to access badges."), frappe.PermissionError)
badges = frappe.get_all(

View File

@@ -59,7 +59,7 @@
"fieldname": "condition",
"fieldtype": "Code",
"label": "Condition",
"mandatory_depends_on": "eval:doc.event == \"Manual Assignment\""
"reqd": 1
},
{
"depends_on": "eval:doc.event == 'Value Change'",
@@ -100,7 +100,7 @@
"link_fieldname": "badge"
}
],
"modified": "2026-02-19 15:05:49.719925",
"modified": "2026-02-20 17:58:25.924109",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Badge",

View File

@@ -10,17 +10,17 @@ from lms.lms.doctype.lms_badge.lms_badge import eval_condition
class LMSBadgeAssignment(Document):
def validate(self):
self.validate_owner()
self.validate_duplicate_badge_assignment()
self.validate_badge_criteria()
self.validate_owner()
def validate_owner(self):
if self.owner == self.member:
return
roles = frappe.get_roles(self.owner)
if "Moderator" not in roles:
frappe.throw(_("You must be a Moderator to assign badges to users."))
event = frappe.db.get_value("LMS Badge", self.badge, "event")
if event == "Manual Assignment":
roles = frappe.get_roles(frappe.session.user)
admins = ["Moderator", "Course Creator", "Batch Evaluator"]
if not any(role in roles for role in admins):
frappe.throw(_("You must be an Admin to assign badges to users."))
def validate_duplicate_badge_assignment(self):
grant_only_once = frappe.db.get_value("LMS Badge", self.badge, "grant_only_once")
@@ -40,25 +40,27 @@ class LMSBadgeAssignment(Document):
"LMS Badge", self.badge, ["reference_doctype", "user_field", "condition", "enabled"], as_dict=True
)
if badge_details:
if badge_details.reference_doctype and badge_details.user_field and badge_details.condition:
user_fieldname = frappe.db.get_value(
"DocField",
{"parent": badge_details.reference_doctype, "fieldname": badge_details.user_field},
"fieldname",
if not badge_details:
return
if badge_details.reference_doctype and badge_details.user_field and badge_details.condition:
user_fieldname = frappe.db.get_value(
"DocField",
{"parent": badge_details.reference_doctype, "fieldname": badge_details.user_field},
"fieldname",
)
documents = frappe.get_all(
badge_details.reference_doctype,
{user_fieldname: self.member},
)
for document in documents:
reference_value = eval_condition(
frappe.get_doc(badge_details.reference_doctype, document.name),
badge_details.condition,
)
if reference_value:
return
documents = frappe.get_all(
badge_details.reference_doctype,
{user_fieldname: self.member},
)
for document in documents:
reference_value = eval_condition(
frappe.get_doc(badge_details.reference_doctype, document.name),
badge_details.condition,
)
if reference_value:
return
frappe.throw(_("Member does not meet the criteria for the badge {0}.").format(self.badge))
frappe.throw(_("Member does not meet the criteria for the badge {0}.").format(self.badge))

View File

@@ -20,6 +20,7 @@ from lms.lms.utils import (
get_lesson_url,
get_lms_route,
get_quiz_details,
guest_access_allowed,
update_payment_record,
)
@@ -213,6 +214,10 @@ def create_live_class(
auto_recording: str,
description: str = None,
):
roles = frappe.get_roles()
if not any(role in roles for role in ["Moderator", "Batch Evaluator"]):
frappe.throw(_("You do not have permission to create a live class."))
payload = {
"topic": title,
"start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"),
@@ -391,3 +396,23 @@ def send_mail(batch, student):
args=args,
header=[_(f"Batch Start Reminder: {batch.title}"), "orange"],
)
def has_permission(doc, ptype="read", user=None):
user = user or frappe.session.user
if user == "Guest" and not guest_access_allowed():
return False
roles = frappe.get_roles(user)
if "Moderator" in roles or "Batch Evaluator" in roles:
return True
is_enrolled = frappe.db.exists("LMS Batch Enrollment", {"batch": doc.name, "member": user})
if is_enrolled:
return True
is_batch_published = frappe.db.get_value("LMS Batch", doc.name, "published")
if is_batch_published:
return True
return False

View File

@@ -123,7 +123,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-12-17 16:50:31.128747",
"modified": "2026-02-20 17:32:34.580862",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Certificate",
@@ -153,27 +153,6 @@
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1
},
{
"create": 1,
"delete": 1,
@@ -197,6 +176,15 @@
"role": "Course Creator",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1
}
],
"row_format": "Dynamic",

View File

@@ -12,6 +12,7 @@ from frappe.utils.telemetry import capture
class LMSCertificate(Document):
def validate(self):
self.validate_criteria()
self.validate_duplicate_certificate()
def autoname(self):
@@ -54,6 +55,43 @@ class LMSCertificate(Document):
header=[subject, "green"],
)
def validate_criteria(self):
self.validate_role_of_owner()
self.validate_batch_enrollment()
self.validate_course_enrollment()
def validate_role_of_owner(self):
roles = frappe.get_roles()
is_admin = any(role in roles for role in ["Moderator", "Course Creator", "Batch Evaluator"])
if not self.course and not self.batch_name and not is_admin:
frappe.throw(_("Course or Batch is required to issue a certificate."))
def validate_batch_enrollment(self):
if self.batch_name:
is_enrolled = frappe.db.exists(
"LMS Batch Enrollment", {"batch": self.batch_name, "member": self.member}
)
if not is_enrolled:
frappe.throw(_("Certification cannot be issued as the member is not enrolled in this batch."))
def validate_course_enrollment(self):
if self.course:
is_enrolled = frappe.db.exists("LMS Enrollment", {"course": self.course, "member": self.member})
if not is_enrolled:
frappe.throw(
_("Certification cannot be issued as the member is not enrolled in this course.")
)
completion_certificate = frappe.db.get_value("LMS Course", self.course, "enable_certification")
if completion_certificate:
progress = frappe.db.get_value(
"LMS Enrollment", {"course": self.course, "member": self.member}, "progress"
)
if progress < 100:
frappe.throw(
_("Certification cannot be issued as the member has not completed the course.")
)
def validate_duplicate_certificate(self):
self.validate_course_duplicates()
self.validate_batch_duplicates()
@@ -177,3 +215,19 @@ def validate_certification_eligibility(course):
)
if progress < 100:
frappe.throw(_("You have not completed the course yet."))
def has_permission(doc, ptype="read", user=None):
user = user or frappe.session.user
roles = frappe.get_roles(user)
if "Moderator" in roles or "Course Creator" in roles or "Batch Evaluator" in roles:
return True
return doc.published
def get_permission_query_conditions(user):
user = user or frappe.session.user
roles = frappe.get_roles(user)
if "Moderator" in roles or "Course Creator" in roles or "Batch Evaluator" in roles:
return None
return """(`tabLMS Certificate`.published = 1)"""

View File

@@ -41,7 +41,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-01-29 16:10:47.787285",
"modified": "2026-02-20 17:40:39.823017",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Course Review",
@@ -63,8 +63,8 @@
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1,

View File

@@ -169,3 +169,15 @@ def get_minutes(duration_in_seconds):
if duration_in_seconds:
return int(duration_in_seconds) // 60
return 0
def has_permission(doc, ptype="read", user=None):
user = user or frappe.session.user
roles = frappe.get_roles(user)
if "Moderator" in roles or "Batch Evaluator" in roles:
return True
return frappe.db.exists(
"LMS Batch Enrollment",
{"batch": doc.batch_name, "member": user},
)

View File

@@ -5,6 +5,8 @@ import frappe
from frappe import _
from frappe.model.document import Document
from lms.lms.utils import guest_access_allowed
class LMSProgram(Document):
def validate(self):
@@ -41,3 +43,24 @@ class LMSProgram(Document):
if self.member_count != member_count:
self.member_count = member_count
def has_permission(doc, ptype="read", user=None):
user = user or frappe.session.user
if user == "Guest" and not guest_access_allowed():
return False
roles = frappe.get_roles(user)
if "Moderator" in roles or "Course Creator" in roles:
return True
is_enrolled = frappe.db.exists("LMS Program Member", {"parent": doc.name, "member": user})
if is_enrolled:
return True
is_program_published = frappe.db.get_value("LMS Program", doc.name, "published")
if is_program_published:
return True
return False

View File

@@ -88,7 +88,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-06-24 14:42:08.288983",
"modified": "2026-02-20 14:43:56.587110",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Programming Exercise Submission",
@@ -146,6 +146,7 @@
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,