Skip to content
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
42 changes: 42 additions & 0 deletions backend/app/deps/authorization_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,48 @@ async def __call__(
)


class FileAuthorization:
"""We use class dependency so that we can provide the `permission` parameter to the dependency.
For more info see https://fastapi.tiangolo.com/advanced/advanced-dependencies/."""

def __init__(self, role: str):
self.role = role

async def __call__(
self,
file_id: str,
db: MongoClient = Depends(get_db),
current_user: str = Depends(get_current_username),
):
if (file := await db["files"].find_one({"_id": ObjectId(file_id)})) is not None:
file_out = FileOut.from_mongo(file)
if (
authorization_q := await db["authorization"].find_one(
{
"$and": [
{"dataset_id": ObjectId(file_out.dataset_id)},
{
"$or": [
{"creator": current_user},
{"user_ids": current_user},
]
},
]
}
)
) is not None:
authorization = AuthorizationDB.from_mongo(authorization_q)
if access(authorization.role, self.role):
return True

raise HTTPException(
status_code=403,
detail=f"User `{current_user} does not have `{self.role}` permission on file {file_id}",
)
else:
raise HTTPException(status_code=404, detail=f"File {file_id} not found")


def access(user_role: RoleType, role_required: RoleType) -> bool:
"""Enforce implied role hierarchy OWNER > EDITOR > UPLOADER > VIEWER"""
if user_role == RoleType.OWNER:
Expand Down
12 changes: 10 additions & 2 deletions backend/app/routers/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from pymongo import MongoClient

from app import dependencies
from app.deps.authorization_deps import FileAuthorization
from app.config import settings
from app.search.connect import (
insert_record,
Expand Down Expand Up @@ -211,6 +212,7 @@ async def update_file(
es: Elasticsearch = Depends(dependencies.get_elasticsearchclient),
credentials: HTTPAuthorizationCredentials = Security(security),
rabbitmq_client: BlockingChannel = Depends(dependencies.get_rabbitmq),
allow: bool = Depends(FileAuthorization("uploader")),
):
# Check all connection and abort if any one of them is not available
if db is None or fs is None or es is None:
Expand Down Expand Up @@ -309,6 +311,7 @@ async def download_file(
version: Optional[int] = None,
db: MongoClient = Depends(dependencies.get_db),
fs: Minio = Depends(dependencies.get_fs),
allow: bool = Depends(FileAuthorization("viewer")),
):
# If file exists in MongoDB, download from Minio
if (file := await db["files"].find_one({"_id": ObjectId(file_id)})) is not None:
Expand Down Expand Up @@ -353,6 +356,7 @@ async def delete_file(
db: MongoClient = Depends(dependencies.get_db),
fs: Minio = Depends(dependencies.get_fs),
es: Elasticsearch = Depends(dependencies.get_elasticsearchclient),
allow: bool = Depends(FileAuthorization("editor")),
):
if (file := await db["files"].find_one({"_id": ObjectId(file_id)})) is not None:
await remove_file_entry(file_id, db, fs, es)
Expand All @@ -365,14 +369,15 @@ async def delete_file(
async def get_file_summary(
file_id: str,
db: MongoClient = Depends(dependencies.get_db),
allow: bool = Depends(FileAuthorization("viewer")),
):
if (file := await db["files"].find_one({"_id": ObjectId(file_id)})) is not None:
# TODO: Incrementing too often (3x per page view)
# file["views"] += 1
# db["files"].replace_one({"_id": ObjectId(file_id)}, file)
return FileOut.from_mongo(file)

raise HTTPException(status_code=404, detail=f"File {file_id} not found")
else:
raise HTTPException(status_code=404, detail=f"File {file_id} not found")


@router.get("/{file_id}/versions", response_model=List[FileVersion])
Expand All @@ -381,6 +386,7 @@ async def get_file_versions(
db: MongoClient = Depends(dependencies.get_db),
skip: int = 0,
limit: int = 20,
allow: bool = Depends(FileAuthorization("viewer")),
):
if (file := await db["files"].find_one({"_id": ObjectId(file_id)})) is not None:
mongo_versions = []
Expand Down Expand Up @@ -410,6 +416,7 @@ async def get_file_extract(
credentials: HTTPAuthorizationCredentials = Security(security),
db: MongoClient = Depends(dependencies.get_db),
rabbitmq_client: BlockingChannel = Depends(dependencies.get_rabbitmq),
allow: bool = Depends(FileAuthorization("uploader")),
):
if extractorName is None:
raise HTTPException(status_code=400, detail=f"No extractorName specified")
Expand Down Expand Up @@ -447,6 +454,7 @@ async def resubmit_file_extractions(
user=Depends(get_current_user),
db: MongoClient = Depends(dependencies.get_db),
rabbitmq_client: BlockingChannel = Depends(dependencies.get_rabbitmq),
allow: bool = Depends(FileAuthorization("editor")),
):
"""This route will check metadata. We get the extractors run from metadata from extractors.
Then they are resubmitted. At present parameters are not stored. This will change once Jobs are
Expand Down