Skip to content

Commit 3e71882

Browse files
authored
Adding admin dependency in authorization of dataset,files, metadata,search (#819)
* Basic implementation of admin in user collection in mongodb * black formatting * adding codegen files * minor fix to boolean logic * Adding get_admin dependency black formatting adding codegen file removing redundant print statement fixing pytest failure fixing pytest failure ran black * fixed pytest failure * Adding admin dependency in authorization of dataset,files, metadata, search * allowing admin to view all datasets * adding admin in get_roles functions * adding codegen files * modifying test * addressing commnets * adding the test back * merge main
1 parent b30e6f4 commit 3e71882

File tree

10 files changed

+194
-22
lines changed

10 files changed

+194
-22
lines changed

backend/app/deps/authorization_deps.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from app.models.groups import GroupOut, GroupDB
1111
from app.models.metadata import MetadataDB
1212
from app.models.pyobjectid import PyObjectId
13+
from app.routers.authentication import get_admin
1314

1415

1516
async def get_role(
@@ -147,8 +148,15 @@ async def __call__(
147148
self,
148149
dataset_id: str,
149150
current_user: str = Depends(get_current_username),
151+
admin: bool = Depends(get_admin),
150152
):
151153
# TODO: Make sure we enforce only one role per user per dataset, or find_one could yield wrong answer here.
154+
155+
# If the current user is admin, user has access irrespective of any role assigned
156+
if admin:
157+
return True
158+
159+
# Else check role assigned to the user
152160
authorization = await AuthorizationDB.find_one(
153161
AuthorizationDB.dataset_id == PyObjectId(dataset_id),
154162
Or(
@@ -196,7 +204,13 @@ async def __call__(
196204
self,
197205
file_id: str,
198206
current_user: str = Depends(get_current_username),
207+
admin: bool = Depends(get_admin),
199208
):
209+
# If the current user is admin, user has access irrespective of any role assigned
210+
if admin:
211+
return True
212+
213+
# Else check role assigned to the user
200214
if (file := await FileDB.get(PydanticObjectId(file_id))) is not None:
201215
authorization = await AuthorizationDB.find_one(
202216
AuthorizationDB.dataset_id == file.dataset_id,
@@ -227,7 +241,13 @@ async def __call__(
227241
self,
228242
metadata_id: str,
229243
current_user: str = Depends(get_current_username),
244+
admin: bool = Depends(get_admin),
230245
):
246+
# If the current user is admin, user has access irrespective of any role assigned
247+
if admin:
248+
return True
249+
250+
# Else check role assigned to the user
231251
if (md_out := await MetadataDB.get(PydanticObjectId(metadata_id))) is not None:
232252
resource_type = md_out.resource.collection
233253
resource_id = md_out.resource.resource_id
@@ -287,7 +307,13 @@ async def __call__(
287307
self,
288308
group_id: str,
289309
current_user: str = Depends(get_current_username),
310+
admin: bool = Depends(get_admin),
290311
):
312+
# If the current user is admin, user has access irrespective of any role assigned
313+
if admin:
314+
return True
315+
316+
# Else check role assigned to the user
291317
if (group := await GroupDB.get(group_id)) is not None:
292318
if group.creator == current_user:
293319
# Creator can do everything

backend/app/routers/authentication.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,18 @@ async def authenticate_user(email: str, password: str):
9595

9696
async def get_admin(dataset_id: str = None, current_username=Depends(get_current_user)):
9797
if (
98+
current_user := await UserDB.find_one(UserDB.email == current_username.email)
99+
) is not None:
100+
if current_user.admin:
101+
return current_user.admin
102+
elif (
98103
dataset_id
99104
and (dataset_db := await DatasetDB.get(PydanticObjectId(dataset_id)))
100105
is not None
101106
):
102-
return DatasetDB.creator.email == current_username.email
107+
return dataset_db.creator.email == current_username.email
103108
else:
104-
if (
105-
current_user := await UserDB.find_one(
106-
UserDB.email == current_username.email
107-
)
108-
) is not None:
109-
return current_user.admin
110-
else:
111-
raise HTTPException(
112-
status_code=404, detail=f"User {current_username.email} not found"
113-
)
109+
return False
114110

115111

116112
@router.post("/users/set_admin/{useremail}", response_model=UserOut)

backend/app/routers/authorization.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from app.models.groups import GroupDB
3030
from app.models.pyobjectid import PyObjectId
3131
from app.models.users import UserDB
32+
from app.routers.authentication import get_admin
3233
from app.search.index import index_dataset
3334

3435
router = APIRouter()
@@ -68,18 +69,23 @@ async def save_authorization(
6869
async def get_dataset_role(
6970
dataset_id: str,
7071
current_user=Depends(get_current_username),
72+
admin=Depends(get_admin),
7173
):
7274
"""Retrieve role of user for a specific dataset."""
7375
# Get group id and the associated users from authorization
74-
if (
75-
auth_db := await AuthorizationDB.find_one(
76+
if admin:
77+
auth_db = await AuthorizationDB.find_one(
78+
AuthorizationDB.dataset_id == PyObjectId(dataset_id)
79+
)
80+
else:
81+
auth_db = await AuthorizationDB.find_one(
7682
AuthorizationDB.dataset_id == PyObjectId(dataset_id),
7783
Or(
7884
AuthorizationDB.creator == current_user,
7985
AuthorizationDB.user_ids == current_user,
8086
),
8187
)
82-
) is None:
88+
if auth_db is None:
8389
if (
8490
current_dataset := await DatasetDB.get(PydanticObjectId(dataset_id))
8591
) is not None:
@@ -124,7 +130,11 @@ async def get_file_role(
124130
file_id: str,
125131
current_user=Depends(get_current_username),
126132
role: RoleType = Depends(get_role_by_file),
133+
admin=Depends(get_admin),
127134
):
135+
# admin is a superuser and has all the privileges
136+
if admin:
137+
return RoleType.OWNER
128138
"""Retrieve role of user for an individual file. Role cannot change between file versions."""
129139
return role
130140

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

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

backend/app/routers/datasets.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
from app.models.thumbnails import ThumbnailDB
5555
from app.models.users import UserOut
5656
from app.rabbitmq.listeners import submit_dataset_job
57+
from app.routers.authentication import get_admin
5758
from app.routers.files import add_file_entry, remove_file_entry
5859
from app.search.connect import (
5960
delete_document_by_id,
@@ -210,8 +211,15 @@ async def get_datasets(
210211
skip: int = 0,
211212
limit: int = 10,
212213
mine: bool = False,
214+
admin=Depends(get_admin),
213215
):
214-
if mine:
216+
if admin:
217+
datasets = await DatasetDBViewList.find(
218+
sort=(-DatasetDBViewList.created),
219+
skip=skip,
220+
limit=limit,
221+
).to_list()
222+
elif mine:
215223
datasets = await DatasetDBViewList.find(
216224
DatasetDBViewList.creator.email == user_id,
217225
sort=(-DatasetDBViewList.created),

backend/app/routers/elasticsearch.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55

66
from app.config import settings
77
from app.keycloak_auth import get_current_username
8+
from app.routers.authentication import get_admin
89
from app.search.connect import connect_elasticsearch, search_index
910

1011
router = APIRouter()
1112

1213

13-
def _add_permissions_clause(query, username: str):
14+
def _add_permissions_clause(query, username: str, admin: bool = Depends(get_admin)):
1415
"""Append filter to Elasticsearch object that restricts permissions based on the requesting user."""
1516
# TODO: Add public filter once added
17+
1618
user_clause = {
1719
"bool": {
1820
"should": [
@@ -29,9 +31,12 @@ def _add_permissions_clause(query, username: str):
2931
continue # last line
3032
json_content = json.loads(content)
3133
if "query" in json_content:
32-
json_content["query"] = {
33-
"bool": {"must": [user_clause, json_content["query"]]}
34-
}
34+
if admin:
35+
json_content["query"] = {"bool": {"must": [json_content["query"]]}}
36+
else:
37+
json_content["query"] = {
38+
"bool": {"must": [user_clause, json_content["query"]]}
39+
}
3540
updated_query += json.dumps(json_content) + "\n"
3641
return updated_query.encode()
3742

frontend/src/openapi/v2/services/AuthorizationService.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,21 @@ export class AuthorizationService {
9595

9696
/**
9797
* Get File Role
98-
* Retrieve role of user for an individual file. Role cannot change between file versions.
9998
* @param fileId
99+
* @param datasetId
100100
* @returns RoleType Successful Response
101101
* @throws ApiError
102102
*/
103103
public static getFileRoleApiV2AuthorizationsFilesFileIdRoleGet(
104104
fileId: string,
105+
datasetId?: string,
105106
): CancelablePromise<RoleType> {
106107
return __request({
107108
method: 'GET',
108109
path: `/api/v2/authorizations/files/${fileId}/role`,
110+
query: {
111+
'dataset_id': datasetId,
112+
},
109113
errors: {
110114
422: `Validation Error`,
111115
},
@@ -114,17 +118,21 @@ export class AuthorizationService {
114118

115119
/**
116120
* Get Metadata Role
117-
* Retrieve role of user for group. Group roles can be OWNER, EDITOR, or VIEWER (for regular Members).
118121
* @param metadataId
122+
* @param datasetId
119123
* @returns AuthorizationMetadata Successful Response
120124
* @throws ApiError
121125
*/
122126
public static getMetadataRoleApiV2AuthorizationsMetadataMetadataIdRoleGet(
123127
metadataId: string,
128+
datasetId?: string,
124129
): CancelablePromise<AuthorizationMetadata> {
125130
return __request({
126131
method: 'GET',
127132
path: `/api/v2/authorizations/metadata/${metadataId}/role`,
133+
query: {
134+
'dataset_id': datasetId,
135+
},
128136
errors: {
129137
422: `Validation Error`,
130138
},
@@ -133,17 +141,21 @@ export class AuthorizationService {
133141

134142
/**
135143
* Get Group Role
136-
* Retrieve role of user on a particular group (i.e. whether they can change group memberships).
137144
* @param groupId
145+
* @param datasetId
138146
* @returns RoleType Successful Response
139147
* @throws ApiError
140148
*/
141149
public static getGroupRoleApiV2AuthorizationsGroupsGroupIdRoleGet(
142150
groupId: string,
151+
datasetId?: string,
143152
): CancelablePromise<RoleType> {
144153
return __request({
145154
method: 'GET',
146155
path: `/api/v2/authorizations/groups/${groupId}/role`,
156+
query: {
157+
'dataset_id': datasetId,
158+
},
147159
errors: {
148160
422: `Validation Error`,
149161
},

frontend/src/openapi/v2/services/DatasetsService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ export class DatasetsService {
2121
* @param skip
2222
* @param limit
2323
* @param mine
24+
* @param datasetId
2425
* @returns DatasetOut Successful Response
2526
* @throws ApiError
2627
*/
2728
public static getDatasetsApiV2DatasetsGet(
2829
skip?: number,
2930
limit: number = 10,
3031
mine: boolean = false,
32+
datasetId?: string,
3133
): CancelablePromise<Array<DatasetOut>> {
3234
return __request({
3335
method: 'GET',
@@ -36,6 +38,7 @@ export class DatasetsService {
3638
'skip': skip,
3739
'limit': limit,
3840
'mine': mine,
41+
'dataset_id': datasetId,
3942
},
4043
errors: {
4144
422: `Validation Error`,

0 commit comments

Comments
 (0)