diff --git a/src/backend/src/filesystem/ll_operations/ll_write.js b/src/backend/src/filesystem/ll_operations/ll_write.js index 163fea403..f7d63d255 100644 --- a/src/backend/src/filesystem/ll_operations/ll_write.js +++ b/src/backend/src/filesystem/ll_operations/ll_write.js @@ -32,12 +32,27 @@ const crypto = require('crypto'); const STUCK_STATUS_TIMEOUT = 10 * 1000; const STUCK_ALARM_TIMEOUT = 20 * 1000; +/** + * Base class for low-level write operations providing common storage upload functionality. + * @extends LLFilesystemOperation + */ class LLWriteBase extends LLFilesystemOperation { static MODULES = { config: require('../../config.js'), simple_retry: require('../../util/retryutil.js').simple_retry, } + /** + * Uploads a file to storage with progress tracking and error handling. + * @param {Object} params - Upload parameters + * @param {string} params.uuid - Unique identifier for the file + * @param {string} [params.bucket] - Storage bucket name + * @param {string} [params.bucket_region] - Storage bucket region + * @param {Object} params.file - File object containing stream or buffer + * @param {Object} params.tmp - Temporary file information + * @returns {Promise} The upload state object + * @throws {APIError} When upload fails + */ async _storage_upload ({ uuid, bucket, bucket_region, file, @@ -154,6 +169,11 @@ class LLWriteBase extends LLFilesystemOperation { * @extends LLWriteBase */ class LLOWrite extends LLWriteBase { + /** + * Executes the overwrite operation by writing to an existing file node. + * @returns {Promise} Result of the write operation + * @throws {APIError} When the target node does not exist + */ async _run () { const node = this.values.node; @@ -193,6 +213,11 @@ class LLCWrite extends LLWriteBase { config: require('../../config.js'), } + /** + * Executes the create operation by writing a new file to the parent directory. + * @returns {Promise} Result of the write operation + * @throws {APIError} When the parent directory does not exist + */ async _run () { const parent = this.values.parent; diff --git a/src/backend/src/filesystem/strategies/storage_a/LocalDiskStorageStrategy.js b/src/backend/src/filesystem/strategies/storage_a/LocalDiskStorageStrategy.js index 857f71e1e..e05bcb6c6 100644 --- a/src/backend/src/filesystem/strategies/storage_a/LocalDiskStorageStrategy.js +++ b/src/backend/src/filesystem/strategies/storage_a/LocalDiskStorageStrategy.js @@ -18,13 +18,26 @@ */ const { BaseOperation } = require("../../../services/OperationTraceService"); +/** + * Handles file upload operations to local disk storage. + * Extends BaseOperation to provide upload functionality with progress tracking. + */ class LocalDiskUploadStrategy extends BaseOperation { + /** + * Creates a new LocalDiskUploadStrategy instance. + * @param {Object} parent - The parent storage strategy instance + */ constructor (parent) { super(); this.parent = parent; this.uid = null; } + /** + * Executes the upload operation by storing file data to local disk. + * Handles both buffer and stream-based uploads with progress tracking. + * @returns {Promise} Resolves when the upload is complete + */ async _run () { const { uid, file, storage_api } = this.values; @@ -50,15 +63,31 @@ class LocalDiskUploadStrategy extends BaseOperation { } } + /** + * Hook called after the operation is inserted into the trace. + */ post_insert () {} } +/** + * Handles file copy operations within local disk storage. + * Extends BaseOperation to provide copy functionality with progress tracking. + */ class LocalDiskCopyStrategy extends BaseOperation { + /** + * Creates a new LocalDiskCopyStrategy instance. + * @param {Object} parent - The parent storage strategy instance + */ constructor (parent) { super(); this.parent = parent; } + /** + * Executes the copy operation by duplicating a file from source to destination. + * Updates progress tracker to indicate completion. + * @returns {Promise} Resolves when the copy is complete + */ async _run () { const { src_node, dst_storage, storage_api } = this.values; const { progress_tracker } = storage_api; @@ -73,15 +102,30 @@ class LocalDiskCopyStrategy extends BaseOperation { progress_tracker.set(1); } + /** + * Hook called after the operation is inserted into the trace. + */ post_insert () {} } +/** + * Handles file deletion operations from local disk storage. + * Extends BaseOperation to provide delete functionality. + */ class LocalDiskDeleteStrategy extends BaseOperation { + /** + * Creates a new LocalDiskDeleteStrategy instance. + * @param {Object} parent - The parent storage strategy instance + */ constructor (parent) { super(); this.parent = parent; } + /** + * Executes the delete operation by removing a file from local disk storage. + * @returns {Promise} Resolves when the deletion is complete + */ async _run () { const { node } = this.values; @@ -91,20 +135,50 @@ class LocalDiskDeleteStrategy extends BaseOperation { } } +/** + * Main strategy class for managing local disk storage operations. + * Provides factory methods for creating upload, copy, and delete operations. + */ class LocalDiskStorageStrategy { + /** + * Creates a new LocalDiskStorageStrategy instance. + * @param {Object} config - Configuration object + * @param {Object} config.services - Services container for dependency injection + */ constructor ({ services }) { this.svc_localDiskStorage = services.get('local-disk-storage'); } + + /** + * Creates a new upload operation instance. + * @returns {LocalDiskUploadStrategy} A new upload strategy instance + */ create_upload () { return new LocalDiskUploadStrategy(this); } + + /** + * Creates a new copy operation instance. + * @returns {LocalDiskCopyStrategy} A new copy strategy instance + */ create_copy () { return new LocalDiskCopyStrategy(this); } + + /** + * Creates a new delete operation instance. + * @returns {LocalDiskDeleteStrategy} A new delete strategy instance + */ create_delete () { return new LocalDiskDeleteStrategy(this); } + /** + * Creates a readable stream for accessing file data from local disk storage. + * @param {string} uid - The unique identifier of the file to read + * @param {Object} [options={}] - Optional parameters for stream creation + * @returns {Promise} A readable stream for the file data + */ async create_read_stream (uid, options = {}) { return await this.svc_localDiskStorage.create_read_stream(uid, options); } diff --git a/src/backend/src/modules/dns/DNSService.js b/src/backend/src/modules/dns/DNSService.js index 5a1116d79..33ae586bf 100644 --- a/src/backend/src/modules/dns/DNSService.js +++ b/src/backend/src/modules/dns/DNSService.js @@ -1,7 +1,15 @@ const BaseService = require("../../services/BaseService"); const { sleep } = require("../../util/asyncutil"); +/** + * DNS service that provides DNS client functionality and optional test server + * @extends BaseService + */ class DNSService extends BaseService { + /** + * Initializes the DNS service by creating a DNS client and optionally starting a test server + * @returns {Promise} + */ async _init () { const dns2 = require('dns2'); // this.dns = new dns2(this.config.client); @@ -15,10 +23,18 @@ class DNSService extends BaseService { } } + /** + * Returns the DNS client instance + * @returns {Object} The DNS client + */ get_client () { return this.dns; } + /** + * Creates and starts a test DNS server that responds to A and TXT record queries + * The server listens on port 5300 and returns mock responses for testing purposes + */ test_server_ () { const dns2 = require('dns2'); const { Packet } = dns2 diff --git a/src/backend/src/modules/puterfs/DatabaseFSEntryFetcher.js b/src/backend/src/modules/puterfs/DatabaseFSEntryFetcher.js index df881265f..cc9e4ced2 100644 --- a/src/backend/src/modules/puterfs/DatabaseFSEntryFetcher.js +++ b/src/backend/src/modules/puterfs/DatabaseFSEntryFetcher.js @@ -20,8 +20,16 @@ const { DB_READ } = require("../../services/database/consts"); const { NodePathSelector, NodeUIDSelector, NodeInternalIDSelector, NodeChildSelector, RootNodeSelector } = require("../../filesystem/node/selectors"); const BaseService = require("../../services/BaseService"); +/** + * Service for fetching filesystem entries from the database using various selector types. + * Handles different methods of locating files and directories in the filesystem. + */ module.exports = class DatabaseFSEntryFetcher extends BaseService { static CONCERN = 'filesystem'; + + /** + * Initializes the default properties that will be selected from the database. + */ _construct () { this.defaultProperties = [ 'id', @@ -53,10 +61,19 @@ module.exports = class DatabaseFSEntryFetcher extends BaseService { ] } + /** + * Initializes the database connection for filesystem operations. + */ _init () { this.db = this.services.get('database').get(DB_READ, 'filesystem'); } + /** + * Finds a filesystem entry using the provided selector. + * @param {Object} selector - The selector object specifying how to find the entry + * @param {Object} fetch_entry_options - Options for fetching the entry + * @returns {Promise} The filesystem entry or null if not found + */ async find (selector, fetch_entry_options) { if ( selector instanceof RootNodeSelector ) { return selector.entry; @@ -98,6 +115,12 @@ module.exports = class DatabaseFSEntryFetcher extends BaseService { } } + /** + * Finds a filesystem entry by its UUID. + * @param {string} uuid - The UUID of the entry to find + * @param {Object} fetch_entry_options - Options including thumbnail flag + * @returns {Promise} The filesystem entry or undefined if not found + */ async findByUID(uuid, fetch_entry_options = {}) { const { thumbnail } = fetch_entry_options; @@ -112,6 +135,12 @@ module.exports = class DatabaseFSEntryFetcher extends BaseService { return fsentry[0]; } + /** + * Finds a filesystem entry by its internal database ID. + * @param {number} id - The internal ID of the entry to find + * @param {Object} fetch_entry_options - Options including thumbnail flag + * @returns {Promise} The filesystem entry or undefined if not found + */ async findByID(id, fetch_entry_options = {}) { const { thumbnail } = fetch_entry_options; @@ -126,6 +155,12 @@ module.exports = class DatabaseFSEntryFetcher extends BaseService { return fsentry[0]; } + /** + * Finds a filesystem entry by its full path. + * @param {string} path - The full path of the entry to find + * @param {Object} fetch_entry_options - Options including thumbnail flag and tracer + * @returns {Promise} The filesystem entry or false if not found + */ async findByPath(path, fetch_entry_options = {}) { const { thumbnail } = fetch_entry_options; @@ -199,6 +234,11 @@ module.exports = class DatabaseFSEntryFetcher extends BaseService { return result[0]; } + /** + * Finds the ID of a child entry with the given name in the root directory. + * @param {string} name - The name of the child entry to find + * @returns {Promise} The ID of the child entry or undefined if not found + */ async findNameInRoot (name) { let child_id = await this.db.read( "SELECT `id` FROM `fsentries` WHERE `parent_uid` IS NULL AND name = ? LIMIT 1", @@ -207,6 +247,12 @@ module.exports = class DatabaseFSEntryFetcher extends BaseService { return child_id[0]?.id; } + /** + * Finds the ID of a child entry with the given name under a specific parent. + * @param {string} parent_uid - The UUID of the parent directory + * @param {string} name - The name of the child entry to find + * @returns {Promise} The ID of the child entry or undefined if not found + */ async findNameInParent (parent_uid, name) { let child_id = await this.db.read( "SELECT `id` FROM `fsentries` WHERE `parent_uid` = ? AND name = ? LIMIT 1", @@ -215,6 +261,12 @@ module.exports = class DatabaseFSEntryFetcher extends BaseService { return child_id[0]?.id; } + /** + * Checks if an entry with the given name exists under a specific parent. + * @param {string} parent_uid - The UUID of the parent directory + * @param {string} name - The name to check for + * @returns {Promise} True if the name exists under the parent, false otherwise + */ async nameExistsUnderParent (parent_uid, name) { let check_dupe = await this.db.read( "SELECT `id` FROM `fsentries` WHERE `parent_uid` = ? AND name = ? LIMIT 1", @@ -223,6 +275,12 @@ module.exports = class DatabaseFSEntryFetcher extends BaseService { return !! check_dupe[0]; } + /** + * Checks if an entry with the given name exists under a parent specified by ID. + * @param {number} parent_id - The internal ID of the parent directory + * @param {string} name - The name to check for + * @returns {Promise} True if the name exists under the parent, false otherwise + */ async nameExistsUnderParentID (parent_id, name) { const parent = await this.findByID(parent_id); if ( ! parent ) { diff --git a/src/backend/src/om/entitystorage/WriteByOwnerOnlyES.js b/src/backend/src/om/entitystorage/WriteByOwnerOnlyES.js index 5ae0269f9..200b7436a 100644 --- a/src/backend/src/om/entitystorage/WriteByOwnerOnlyES.js +++ b/src/backend/src/om/entitystorage/WriteByOwnerOnlyES.js @@ -22,8 +22,21 @@ const { BaseES } = require("./BaseES"); const WRITE_ALL_OWNER_ES = 'system:es:write-all-owners'; +/** + * Entity storage layer that restricts write operations to entity owners only. + * Extends BaseES to add ownership-based access control for upsert and delete operations. + */ class WriteByOwnerOnlyES extends BaseES { + /** + * Static methods object containing the access-controlled entity storage operations. + */ static METHODS = { + /** + * Updates or inserts an entity after verifying ownership permissions. + * @param {Object} entity - The entity to upsert + * @param {Object} extra - Additional parameters including old_entity + * @returns {Promise} Result of the upstream upsert operation + */ async upsert (entity, extra) { const { old_entity } = extra; @@ -34,6 +47,12 @@ class WriteByOwnerOnlyES extends BaseES { return await this.upstream.upsert(entity, extra); }, + /** + * Deletes an entity after verifying the current user owns it. + * @param {string} uid - The unique identifier of the entity to delete + * @param {Object} extra - Additional parameters including old_entity + * @returns {Promise} Result of the upstream delete operation + */ async delete (uid, extra) { const { old_entity } = extra; @@ -42,6 +61,13 @@ class WriteByOwnerOnlyES extends BaseES { return await this.upstream.delete(uid, extra); }, + /** + * Verifies that the current user has permission to modify the entity. + * Allows access if user has system-wide write permission or owns the entity. + * @param {Object} params - Parameters object + * @param {Object} params.old_entity - The existing entity to check ownership for + * @throws {APIError} Throws forbidden error if user lacks permission + */ async _check_allowed ({ old_entity }) { const svc_permission = this.context.get('services').get('permission'); const has_permission_to_write_all = await svc_permission.check(Context.get("actor"), WRITE_ALL_OWNER_ES);