Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic permissions to files #445

Merged
merged 4 commits into from
Aug 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Additional notes:
4. Run the webapp from a development server with `yarn serve`.

Similar to the Flask development server, these steps will provide a development environment that serves the web app at `localhost:8080` (by default) and automatically reloads it as changes are made to the source code.
Various other development scripts are available through `yarn` (see also the [webapp README](./webapp/README.md)):
Various other development scripts are available through `yarn`:

- `yarn lint`: Lint the javascript code using `eslint`, identifying issues and automatically fixing many. This linting process also runs automatically every time the development server reloads.
- `yarn test:unit`: run the unit/componenet tests using `jest`. These test individual functions or components.
Expand Down
4 changes: 2 additions & 2 deletions pydatalab/docs/.pages
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ nav:
- index.md
- INSTALL.md
- design
- schemas
- api_reference
- 'Model schemas': schemas
- api_reference.md
File renamed without changes.
17 changes: 4 additions & 13 deletions pydatalab/pydatalab/blocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,10 @@
from pydatalab.apps.raman import RamanBlock
from pydatalab.apps.tga import MassSpecBlock
from pydatalab.apps.xrd import XRDBlock
from pydatalab.blocks.base import (
DataBlock,
)
from pydatalab.blocks.common import (
CommentBlock,
MediaBlock,
NotSupportedBlock,
)
from pydatalab.blocks.base import DataBlock
from pydatalab.blocks.common import CommentBlock, MediaBlock, NotSupportedBlock

BLOCKS: Sequence[Type[DataBlock]] = (
DataBlock,
BLOCKS: Sequence[Type["DataBlock"]] = (
CommentBlock,
MediaBlock,
XRDBlock,
Expand All @@ -31,11 +24,9 @@
EISBlock,
)

BLOCK_TYPES: Dict[str, Type[DataBlock]] = {block.blocktype: block for block in BLOCKS}
BLOCK_TYPES["test"] = DataBlock
BLOCK_TYPES: Dict[str, Type["DataBlock"]] = {block.blocktype: block for block in BLOCKS}

__all__ = (
"DataBlock",
"CommentBlock",
"MediaBlock",
"XRDBlock",
Expand Down
32 changes: 23 additions & 9 deletions pydatalab/pydatalab/file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.permissions import get_default_permissions

FILE_DIRECTORY = CONFIG.FILE_DIRECTORY
DIRECTORIES_DICT = {fs.name: fs for fs in CONFIG.REMOTE_FILESYSTEMS}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -286,12 +290,19 @@ 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
"""

from pydatalab.routes.utils import get_default_permissions
from pydatalab.permissions import get_default_permissions

sample_collection = flask_mongo.db.items
file_collection = flask_mongo.db.files
Expand Down Expand Up @@ -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())
Expand All @@ -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,
Expand Down Expand Up @@ -376,7 +388,7 @@ def save_uploaded_file(file, item_ids=None, block_ids=None, last_modified=None,


def add_file_from_remote_directory(file_entry, item_id, block_ids=None):
from pydatalab.routes.utils import get_default_permissions
from pydatalab.permissions import get_default_permissions

file_collection = flask_mongo.db.files
sample_collection = flask_mongo.db.items
Expand Down Expand Up @@ -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,
Expand All @@ -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"
Expand All @@ -490,7 +504,7 @@ def remove_file_from_sample(item_id: Union[str, ObjectId], file_id: Union[str, O
file_id: The database ID of the file to remove from the item.

"""
from pydatalab.routes.utils import get_default_permissions
from pydatalab.permissions import get_default_permissions

item_id, file_id = ObjectId(item_id), ObjectId(file_id)
sample_collection = flask_mongo.db.items
Expand Down
3 changes: 2 additions & 1 deletion pydatalab/pydatalab/models/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -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$")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def get_default_permissions(user_only: bool = True) -> Dict[str, Any]:
):
return {}

null_perm = {"creator_ids": {"$size": 0}}
null_perm = {"$or": [{"creator_ids": {"$size": 0}}, {"creator_ids": {"$exists": False}}]}
if current_user.is_authenticated and current_user.person is not None:
# find managed users under the given user (can later be expanded to groups)
managed_users = list(
Expand All @@ -41,7 +41,7 @@ def get_default_permissions(user_only: bool = True) -> Dict[str, Any]:
)
if managed_users:
managed_users = [u["_id"] for u in managed_users]
LOGGER.info("Found users %s for user %s", managed_users, current_user.person)
LOGGER.debug("Found managed users %s for user %s", managed_users, current_user.person)

user_perm = {"creator_ids": {"$in": [current_user.person.immutable_id] + managed_users}}
if user_only:
Expand Down
5 changes: 3 additions & 2 deletions pydatalab/pydatalab/routes/v0_1/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

from flask import jsonify, request

from pydatalab.blocks import BLOCK_TYPES, DataBlock
from pydatalab.blocks import BLOCK_TYPES
from pydatalab.blocks.base import DataBlock
from pydatalab.logger import LOGGER
from pydatalab.mongo import flask_mongo
from pydatalab.routes.utils import get_default_permissions
from pydatalab.permissions import get_default_permissions


def add_data_block():
Expand Down
2 changes: 1 addition & 1 deletion pydatalab/pydatalab/routes/v0_1/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pydatalab.logger import logged_route
from pydatalab.models.collections import Collection
from pydatalab.mongo import flask_mongo
from pydatalab.routes.utils import get_default_permissions
from pydatalab.permissions import get_default_permissions
from pydatalab.routes.v0_1.items import creators_lookup, get_samples_summary

collection = Blueprint("collections", __name__)
Expand Down
21 changes: 15 additions & 6 deletions pydatalab/pydatalab/routes/v0_1/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -10,17 +11,25 @@
import pydatalab.mongo
from pydatalab import file_utils
from pydatalab.config import CONFIG
from pydatalab.routes.utils import get_default_permissions


def get_file(file_id, filename):
if not current_user.is_authenticated and not CONFIG.TESTING:
from pydatalab.permissions import get_default_permissions


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,
Expand Down
2 changes: 1 addition & 1 deletion pydatalab/pydatalab/routes/v0_1/graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from flask import jsonify, request

from pydatalab.mongo import flask_mongo
from pydatalab.routes.utils import get_default_permissions
from pydatalab.permissions import get_default_permissions


def get_graph_cy_format(item_id: Optional[str] = None, collection_id: Optional[str] = None):
Expand Down
2 changes: 1 addition & 1 deletion pydatalab/pydatalab/routes/v0_1/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from pydatalab.models.relationships import RelationshipType
from pydatalab.models.utils import generate_unique_refcode
from pydatalab.mongo import flask_mongo
from pydatalab.routes.utils import get_default_permissions
from pydatalab.permissions import get_default_permissions


def reserialize_blocks(display_order: List[str], blocks_obj: Dict[str, Dict]) -> Dict[str, Dict]:
Expand Down
24 changes: 24 additions & 0 deletions pydatalab/schemas/cell.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 24 additions & 0 deletions pydatalab/schemas/sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 24 additions & 0 deletions pydatalab/schemas/startingmaterial.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading