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
26 changes: 26 additions & 0 deletions backend/app/deps/authorization_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from app.models.groups import GroupOut, GroupDB
from app.models.metadata import MetadataDB
from app.models.pyobjectid import PyObjectId
from app.routers.authentication import get_admin


async def get_role(
Expand Down Expand Up @@ -147,8 +148,15 @@ async def __call__(
self,
dataset_id: str,
current_user: str = Depends(get_current_username),
admin: bool = Depends(get_admin),
):
# TODO: Make sure we enforce only one role per user per dataset, or find_one could yield wrong answer here.

# If the current user is admin, user has access irrespective of any role assigned
if admin:
return True

# Else check role assigned to the user
authorization = await AuthorizationDB.find_one(
AuthorizationDB.dataset_id == PyObjectId(dataset_id),
Or(
Expand Down Expand Up @@ -196,7 +204,13 @@ async def __call__(
self,
file_id: str,
current_user: str = Depends(get_current_username),
admin: bool = Depends(get_admin),
):
# If the current user is admin, user has access irrespective of any role assigned
if admin:
return True

# Else check role assigned to the user
if (file := await FileDB.get(PydanticObjectId(file_id))) is not None:
authorization = await AuthorizationDB.find_one(
AuthorizationDB.dataset_id == file.dataset_id,
Expand Down Expand Up @@ -227,7 +241,13 @@ async def __call__(
self,
metadata_id: str,
current_user: str = Depends(get_current_username),
admin: bool = Depends(get_admin),
):
# If the current user is admin, user has access irrespective of any role assigned
if admin:
return True

# Else check role assigned to the user
if (md_out := await MetadataDB.get(PydanticObjectId(metadata_id))) is not None:
resource_type = md_out.resource.collection
resource_id = md_out.resource.resource_id
Expand Down Expand Up @@ -287,7 +307,13 @@ async def __call__(
self,
group_id: str,
current_user: str = Depends(get_current_username),
admin: bool = Depends(get_admin),
):
# If the current user is admin, user has access irrespective of any role assigned
if admin:
return True

# Else check role assigned to the user
if (group := await GroupDB.get(group_id)) is not None:
if group.creator == current_user:
# Creator can do everything
Expand Down
18 changes: 7 additions & 11 deletions backend/app/routers/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,18 @@ async def authenticate_user(email: str, password: str):

async def get_admin(dataset_id: str = None, current_username=Depends(get_current_user)):
if (
current_user := await UserDB.find_one(UserDB.email == current_username.email)
) is not None:
if current_user.admin:
return current_user.admin
elif (
dataset_id
and (dataset_db := await DatasetDB.get(PydanticObjectId(dataset_id)))
is not None
):
return DatasetDB.creator.email == current_username.email
return dataset_db.creator.email == current_username.email
else:
if (
current_user := await UserDB.find_one(
UserDB.email == current_username.email
)
) is not None:
return current_user.admin
else:
raise HTTPException(
status_code=404, detail=f"User {current_username.email} not found"
)
return False


@router.post("/users/set_admin/{useremail}", response_model=UserOut)
Expand Down
24 changes: 21 additions & 3 deletions backend/app/routers/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from app.models.groups import GroupDB
from app.models.pyobjectid import PyObjectId
from app.models.users import UserDB
from app.routers.authentication import get_admin
from app.search.index import index_dataset

router = APIRouter()
Expand Down Expand Up @@ -68,18 +69,23 @@ async def save_authorization(
async def get_dataset_role(
dataset_id: str,
current_user=Depends(get_current_username),
admin=Depends(get_admin),
):
"""Retrieve role of user for a specific dataset."""
# Get group id and the associated users from authorization
if (
auth_db := await AuthorizationDB.find_one(
if admin:
auth_db = await AuthorizationDB.find_one(
AuthorizationDB.dataset_id == PyObjectId(dataset_id)
)
else:
auth_db = await AuthorizationDB.find_one(
AuthorizationDB.dataset_id == PyObjectId(dataset_id),
Or(
AuthorizationDB.creator == current_user,
AuthorizationDB.user_ids == current_user,
),
)
) is None:
if auth_db is None:
if (
current_dataset := await DatasetDB.get(PydanticObjectId(dataset_id))
) is not None:
Expand Down Expand Up @@ -124,7 +130,11 @@ async def get_file_role(
file_id: str,
current_user=Depends(get_current_username),
role: RoleType = Depends(get_role_by_file),
admin=Depends(get_admin),
):
# admin is a superuser and has all the privileges
if admin:
return RoleType.OWNER
"""Retrieve role of user for an individual file. Role cannot change between file versions."""
return role

Expand All @@ -134,7 +144,11 @@ async def get_metadata_role(
metadata_id: str,
current_user=Depends(get_current_username),
role: RoleType = Depends(get_role_by_metadata),
admin=Depends(get_admin),
):
# admin is a superuser and has all the privileges
if admin:
return RoleType.OWNER
"""Retrieve role of user for group. Group roles can be OWNER, EDITOR, or VIEWER (for regular Members)."""
return role

Expand All @@ -144,7 +158,11 @@ async def get_group_role(
group_id: str,
current_user=Depends(get_current_username),
role: RoleType = Depends(get_role_by_group),
admin=Depends(get_admin),
):
# admin is a superuser and has all the privileges
if admin:
return RoleType.OWNER
"""Retrieve role of user on a particular group (i.e. whether they can change group memberships)."""
return role

Expand Down
10 changes: 9 additions & 1 deletion backend/app/routers/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
from app.models.thumbnails import ThumbnailDB
from app.models.users import UserOut
from app.rabbitmq.listeners import submit_dataset_job
from app.routers.authentication import get_admin
from app.routers.files import add_file_entry, remove_file_entry
from app.search.connect import (
delete_document_by_id,
Expand Down Expand Up @@ -210,8 +211,15 @@ async def get_datasets(
skip: int = 0,
limit: int = 10,
mine: bool = False,
admin=Depends(get_admin),
):
if mine:
if admin:
datasets = await DatasetDBViewList.find(
sort=(-DatasetDBViewList.created),
skip=skip,
limit=limit,
).to_list()
elif mine:
datasets = await DatasetDBViewList.find(
DatasetDBViewList.creator.email == user_id,
sort=(-DatasetDBViewList.created),
Expand Down
13 changes: 9 additions & 4 deletions backend/app/routers/elasticsearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@

from app.config import settings
from app.keycloak_auth import get_current_username
from app.routers.authentication import get_admin
from app.search.connect import connect_elasticsearch, search_index

router = APIRouter()


def _add_permissions_clause(query, username: str):
def _add_permissions_clause(query, username: str, admin: bool = Depends(get_admin)):
"""Append filter to Elasticsearch object that restricts permissions based on the requesting user."""
# TODO: Add public filter once added

user_clause = {
"bool": {
"should": [
Expand All @@ -29,9 +31,12 @@ def _add_permissions_clause(query, username: str):
continue # last line
json_content = json.loads(content)
if "query" in json_content:
json_content["query"] = {
"bool": {"must": [user_clause, json_content["query"]]}
}
if admin:
json_content["query"] = {"bool": {"must": [json_content["query"]]}}
else:
json_content["query"] = {
"bool": {"must": [user_clause, json_content["query"]]}
}
updated_query += json.dumps(json_content) + "\n"
return updated_query.encode()

Expand Down
18 changes: 15 additions & 3 deletions frontend/src/openapi/v2/services/AuthorizationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,21 @@ export class AuthorizationService {

/**
* Get File Role
* Retrieve role of user for an individual file. Role cannot change between file versions.
* @param fileId
* @param datasetId
* @returns RoleType Successful Response
* @throws ApiError
*/
public static getFileRoleApiV2AuthorizationsFilesFileIdRoleGet(
fileId: string,
datasetId?: string,
): CancelablePromise<RoleType> {
return __request({
method: 'GET',
path: `/api/v2/authorizations/files/${fileId}/role`,
query: {
'dataset_id': datasetId,
},
errors: {
422: `Validation Error`,
},
Expand All @@ -114,17 +118,21 @@ export class AuthorizationService {

/**
* Get Metadata Role
* Retrieve role of user for group. Group roles can be OWNER, EDITOR, or VIEWER (for regular Members).
* @param metadataId
* @param datasetId
* @returns AuthorizationMetadata Successful Response
* @throws ApiError
*/
public static getMetadataRoleApiV2AuthorizationsMetadataMetadataIdRoleGet(
metadataId: string,
datasetId?: string,
): CancelablePromise<AuthorizationMetadata> {
return __request({
method: 'GET',
path: `/api/v2/authorizations/metadata/${metadataId}/role`,
query: {
'dataset_id': datasetId,
},
errors: {
422: `Validation Error`,
},
Expand All @@ -133,17 +141,21 @@ export class AuthorizationService {

/**
* Get Group Role
* Retrieve role of user on a particular group (i.e. whether they can change group memberships).
* @param groupId
* @param datasetId
* @returns RoleType Successful Response
* @throws ApiError
*/
public static getGroupRoleApiV2AuthorizationsGroupsGroupIdRoleGet(
groupId: string,
datasetId?: string,
): CancelablePromise<RoleType> {
return __request({
method: 'GET',
path: `/api/v2/authorizations/groups/${groupId}/role`,
query: {
'dataset_id': datasetId,
},
errors: {
422: `Validation Error`,
},
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/openapi/v2/services/DatasetsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ export class DatasetsService {
* @param skip
* @param limit
* @param mine
* @param datasetId
* @returns DatasetOut Successful Response
* @throws ApiError
*/
public static getDatasetsApiV2DatasetsGet(
skip?: number,
limit: number = 10,
mine: boolean = false,
datasetId?: string,
): CancelablePromise<Array<DatasetOut>> {
return __request({
method: 'GET',
Expand All @@ -36,6 +38,7 @@ export class DatasetsService {
'skip': skip,
'limit': limit,
'mine': mine,
'dataset_id': datasetId,
},
errors: {
422: `Validation Error`,
Expand Down
Loading