From 4db755aa621a6001b7e647ba109f44ba7600aa0f Mon Sep 17 00:00:00 2001 From: SharanRP Date: Sun, 28 Sep 2025 01:07:23 +0530 Subject: [PATCH 1/4] feat: feat: enhance route guards to validate entity types before navigation --- frontend/src/router.js | 86 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/frontend/src/router.js b/frontend/src/router.js index ef539dda7..42e4cb10f 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -132,7 +132,19 @@ const routes = [ path: "/t/:team/", name: "Team", component: () => import("@/pages/Team.vue"), - beforeEnter: [setRootBreadCrumb], + beforeEnter: [ + setRootBreadCrumb, + async (to) => { + const teams = createResource({ + url: "/api/method/drive.api.permissions.get_teams", + method: "GET", + }) + await teams.fetch() + if (!teams.data.includes(to.params.team)) { + return "/" + } + } + ], props: true, }, { @@ -140,7 +152,29 @@ const routes = [ name: "File", component: () => import("@/pages/File.vue"), meta: { allowGuest: true, filePage: true }, - beforeEnter: [manageBreadcrumbs], + beforeEnter: [ + manageBreadcrumbs, + async (to) => { + const entity = createResource({ + url: "/api/method/drive.api.files.get_entity_type", + method: "GET", + params: { + entity_name: to.params.entityName, + }, + }) + await entity.fetch() + if (entity.data.type !== "file") { + const correctRoute = { + folder: `/d/${to.params.entityName}`, + document: `/w/${to.params.entityName}`, + }[entity.data.type] + if (correctRoute) { + return correctRoute + } + return "/" + } + } + ], props: true, }, { @@ -148,7 +182,29 @@ const routes = [ name: "Folder", component: () => import("@/pages/Folder.vue"), meta: { allowGuest: true }, - beforeEnter: [manageBreadcrumbs], + beforeEnter: [ + manageBreadcrumbs, + async (to) => { + const entity = createResource({ + url: "/api/method/drive.api.files.get_entity_type", + method: "GET", + params: { + entity_name: to.params.entityName, + }, + }) + await entity.fetch() + if (entity.data.type !== "folder") { + const correctRoute = { + file: `/f/${to.params.entityName}`, + document: `/w/${to.params.entityName}`, + }[entity.data.type] + if (correctRoute) { + return correctRoute + } + return "/" + } + } + ], props: true, }, { @@ -156,8 +212,30 @@ const routes = [ name: "Document", meta: { documentPage: true, allowGuest: true }, component: () => import("@/pages/Document.vue"), + beforeEnter: [ + manageBreadcrumbs, + async (to) => { + const entity = createResource({ + url: "/api/method/drive.api.files.get_entity_type", + method: "GET", + params: { + entity_name: to.params.entityName, + }, + }) + await entity.fetch() + if (entity.data.type !== "document") { + const correctRoute = { + file: `/f/${to.params.entityName}`, + folder: `/d/${to.params.entityName}`, + }[entity.data.type] + if (correctRoute) { + return correctRoute + } + return "/" + } + } + ], props: true, - beforeEnter: [manageBreadcrumbs], }, ] From c361e6edc64d91aaafa459d709d363376841a846 Mon Sep 17 00:00:00 2001 From: SharanRP Date: Tue, 30 Sep 2025 00:27:21 +0530 Subject: [PATCH 2/4] feat: refactor entity type validation into a separate function for route guards --- frontend/src/router.js | 85 +++++++++++++----------------------------- 1 file changed, 25 insertions(+), 60 deletions(-) diff --git a/frontend/src/router.js b/frontend/src/router.js index 42e4cb10f..23baea924 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -17,6 +17,28 @@ async function setRootBreadCrumb(to) { } } +async function validateEntityType(to, expectedType) { + const entity = createResource({ + url: "/api/method/drive.api.files.get_entity_type", + method: "GET", + params: { + entity_name: to.params.entityName, + }, + }) + await entity.fetch() + if (entity.data.type !== expectedType) { + const correctRoute = { + folder: `/d/${to.params.entityName}`, + document: `/w/${to.params.entityName}`, + file: `/f/${to.params.entityName}`, + }[entity.data.type] + if (correctRoute) { + return correctRoute + } + return "/" + } +} + const routes = [ { path: "/signup", @@ -154,26 +176,7 @@ const routes = [ meta: { allowGuest: true, filePage: true }, beforeEnter: [ manageBreadcrumbs, - async (to) => { - const entity = createResource({ - url: "/api/method/drive.api.files.get_entity_type", - method: "GET", - params: { - entity_name: to.params.entityName, - }, - }) - await entity.fetch() - if (entity.data.type !== "file") { - const correctRoute = { - folder: `/d/${to.params.entityName}`, - document: `/w/${to.params.entityName}`, - }[entity.data.type] - if (correctRoute) { - return correctRoute - } - return "/" - } - } + async (to) => validateEntityType(to, "file") ], props: true, }, @@ -184,26 +187,7 @@ const routes = [ meta: { allowGuest: true }, beforeEnter: [ manageBreadcrumbs, - async (to) => { - const entity = createResource({ - url: "/api/method/drive.api.files.get_entity_type", - method: "GET", - params: { - entity_name: to.params.entityName, - }, - }) - await entity.fetch() - if (entity.data.type !== "folder") { - const correctRoute = { - file: `/f/${to.params.entityName}`, - document: `/w/${to.params.entityName}`, - }[entity.data.type] - if (correctRoute) { - return correctRoute - } - return "/" - } - } + async (to) => validateEntityType(to, "folder") ], props: true, }, @@ -214,26 +198,7 @@ const routes = [ component: () => import("@/pages/Document.vue"), beforeEnter: [ manageBreadcrumbs, - async (to) => { - const entity = createResource({ - url: "/api/method/drive.api.files.get_entity_type", - method: "GET", - params: { - entity_name: to.params.entityName, - }, - }) - await entity.fetch() - if (entity.data.type !== "document") { - const correctRoute = { - file: `/f/${to.params.entityName}`, - folder: `/d/${to.params.entityName}`, - }[entity.data.type] - if (correctRoute) { - return correctRoute - } - return "/" - } - } + async (to) => validateEntityType(to, "document") ], props: true, }, From d611471e2d7fc6104d891510a05a0f0782581394 Mon Sep 17 00:00:00 2001 From: SharanRP Date: Tue, 14 Oct 2025 00:29:26 +0530 Subject: [PATCH 3/4] feat: enhance entity type validation and routing for file, folder, and document pages --- drive/api/files.py | 17 ----------- drive/api/permissions.py | 32 ++++++++++++++++++++- frontend/src/pages/Document.vue | 8 ++++++ frontend/src/pages/File.vue | 6 +++- frontend/src/pages/Folder.vue | 6 +++- frontend/src/router.js | 51 +++------------------------------ 6 files changed, 53 insertions(+), 67 deletions(-) diff --git a/drive/api/files.py b/drive/api/files.py index 0fa1b23be..8251d9d78 100644 --- a/drive/api/files.py +++ b/drive/api/files.py @@ -872,23 +872,6 @@ def get_new_title(title, parent_name, folder=False, entity=None): return f"{entity_title} ({len(sibling_entity_titles)}){entity_ext}" -@frappe.whitelist(allow_guest=True) -def get_entity_type(entity_name): - entity = frappe.db.get_value( - "Drive File", - {"is_active": 1, "name": entity_name}, - ["team", "name", "mime_type", "is_group", "document"], - as_dict=1, - ) - if entity.document or entity.mime_type == "text/markdown": - entity["type"] = "document" - elif entity.is_group: - entity["type"] = "folder" - else: - entity["type"] = "file" - return entity - - @frappe.whitelist() def get_root_folder(team): if team not in get_teams(): diff --git a/drive/api/permissions.py b/drive/api/permissions.py index 52384094e..3421120d2 100644 --- a/drive/api/permissions.py +++ b/drive/api/permissions.py @@ -135,9 +135,13 @@ def get_teams(user=None, details=None, exclude_personal=True): @frappe.whitelist(allow_guest=True) -def get_entity_with_permissions(entity_name): +def get_entity_with_permissions(entity_name, expected_type=None): """ Return file data with permissions + + :param entity_name: Name of the entity to fetch + :param expected_type: Expected entity type ('file', 'folder', or 'document'). + If provided and doesn't match, returns redirect info """ entity = frappe.db.get_value( "Drive File", @@ -148,6 +152,32 @@ def get_entity_with_permissions(entity_name): if not entity: frappe.throw("We couldn't find what you're looking for.", {"error": frappe.NotFound}) + # Determine actual entity type + if entity.document or entity.mime_type == "text/markdown": + actual_type = "document" + elif entity.is_group: + actual_type = "folder" + else: + actual_type = "file" + + # Check if type matches expected type and return redirect if mismatch + if expected_type and expected_type != actual_type: + route_map = { + "folder": f"/d/{entity_name}", + "document": f"/w/{entity_name}", + "file": f"/f/{entity_name}", + } + correct_route = route_map.get(actual_type) + if correct_route: + return { + "redirect": True, + "route": correct_route, + "entity_type": actual_type, + "expected_type": expected_type + } + # If no valid route found, throw error + frappe.throw("Invalid entity type.", frappe.ValidationError) + entity["in_home"] = entity.team == get_default_team() user_access = get_user_access(entity) if user_access.get("read") == 0: diff --git a/frontend/src/pages/Document.vue b/frontend/src/pages/Document.vue index 01736e787..aa6fdf00b 100644 --- a/frontend/src/pages/Document.vue +++ b/frontend/src/pages/Document.vue @@ -403,6 +403,14 @@ const document = createResource({ auto: true, params: { entity_name: props.entityName, + expected_type: "document", + }, + transform(entity) { + if (entity.redirect) { + router.push(entity.route) + return null + } + return entity }, onSuccess, }) diff --git a/frontend/src/pages/File.vue b/frontend/src/pages/File.vue index 5305359bc..24e004357 100644 --- a/frontend/src/pages/File.vue +++ b/frontend/src/pages/File.vue @@ -126,8 +126,12 @@ const onSuccess = async (entity) => { let file = createResource({ url: "drive.api.permissions.get_entity_with_permissions", - params: { entity_name: props.entityName }, + params: { entity_name: props.entityName, expected_type: "file" }, transform(entity) { + if (entity.redirect) { + router.push(entity.route) + return null + } store.commit("setActiveEntity", entity) return prettyData([entity])[0] }, diff --git a/frontend/src/pages/Folder.vue b/frontend/src/pages/Folder.vue index e9242367a..42bc9af69 100644 --- a/frontend/src/pages/Folder.vue +++ b/frontend/src/pages/Folder.vue @@ -54,12 +54,16 @@ const e = computed(() => props.entityName) let currentFolder = createResource({ url: "drive.api.permissions.get_entity_with_permissions", transform(entity) { + if (entity.redirect) { + router.push(entity.route) + return null + } return prettyData([entity])[0] }, onSuccess, }) store.commit("setCurrentResource", currentFolder) -watch(e, (v) => currentFolder.fetch({ entity_name: v }), { immediate: true }) +watch(e, (v) => currentFolder.fetch({ entity_name: v, expected_type: "folder" }), { immediate: true }) let userInfo = createResource({ url: "frappe.desk.form.load.get_user_info_for_viewers", diff --git a/frontend/src/router.js b/frontend/src/router.js index 23baea924..596c550e4 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -17,28 +17,6 @@ async function setRootBreadCrumb(to) { } } -async function validateEntityType(to, expectedType) { - const entity = createResource({ - url: "/api/method/drive.api.files.get_entity_type", - method: "GET", - params: { - entity_name: to.params.entityName, - }, - }) - await entity.fetch() - if (entity.data.type !== expectedType) { - const correctRoute = { - folder: `/d/${to.params.entityName}`, - document: `/w/${to.params.entityName}`, - file: `/f/${to.params.entityName}`, - }[entity.data.type] - if (correctRoute) { - return correctRoute - } - return "/" - } -} - const routes = [ { path: "/signup", @@ -154,19 +132,7 @@ const routes = [ path: "/t/:team/", name: "Team", component: () => import("@/pages/Team.vue"), - beforeEnter: [ - setRootBreadCrumb, - async (to) => { - const teams = createResource({ - url: "/api/method/drive.api.permissions.get_teams", - method: "GET", - }) - await teams.fetch() - if (!teams.data.includes(to.params.team)) { - return "/" - } - } - ], + beforeEnter: [setRootBreadCrumb], props: true, }, { @@ -174,10 +140,7 @@ const routes = [ name: "File", component: () => import("@/pages/File.vue"), meta: { allowGuest: true, filePage: true }, - beforeEnter: [ - manageBreadcrumbs, - async (to) => validateEntityType(to, "file") - ], + beforeEnter: [manageBreadcrumbs], props: true, }, { @@ -185,10 +148,7 @@ const routes = [ name: "Folder", component: () => import("@/pages/Folder.vue"), meta: { allowGuest: true }, - beforeEnter: [ - manageBreadcrumbs, - async (to) => validateEntityType(to, "folder") - ], + beforeEnter: [manageBreadcrumbs], props: true, }, { @@ -196,10 +156,7 @@ const routes = [ name: "Document", meta: { documentPage: true, allowGuest: true }, component: () => import("@/pages/Document.vue"), - beforeEnter: [ - manageBreadcrumbs, - async (to) => validateEntityType(to, "document") - ], + beforeEnter: [manageBreadcrumbs], props: true, }, ] From 2084e4e8745f060198dc555748bcd1097659af03 Mon Sep 17 00:00:00 2001 From: SharanRP Date: Fri, 24 Oct 2025 18:21:18 +0530 Subject: [PATCH 4/4] feat: enhance entity permission handling and routing logic across file, folder, and document components --- drive/api/permissions.py | 28 ++++++++++++++++++++-------- frontend/src/pages/Document.vue | 12 +++++------- frontend/src/pages/File.vue | 17 +++++++++-------- frontend/src/pages/Folder.vue | 13 +++++++------ frontend/src/router.js | 19 ++++++++++++------- 5 files changed, 53 insertions(+), 36 deletions(-) diff --git a/drive/api/permissions.py b/drive/api/permissions.py index 93c082126..bab665a78 100644 --- a/drive/api/permissions.py +++ b/drive/api/permissions.py @@ -135,13 +135,12 @@ def get_teams(user=None, details=None, exclude_personal=True): @frappe.whitelist(allow_guest=True) -def get_entity_with_permissions(entity_name, expected_type=None): +def get_entity_with_permissions(entity_name): """ - Return file data with permissions + Return file data with permissions. Validates that the request path matches + the entity type and returns redirect information if there's a mismatch. :param entity_name: Name of the entity to fetch - :param expected_type: Expected entity type ('file', 'folder', or 'document'). - If provided and doesn't match, returns redirect info """ entity = frappe.db.get_value( "Drive File", @@ -152,14 +151,27 @@ def get_entity_with_permissions(entity_name, expected_type=None): if not entity: frappe.throw("We couldn't find what you're looking for.", {"error": frappe.NotFound}) - # Determine actual entity type - if entity.document or entity.mime_type == "text/markdown": - actual_type = "document" - elif entity.is_group: + # Determine actual entity type using get_file_type + file_type = get_file_type(entity) + + # Map file_type to route type (Document, Folder, or anything else is file) + if file_type == "Folder": actual_type = "folder" + elif file_type in ["Document", "Frappe Document"]: + actual_type = "document" else: actual_type = "file" + request_path = frappe.request.path + expected_type = None + + if "/w/" in request_path: + expected_type = "document" + elif "/d/" in request_path: + expected_type = "folder" + elif "/f/" in request_path: + expected_type = "file" + # Check if type matches expected type and return redirect if mismatch if expected_type and expected_type != actual_type: route_map = { diff --git a/frontend/src/pages/Document.vue b/frontend/src/pages/Document.vue index cff465ca9..f6de72054 100644 --- a/frontend/src/pages/Document.vue +++ b/frontend/src/pages/Document.vue @@ -283,16 +283,14 @@ const document = createResource({ auto: true, params: { entity_name: props.entityName, - expected_type: "document", }, - transform(entity) { - if (entity.redirect) { - router.push(entity.route) - return null + onSuccess(data) { + if (data.redirect) { + router.push(data.route) + return } - return entity + onSuccess(data) }, - onSuccess, }) store.commit("setCurrentResource", document) diff --git a/frontend/src/pages/File.vue b/frontend/src/pages/File.vue index fcdf51288..9cfcf6e91 100644 --- a/frontend/src/pages/File.vue +++ b/frontend/src/pages/File.vue @@ -125,16 +125,17 @@ const onSuccess = async (entity) => { const file = createResource({ url: "drive.api.permissions.get_entity_with_permissions", - params: { entity_name: props.entityName, expected_type: "file" }, - transform(entity) { - if (entity.redirect) { - router.push(entity.route) - return null + params: { entity_name: props.entityName }, + onSuccess(data) { + if (data.redirect) { + router.push(data.route) + return } - store.commit("setActiveEntity", entity) - return prettyData([entity])[0] + store.commit("setActiveEntity", data) + const prettyEntity = prettyData([data])[0] + file.setData(prettyEntity) + onSuccess(prettyEntity) }, - onSuccess, }) store.commit("setCurrentResource", file) diff --git a/frontend/src/pages/Folder.vue b/frontend/src/pages/Folder.vue index 4850b0163..43be77cea 100644 --- a/frontend/src/pages/Folder.vue +++ b/frontend/src/pages/Folder.vue @@ -53,14 +53,15 @@ const onSuccess = (entity) => { const e = computed(() => props.entityName) const currentFolder = createResource({ url: "drive.api.permissions.get_entity_with_permissions", - transform(entity) { - if (entity.redirect) { - router.push(entity.route) - return null + onSuccess(data) { + if (data.redirect) { + router.push(data.route) + return } - return prettyData([entity])[0] + const prettyEntity = prettyData([data])[0] + currentFolder.setData(prettyEntity) + onSuccess(prettyEntity) }, - onSuccess, }) store.commit("setCurrentResource", currentFolder) watch(e, (v) => currentFolder.fetch({ entity_name: v }), { immediate: true }) diff --git a/frontend/src/router.js b/frontend/src/router.js index 596c550e4..fe23d33ec 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -111,20 +111,25 @@ const routes = [ component: Dummy, beforeEnter: async (to) => { const entity = createResource({ - url: "/api/method/drive.api.files.get_entity_type", + url: "/api/method/drive.api.permissions.get_entity_with_permissions", method: "GET", params: { entity_name: to.params.entityName, }, }) await entity.fetch() - const letter = { - folder: "d", - document: "w", - file: "f", - }[entity.data.type] + + const file_type = entity.data.file_type + let letter = "f" // default to file + + if (file_type === "Folder") { + letter = "d" + } else if (file_type === "Document" || file_type === "Frappe Document") { + letter = "w" + } + return { - path: `/${letter}/${entity.data.name}`, + path: `/${letter}/${to.params.entityName}`, } }, },