From 0ecc402c8ef56a85a88b08883f0e2330bda79949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20Sander?= Date: Wed, 8 Nov 2023 16:28:32 +0000 Subject: [PATCH] fix(disk-util): All subdirs under approved dirs are also approved Fixes #831 --- src/config/production_template.yaml | 6 +- src/lib/disk_utils.js | 8 +- src/test/routes/disk_utils.test.js | 152 ++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 2 deletions(-) diff --git a/src/config/production_template.yaml b/src/config/production_template.yaml index 1a30166b..b271809f 100644 --- a/src/config/production_template.yaml +++ b/src/config/production_template.yaml @@ -576,6 +576,8 @@ Butler: backgroundServerPort: 8081 # Port used internally by Butler's REST API. Any free port on the server where Butler is running can bse used. # List of directories between which file copying via the REST API can be done. + # NOTE: All subdirectories of the specified from and to directories are also approved/allowed. + # # Butler will try to clean up messy paths like this one, which resolves to /Users/goran/butler-test-dir1 # How? First you have /Users/goran/butler-test-dir1//abc which cleans up to /Users/goran/butler-test-dir1/abc, # then up one level (..). @@ -588,7 +590,8 @@ Butler: toDirectory: /to/some/directory2 # List of directories between which file moves via the REST API can be done. - fileMoveApprovedDirectories: + # NOTE: All subdirectories of the specified from and to directories are also approved/allowed. + fileMoveApprovedDirectories: - fromDirectory: /Users/goran/butler-test-dir1//abc//.. toDirectory: /Users/goran/butler-test-dir2 - fromDirectory: /Users/goran/butler-test-dir2 @@ -597,6 +600,7 @@ Butler: toDirectory: /to/some/directory2 # List of directories in which file deletes via the REST API can be done. + # NOTE: All subdirectories of the specified directories are also approved/allowed. fileDeleteApprovedDirectories: - /Users/goran/butler-test-dir1 - /Users/goran/butler-test-dir1//abc//.. diff --git a/src/lib/disk_utils.js b/src/lib/disk_utils.js index 42c6e6a8..7a961a43 100644 --- a/src/lib/disk_utils.js +++ b/src/lib/disk_utils.js @@ -1,9 +1,15 @@ +// Function to check if a given directory (child) is a subdirectory of +// another directory (parent). +// +// This function assumes that both child and parent are normalized paths +// (i.e., they don't contain . or .. segments), and that they use +// forward slashes (/) as path separators. const isDirectoryChildOf = (child, parent) => { if (child === parent) return true; const parentTokens = parent.split('/').filter((i) => i.length); - return parentTokens.every((t, i) => child.split('/')[i] === t); + return parentTokens.every((t, i) => child.split('/').filter((i) => i.length)[i] === t); }; module.exports = { diff --git a/src/test/routes/disk_utils.test.js b/src/test/routes/disk_utils.test.js index a033ac1c..6f242ecd 100644 --- a/src/test/routes/disk_utils.test.js +++ b/src/test/routes/disk_utils.test.js @@ -8,6 +8,8 @@ process.env.NODE_CONFIG_DIR = upath.resolve('./src/config/'); process.env.NODE_ENV = 'production'; const config = require('config'); +const { isDirectoryChildOf } = require('../../lib/disk_utils'); + const instance = axios.create({ baseURL: `http://localhost:${config.get('Butler.restServerConfig.serverPort')}`, timeout: 15000, @@ -358,3 +360,153 @@ describe('E7: POST /v4/createdir', () => { expect(result.data.directory).toEqual(p); }); }); + +/** + * E8 + * Ensure that isDirectoryChildOf works as expected + */ +const dirParent1 = './testDir1'; +const dirParent2 = './testDir1/testDir2'; +const dirParent3 = './testDir1/testDir2/'; + +const dirChild1 = './testDir1'; +const dirChild2 = './testDir1/testDir2'; +const dirChild3 = './testDir1/testDir2/'; +const dirChild4 = './testDir1/testDir2/testDir3'; +const dirChild5 = './testDir1/testDir2/testDir3/testDir4'; +const dirChild6 = './testDir1/testDir2/testDir3/testDir4/'; + +describe('E8: isDirectoryChildOf', () => { + test(`"${dirChild1}" is a child of "${dirParent1}"`, async () => { + try { + result = isDirectoryChildOf(dirChild1, dirParent1); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(true); + }, 5000); + + test(`"${dirChild2}" is a child of "${dirParent2}"`, async () => { + try { + result = isDirectoryChildOf(dirChild2, dirParent2); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(true); + }, 5000); + + test(`"${dirChild3}" is a child of "${dirParent2}"`, async () => { + try { + result = isDirectoryChildOf(dirChild3, dirParent2); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(true); + }, 5000); + + test(`"${dirChild3}" is a child of "${dirParent3}"`, async () => { + try { + result = isDirectoryChildOf(dirChild3, dirParent3); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(true); + }, 5000); + + test(`"${dirChild2}" is a child of "${dirParent3}"`, async () => { + try { + result = isDirectoryChildOf(dirChild2, dirParent3); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(true); + }, 5000); + + test(`"${dirChild4}" is a child of "${dirParent2}"`, async () => { + try { + result = isDirectoryChildOf(dirChild4, dirParent2); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(true); + }, 5000); + + test(`"${dirChild5}" is a child of "${dirParent2}"`, async () => { + try { + result = isDirectoryChildOf(dirChild5, dirParent2); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(true); + }, 5000); + + test(`"${dirChild6}" is a child of "${dirParent2}"`, async () => { + try { + result = isDirectoryChildOf(dirChild6, dirParent2); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(true); + }, 5000); + + // Test failure cases where child directory is not a child of parent + test(`"${dirChild1}" is not a child of "${dirParent2}"`, async () => { + try { + result = isDirectoryChildOf(dirChild1, dirParent2); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(false); + }, 5000); + + test(`"${dirChild4}" is not a child of "${dirParent1}"`, async () => { + try { + result = isDirectoryChildOf(dirChild4, dirParent1); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(false); + }, 5000); + + test(`"${dirChild5}" is not a child of "${dirParent1}"`, async () => { + try { + result = isDirectoryChildOf(dirChild5, dirParent1); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(false); + }, 5000); + + test(`"${dirChild6}" is not a child of "${dirParent1}"`, async () => { + try { + result = isDirectoryChildOf(dirChild6, dirParent1); + } catch (err) { + result = err.response; + console.log('Error testing isDirectoryChildOf'); + } + + expect(result).toBe(false); + }, 5000); +});