diff --git a/pydatalab/pydatalab/file_utils.py b/pydatalab/pydatalab/file_utils.py index 7d5b0dd27..fce2cf4e1 100644 --- a/pydatalab/pydatalab/file_utils.py +++ b/pydatalab/pydatalab/file_utils.py @@ -13,7 +13,9 @@ from pydatalab.config import CONFIG, RemoteFilesystem from pydatalab.logger import LOGGER, logged_route from pydatalab.models import File +from pydatalab.models.utils import PyObjectId from pydatalab.mongo import flask_mongo +from pydatalab.routes.utils import get_default_permissions FILE_DIRECTORY = CONFIG.FILE_DIRECTORY DIRECTORIES_DICT = {fs.name: fs for fs in CONFIG.REMOTE_FILESYSTEMS} @@ -184,7 +186,7 @@ def _check_and_sync_file(file_info: File, file_id: ObjectId) -> File: is_live = False updated_file_info = file_collection.find_one_and_update( - {"_id": file_id}, + {"_id": file_id, **get_default_permissions(user_only=False)}, { "$set": { "size": local_stat_results.st_size, @@ -236,7 +238,9 @@ def get_file_info_by_id( LOGGER.debug("getting file for file_id: %s", file_id) file_collection = flask_mongo.db.files file_id = ObjectId(file_id) - file_info = file_collection.find_one({"_id": file_id}) + file_info = file_collection.find_one( + {"_id": file_id, **get_default_permissions(user_only=False)} + ) if not file_info: raise IOError(f"could not find file with id: {file_id} in db") @@ -286,7 +290,14 @@ def update_uploaded_file(file, file_id, last_modified=None, size_bytes=None): @logged_route -def save_uploaded_file(file, item_ids=None, block_ids=None, last_modified=None, size_bytes=None): +def save_uploaded_file( + file, + item_ids=None, + block_ids=None, + last_modified=None, + size_bytes: int | None = None, + creator_ids: list[PyObjectId | str] | None = None, +): """file is a file object from a flask request. last_modified should be an isodate format. if last_modified is None, the current time will be inserted """ @@ -333,6 +344,7 @@ def save_uploaded_file(file, item_ids=None, block_ids=None, last_modified=None, last_modified_remote=None, # not used for source=uploaded is_live=False, # not available for source=uploaded revision=1, # increment with each update + creator_ids=creator_ids if creator_ids else [], ) result = file_collection.insert_one(new_file_document.dict()) @@ -347,7 +359,7 @@ def save_uploaded_file(file, item_ids=None, block_ids=None, last_modified=None, file.save(file_location) updated_file_entry = file_collection.find_one_and_update( - {"_id": inserted_id}, + {"_id": inserted_id, **get_default_permissions(user_only=False)}, { "$set": { "location": file_location, @@ -447,7 +459,7 @@ def add_file_from_remote_directory(file_entry, item_id, block_ids=None): _sync_file_with_remote(full_remote_path, new_file_location) updated_file_entry = file_collection.find_one_and_update( - {"_id": inserted_id}, + {"_id": inserted_id, **get_default_permissions(user_only=False)}, { "$set": { "location": new_file_location, @@ -471,7 +483,9 @@ def add_file_from_remote_directory(file_entry, item_id, block_ids=None): def retrieve_file_path(file_ObjectId): file_collection = flask_mongo.db.files - result = file_collection.find_one({"_id": ObjectId(file_ObjectId)}) + result = file_collection.find_one( + {"_id": ObjectId(file_ObjectId), **get_default_permissions(user_only=False)} + ) if not result: raise FileNotFoundError( f"The file with file_ObjectId: {file_ObjectId} could not be found in the database" diff --git a/pydatalab/pydatalab/models/files.py b/pydatalab/pydatalab/models/files.py index 64014bbcc..4b468bae4 100644 --- a/pydatalab/pydatalab/models/files.py +++ b/pydatalab/pydatalab/models/files.py @@ -4,10 +4,11 @@ from pydantic import Field from pydatalab.models.entries import Entry +from pydatalab.models.traits import HasOwner, HasRevisionControl from pydatalab.models.utils import IsoformatDateTime -class File(Entry): +class File(Entry, HasOwner, HasRevisionControl): """A model for representing a file that has been tracked or uploaded to datalab.""" type: str = Field("files", const="files", pattern="^files$") diff --git a/pydatalab/pydatalab/routes/v0_1/files.py b/pydatalab/pydatalab/routes/v0_1/files.py index 025d54abd..86731cd1d 100644 --- a/pydatalab/pydatalab/routes/v0_1/files.py +++ b/pydatalab/pydatalab/routes/v0_1/files.py @@ -2,6 +2,7 @@ from typing import Callable, Dict from bson import ObjectId +from bson.errors import InvalidId from flask import jsonify, request, send_from_directory from flask_login import current_user from pymongo import ReturnDocument @@ -13,14 +14,22 @@ from pydatalab.routes.utils import get_default_permissions -def get_file(file_id, filename): - if not current_user.is_authenticated and not CONFIG.TESTING: +def get_file(file_id: str, filename: str): + try: + _file_id = ObjectId(file_id) + except InvalidId: + # If the ID is invalid, then there will be no results in the database anyway, + # so just 401 + _file_id = file_id + if not pydatalab.mongo.flask_mongo.db.files.find_one( + {"_id": _file_id, **get_default_permissions(user_only=True)} + ): return ( jsonify( { "status": "error", "title": "Not Authorized", - "detail": "File access requires login.", + "detail": "Authorization required to access file", } ), 401, diff --git a/pydatalab/schemas/cell.json b/pydatalab/schemas/cell.json index ec5223132..8d74a300a 100644 --- a/pydatalab/schemas/cell.json +++ b/pydatalab/schemas/cell.json @@ -431,6 +431,30 @@ "description": "A model for representing a file that has been tracked or uploaded to datalab.", "type": "object", "properties": { + "revision": { + "title": "Revision", + "default": 1, + "type": "integer" + }, + "revisions": { + "title": "Revisions", + "type": "object" + }, + "creator_ids": { + "title": "Creator Ids", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "creators": { + "title": "Creators", + "type": "array", + "items": { + "$ref": "#/definitions/Person" + } + }, "type": { "title": "Type", "default": "files", diff --git a/pydatalab/schemas/sample.json b/pydatalab/schemas/sample.json index ba29b0ff3..9aa3707d2 100644 --- a/pydatalab/schemas/sample.json +++ b/pydatalab/schemas/sample.json @@ -390,6 +390,30 @@ "description": "A model for representing a file that has been tracked or uploaded to datalab.", "type": "object", "properties": { + "revision": { + "title": "Revision", + "default": 1, + "type": "integer" + }, + "revisions": { + "title": "Revisions", + "type": "object" + }, + "creator_ids": { + "title": "Creator Ids", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "creators": { + "title": "Creators", + "type": "array", + "items": { + "$ref": "#/definitions/Person" + } + }, "type": { "title": "Type", "default": "files", diff --git a/pydatalab/schemas/startingmaterial.json b/pydatalab/schemas/startingmaterial.json index 0d10ac0bf..66f49b464 100644 --- a/pydatalab/schemas/startingmaterial.json +++ b/pydatalab/schemas/startingmaterial.json @@ -441,6 +441,30 @@ "description": "A model for representing a file that has been tracked or uploaded to datalab.", "type": "object", "properties": { + "revision": { + "title": "Revision", + "default": 1, + "type": "integer" + }, + "revisions": { + "title": "Revisions", + "type": "object" + }, + "creator_ids": { + "title": "Creator Ids", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "creators": { + "title": "Creators", + "type": "array", + "items": { + "$ref": "#/definitions/Person" + } + }, "type": { "title": "Type", "default": "files",