Repository: frappe/frappe

Issue:
Shared documents not visible
If a role does not have Select/Read permissions for a DocType, earlier they could still access the list view for it as long as one or more Documents from that DocType were shared with them. Now the list view is showing an error

`
### App Versions
```
{
	"erpnext": "15.3.0",
	"frappe": "15.2.1",
	"hrms": "15.4.0",
	"india_compliance": "15.0.2"
}
```
### Route
```
List/Sales Order/List
```
### Traceback
```
Syntax error in query:
select distinct 
			from `tabSales Order`
			where `tabSales Order`.name in ('SO-PEB-2324-007') 
Traceback (most recent call last):
  File "apps/frappe/frappe/app.py", line 110, in application
    response = frappe.api.handle(request)
  File "apps/frappe/frappe/api/__init__.py", line 49, in handle
    data = endpoint(**arguments)
  File "apps/frappe/frappe/api/v1.py", line 36, in handle_rpc_call
    return frappe.handler.handle()
  File "apps/frappe/frappe/handler.py", line 49, in handle
    data = execute_cmd(cmd)
  File "apps/frappe/frappe/handler.py", line 85, in execute_cmd
    return frappe.call(method, **frappe.form_dict)
  File "apps/frappe/frappe/__init__.py", line 1683, in call
    return fn(*args, **newargs)
  File "apps/frappe/frappe/utils/typing_validations.py", line 31, in wrapper
    return func(*args, **kwargs)
  File "apps/frappe/frappe/__init__.py", line 848, in wrapper_fn
    retval = fn(*args, **get_newargs(fn, kwargs))
  File "apps/frappe/frappe/desk/reportview.py", line 58, in get_count
    data = execute(**args)[0].get("total_count")
  File "apps/frappe/frappe/desk/reportview.py", line 64, in execute
    return DatabaseQuery(doctype).execute(*args, **kwargs)
  File "apps/frappe/frappe/model/db_query.py", line 200, in execute
    result = self.build_and_run()
  File "apps/frappe/frappe/model/db_query.py", line 240, in build_and_run
    return frappe.db.sql(
  File "apps/frappe/frappe/database/database.py", line 217, in sql
    self._cursor.execute(query, values)
  File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 153, in execute
    result = self._query(query)
  File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 322, in _query
    conn.query(q)
  File "env/lib/python3.10/site-packages/pymysql/connections.py", line 558, in query
    self._affected_rows = self._read_query_result(unbuffered=unbuffered)
  File "env/lib/python3.10/site-packages/pymysql/connections.py", line 822, in _read_query_result
    result.read()
  File "env/lib/python3.10/site-packages/pymysql/connections.py", line 1200, in read
    first_packet = self.connection._read_packet()
  File "env/lib/python3.10/site-packages/pymysql/connections.py", line 772, in _read_packet
    packet.raise_for_error()
  File "env/lib/python3.10/site-packages/pymysql/protocol.py", line 221, in raise_for_error
    err.raise_mysql_exception(self._data)
  File "env/lib/python3.10/site-packages/pymysql/err.py", line 143, in raise_mysql_exception
    raise errorclass(errno, errval)
pymysql.err.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'from `tabSales Order`\n\t\t\twhere `tabSales Order`.name in ('SO-PEB-2324-007')' at line 2")

```
### Request Data
```
{
	"type": "POST",
	"args": {
		"doctype": "Sales Order",
		"filters": "[]",
		"fields": "[]",
		"distinct": false
	},
	"headers": {},
	"error_handlers": {},
	"url": "/api/method/frappe.desk.reportview.get_count",
	"request_id": null
}
```
### Response Data
```
{
	"exception": "pymysql.err.ProgrammingError: (1064, \"You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'from `tabSales Order`\\n\\t\\t\\twhere `tabSales Order`.name in ('SO-PEB-2324-007')' at line 2\")",
	"exc_type": "ProgrammingError"
}
```
`

## Context information (for bug reports)

**Output of `bench version`**
```
(paste here)
```

## Steps to reproduce the issue

1. Give a role no access to a certain DocType
2. Share 1 or more records of that DocType with a particular User
3. Go to the list view of that DocType

### Observed result
The error above

### Expected result
Filtered list view containing only the DocTypes shared with that user


Discussion:
I'm able to access the shared document itself by going directly to the URL but not from the list view. If I disable count from List View settings, then the above error goes away but clicking on the Document from the list view does nothing.

Resolving patch:
diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py
--- a/frappe/model/__init__.py
+++ b/frappe/model/__init__.py
@@ -230,6 +230,9 @@ def get_permitted_fields(
 	if permission_type is None:
 		permission_type = "select" if frappe.only_has_select_perm(doctype, user=user) else "read"
 
+	meta_fields = meta.default_fields.copy()
+	optional_meta_fields = [x for x in optional_fields if x in valid_columns]
+
 	if permitted_fields := meta.get_permitted_fieldnames(
 		parenttype=parenttype,
 		user=user,
@@ -239,15 +242,12 @@ def get_permitted_fields(
 		if permission_type == "select":
 			return permitted_fields
 
-		meta_fields = meta.default_fields.copy()
-		optional_meta_fields = [x for x in optional_fields if x in valid_columns]
-
 		if meta.istable:
 			meta_fields.extend(child_table_fields)
 
 		return meta_fields + permitted_fields + optional_meta_fields
 
-	return []
+	return meta_fields + optional_meta_fields
 
 
 def is_default_field(fieldname: str) -> bool:
diff --git a/frappe/model/meta.py b/frappe/model/meta.py
--- a/frappe/model/meta.py
+++ b/frappe/model/meta.py
@@ -596,6 +596,10 @@ def get_permitted_fieldnames(
 			self.get_permlevel_access(permission_type=permission_type, parenttype=parenttype, user=user)
 		)
 
+		if 0 not in permlevel_access and permission_type in ("read", "select"):
+			if frappe.share.get_shared(self.name, user, rights=[permission_type], limit=1):
+				permlevel_access.add(0)
+
 		permitted_fieldnames.extend(
 			df.fieldname
 			for df in self.get_fieldnames_with_value(


Test patch:
diff --git a/frappe/core/doctype/docshare/test_docshare.py b/frappe/core/doctype/docshare/test_docshare.py
--- a/frappe/core/doctype/docshare/test_docshare.py
+++ b/frappe/core/doctype/docshare/test_docshare.py
@@ -56,6 +56,24 @@ def test_doc_permission(self):
 		with self.assertRowsRead(1):
 			self.assertTrue(self.event.has_permission())
 
+	def test_list_permission(self):
+		frappe.set_user(self.user)
+		with self.assertRaises(frappe.PermissionError):
+			frappe.get_list("Web Page")
+
+		frappe.set_user("Administrator")
+		doc = frappe.new_doc("Web Page")
+		doc.update({"title": "test document for docshare permissions"})
+		doc.insert()
+		frappe.share.add("Web Page", doc.name, self.user)
+
+		frappe.set_user(self.user)
+		self.assertEqual(len(frappe.get_list("Web Page")), 1)
+
+		doc.delete(ignore_permissions=True)
+		with self.assertRaises(frappe.PermissionError):
+			frappe.get_list("Web Page")
+
 	def test_share_permission(self):
 		frappe.share.add("Event", self.event.name, self.user, write=1, share=1)
 
diff --git a/frappe/tests/test_model_utils.py b/frappe/tests/test_model_utils.py
--- a/frappe/tests/test_model_utils.py
+++ b/frappe/tests/test_model_utils.py
@@ -36,10 +36,10 @@ def test_get_permitted_fields(self):
 		todo_all_columns = frappe.get_meta("ToDo").get_valid_columns()
 		self.assertListEqual(todo_all_fields, todo_all_columns)
 
-		# Guest should have access to no fields in ToDo
+		# Guest should have access to no non-std fields in ToDo
 		with set_user("Guest"):
 			guest_permitted_fields = get_permitted_fields("ToDo")
-			self.assertEqual(guest_permitted_fields, [])
+			self.assertNotIn("description", guest_permitted_fields)
 
 		# everyone should have access to all fields of core doctypes
 		with set_user("Guest"):
@@ -55,15 +55,14 @@ def test_get_permitted_fields(self):
 				"Installed Application", parenttype="Installed Applications"
 			)
 			child_all_fields = frappe.get_meta("Installed Application").get_valid_columns()
-			self.assertEqual(without_parent_fields, [])
 			self.assertLess(len(without_parent_fields), len(with_parent_fields))
 			self.assertSequenceEqual(set(with_parent_fields), set(child_all_fields))
 
-		# guest has access to no fields
+		# guest has access to no non-std fields
 		with set_user("Guest"):
-			self.assertEqual(get_permitted_fields("Installed Application"), [])
-			self.assertEqual(
-				get_permitted_fields("Installed Application", parenttype="Installed Applications"), []
+			self.assertNotIn("app_name", get_permitted_fields("Installed Application"))
+			self.assertNotIn(
+				"app_name", get_permitted_fields("Installed Application", parenttype="Installed Applications")
 			)
 
 	def test_is_default_field(self):