mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-29 14:48:26 +00:00
Co-authored-by: Diptanil Saha <diptanil@frappe.io>
This commit is contained in:
@@ -6,21 +6,12 @@ import frappe
|
||||
|
||||
|
||||
def get_context(context):
|
||||
project_user = frappe.db.get_value(
|
||||
"Project User",
|
||||
{"parent": frappe.form_dict.project, "user": frappe.session.user},
|
||||
["user", "view_attachments", "hide_timesheets"],
|
||||
as_dict=True,
|
||||
)
|
||||
if frappe.session.user != "Administrator" and (not project_user or frappe.session.user == "Guest"):
|
||||
raise frappe.PermissionError
|
||||
project_user = validate_and_get_project_user(project=frappe.form_dict.project)
|
||||
|
||||
context.no_cache = 1
|
||||
context.show_sidebar = True
|
||||
project = frappe.get_doc("Project", frappe.form_dict.project)
|
||||
|
||||
project.has_permission("read")
|
||||
|
||||
project.tasks = get_tasks(
|
||||
project.name, start=0, item_status="open", search=frappe.form_dict.get("search")
|
||||
)
|
||||
@@ -66,6 +57,7 @@ def get_tasks(project, start=0, search=None, item_status=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_task_html(project: str, start: int = 0, item_status: str | None = None):
|
||||
validate_and_get_project_user(project=project)
|
||||
return frappe.render_template(
|
||||
"erpnext/templates/includes/projects/project_tasks.html",
|
||||
{
|
||||
@@ -106,6 +98,7 @@ def get_timesheets(project, start=0, search=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_timesheet_html(project: str, start: int = 0):
|
||||
validate_and_get_project_user(project=project)
|
||||
return frappe.render_template(
|
||||
"erpnext/templates/includes/projects/project_timesheets.html",
|
||||
{"doc": {"timesheets": get_timesheets(project, start)}},
|
||||
@@ -119,3 +112,12 @@ def get_attachments(project):
|
||||
filters={"attached_to_name": project, "attached_to_doctype": "Project", "is_private": 0},
|
||||
fields=["file_name", "file_url", "file_size"],
|
||||
)
|
||||
|
||||
|
||||
def validate_and_get_project_user(project: str):
|
||||
project_doc = frappe.get_doc("Project", project)
|
||||
project_doc.check_permission()
|
||||
|
||||
project_user = next((d for d in project_doc.users if d.user == frappe.session.user), None)
|
||||
|
||||
return project_user
|
||||
|
||||
72
erpnext/templates/pages/test_projects.py
Normal file
72
erpnext/templates/pages/test_projects.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.projects.doctype.project.test_project import make_project
|
||||
from erpnext.templates.pages.projects import validate_and_get_project_user
|
||||
|
||||
|
||||
class TestProjectsPage(FrappeTestCase):
|
||||
"""validate_and_get_project_user() gates the /projects portal page. It must raise
|
||||
frappe.PermissionError for a user who can't read the Project, and otherwise return
|
||||
that user's Project User row (or None if they're permitted but not listed as one --
|
||||
e.g. an internal Projects Manager browsing the portal)."""
|
||||
|
||||
def _create_user(self, email):
|
||||
if not frappe.db.exists("User", email):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "User",
|
||||
"email": email,
|
||||
"first_name": "Portal",
|
||||
"send_welcome_email": 0,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
return email
|
||||
|
||||
def test_raises_permission_error_for_user_without_access(self):
|
||||
project = make_project({"project_name": f"_Test Portal Access {frappe.generate_hash(length=6)}"})
|
||||
outsider = self._create_user(f"outsider_{frappe.generate_hash(length=6)}@example.com")
|
||||
|
||||
with self.set_user(outsider):
|
||||
self.assertRaises(frappe.PermissionError, validate_and_get_project_user, project.name)
|
||||
|
||||
def test_allows_user_listed_as_project_user_and_returns_their_row(self):
|
||||
# Being a Project User shares the Project with that user (see
|
||||
# Project.control_access_for_project_users), which is what lets them past
|
||||
# check_permission() here.
|
||||
member = self._create_user(f"member_{frappe.generate_hash(length=6)}@example.com")
|
||||
|
||||
project = frappe.get_doc(
|
||||
doctype="Project",
|
||||
project_name=f"_Test Portal Access {frappe.generate_hash(length=6)}",
|
||||
status="Open",
|
||||
company="_Test Company",
|
||||
)
|
||||
project.append(
|
||||
"users", {"user": member, "view_attachments": 1, "hide_timesheets": 1, "welcome_email_sent": 1}
|
||||
)
|
||||
project.insert()
|
||||
|
||||
with self.set_user(member):
|
||||
project_user = validate_and_get_project_user(project.name)
|
||||
|
||||
self.assertIsNotNone(project_user)
|
||||
self.assertEqual(project_user.user, member)
|
||||
self.assertEqual(project_user.view_attachments, 1)
|
||||
self.assertEqual(project_user.hide_timesheets, 1)
|
||||
|
||||
def test_allows_internally_permitted_user_not_listed_as_project_user(self):
|
||||
# The permission gate must be the real permission system (check_permission()),
|
||||
# not "is this user in the Project's users child table" -- a Projects Manager
|
||||
# can open any project's portal page without ever being added as its user.
|
||||
project = make_project({"project_name": f"_Test Portal Access {frappe.generate_hash(length=6)}"})
|
||||
manager = self._create_user(f"manager_{frappe.generate_hash(length=6)}@example.com")
|
||||
frappe.get_doc("User", manager).add_roles("Projects Manager")
|
||||
|
||||
with self.set_user(manager):
|
||||
project_user = validate_and_get_project_user(project.name)
|
||||
|
||||
self.assertIsNone(project_user)
|
||||
Reference in New Issue
Block a user