Skip to content

Commit

Permalink
refact: factor out count-related helpers to authz submodule
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlougheed committed Oct 17, 2023
1 parent 2508752 commit 01b9b17
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 38 deletions.
16 changes: 16 additions & 0 deletions chord_metadata_service/authz/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
__all__ = [
"RESOURCE_EVERYTHING",
"PERMISSION_QUERY_DATA",
"PERMISSION_DELETE_DATA",
"PERMISSION_QUERY_DATASET_LEVEL_COUNTS",
"PERMISSION_QUERY_PROJECT_LEVEL_COUNTS",
]

# TODO: this should be a shared module in bento_lib

RESOURCE_EVERYTHING = {"everything": True}
PERMISSION_QUERY_DATA = "query:data"
PERMISSION_DELETE_DATA = "delete:data"

PERMISSION_QUERY_DATASET_LEVEL_COUNTS = "query:dataset_level_counts"
PERMISSION_QUERY_PROJECT_LEVEL_COUNTS = "query:project_level_counts"
47 changes: 47 additions & 0 deletions chord_metadata_service/authz/counts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from django.http import HttpRequest

from .constants import (
PERMISSION_QUERY_DATA,
PERMISSION_QUERY_PROJECT_LEVEL_COUNTS,
PERMISSION_QUERY_DATASET_LEVEL_COUNTS,
)
from .middleware import authz_middleware
from .utils import create_resource


__all__ = [
"get_counts_permission",
"can_see_counts",
"has_counts_permission_for_data_types",
]


def get_counts_permission(dataset_level: bool) -> str:
if dataset_level:
return PERMISSION_QUERY_DATASET_LEVEL_COUNTS
return PERMISSION_QUERY_PROJECT_LEVEL_COUNTS # We don't have a node-level counts permission


async def can_see_counts(request: HttpRequest, resource: dict) -> bool:
return await authz_middleware.async_authz_post(request, "/policy/evaluate", {
"requested_resource": resource,
"required_permissions": [get_counts_permission(resource.get("dataset") is not None)],
})["result"] or (
# If we don't have a count permission, we may still have a query:data permission (no cascade)
await authz_middleware.async_authz_post(request, "/policy/evaluate", {
"requested_resource": resource,
"required_permissions": [PERMISSION_QUERY_DATA],
})["result"]
)


async def has_counts_permission_for_data_types(
request: HttpRequest, project: str, dataset: str, data_types: list[str]
) -> list[bool]:
has_permission: bool = await can_see_counts(request, create_resource(project, dataset, None))

return [
# Either we have permission for all (saves many calls) or we have for a specific data type
has_permission or (await can_see_counts(request, create_resource(project, dataset, dt_id)))
for dt_id in data_types
]
2 changes: 1 addition & 1 deletion chord_metadata_service/authz/permissions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.conf import settings
from rest_framework.permissions import BasePermission, SAFE_METHODS
from chord_metadata_service.authz.middleware import authz_middleware
from .middleware import authz_middleware


__all__ = [
Expand Down
17 changes: 17 additions & 0 deletions chord_metadata_service/authz/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from .constants import RESOURCE_EVERYTHING


__all__ = [
"create_resource",
]


def create_resource(project: str | None, dataset: str | None, data_type: str | None) -> dict:
resource = RESOURCE_EVERYTHING
if project:
resource = {"project": project}
if dataset:
resource["dataset"] = dataset
if data_type:
resource["data_type"] = data_type
return resource
51 changes: 14 additions & 37 deletions chord_metadata_service/chord/views_data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@

from typing import Callable

from chord_metadata_service.chord.models import Dataset, Project
from chord_metadata_service.authz.constants import PERMISSION_QUERY_DATA, PERMISSION_DELETE_DATA
from chord_metadata_service.authz.counts import get_counts_permission, has_counts_permission_for_data_types
from chord_metadata_service.authz.middleware import authz_middleware
from chord_metadata_service.authz.permissions import BentoAllowAny
from chord_metadata_service.authz.utils import create_resource
from chord_metadata_service.chord.models import Dataset, Project
from chord_metadata_service.cleanup import run_all_cleanup
from chord_metadata_service.experiments.models import Experiment, ExperimentResult
from chord_metadata_service.logger import logger
from chord_metadata_service.authz.middleware import authz_middleware
from chord_metadata_service.mcode.models import MCodePacket
from chord_metadata_service.phenopackets.models import Phenopacket

Expand Down Expand Up @@ -87,16 +90,6 @@ async def make_data_type_response_object(
return res


RESOURCE_EVERYTHING = {"everything": True}
PERMISSION_QUERY_DATA = "query:data"


def get_counts_permission(dataset_level: bool) -> str:
if dataset_level:
return "query:dataset_level_counts"
return "query:project_level_counts" # TODO: we don't have a node-level counts permission


async def can_see_counts(request: HttpRequest, resource: dict) -> bool:
return await authz_middleware.async_authz_post(request, "/policy/evaluate", {
"requested_resource": resource,
Expand All @@ -110,34 +103,20 @@ async def can_see_counts(request: HttpRequest, resource: dict) -> bool:
)


def create_resource(project: str | None, dataset: str | None, data_type: str | None) -> dict:
resource = RESOURCE_EVERYTHING
if project:
resource = {"project": project}
if dataset:
resource["dataset"] = dataset
if data_type:
resource["data_type"] = data_type
return resource


@api_view(["GET"])
@permission_classes([BentoAllowAny])
async def data_type_list(request: HttpRequest):
# TODO: Permissions: only return counts when we are authenticated/have access to counts or full data.

project = request.GET.get("project", "").strip() or None
dataset = request.GET.get("dataset", "").strip() or None

has_permission: bool = await can_see_counts(request, create_resource(project, dataset, None))
data_types = list(dt.DATA_TYPES.values())
data_type_count_perms = await has_counts_permission_for_data_types(request, project, dataset, data_types)

dt_response = []
for dt_id, dt_d in dt.DATA_TYPES.items():
for dt_id, dt_d, dt_counts_perm in zip(data_types, dt.DATA_TYPES.values(), data_type_count_perms):
try:
has_dt_permission: bool = has_permission or (
await can_see_counts(request, create_resource(project, dataset, dt_id)))
dt_response.append(
await make_data_type_response_object(dt_id, dt_d, project, dataset, include_counts=has_dt_permission))
await make_data_type_response_object(dt_id, dt_d, project, dataset, include_counts=dt_counts_perm))
except ValueError as e:
return Response(errors.bad_request_error(str(e)), status=status.HTTP_400_BAD_REQUEST)

Expand Down Expand Up @@ -199,7 +178,7 @@ async def dataset_data_type(request: HttpRequest, dataset_id: str, data_type: st
if request.method == "DELETE":
has_permission: bool = await authz_middleware.async_authz_post(request, "/permissions/evaluate", {
"requested_resource": resource,
"required_permissions": ["delete:data"],
"required_permissions": [PERMISSION_DELETE_DATA],
})
authz_middleware.mark_authz_done(request)

Expand Down Expand Up @@ -232,21 +211,19 @@ async def dataset_data_type_summary(request: HttpRequest, dataset_id: str):
dataset = await Dataset.objects.aget(identifier=dataset_id)
project = await Project.objects.aget(datasets=dataset)

base_resource = create_resource(project, dataset, None)
has_permission: bool = await can_see_counts(request, dataset, base_resource)
data_types = list(dt.DATA_TYPES.values())
data_type_count_perms = await has_counts_permission_for_data_types(request, project, dataset, data_types)

dt_response = []
for dt_id, dt_d in dt.DATA_TYPES.items():
for dt_id, dt_d, dt_count_perm in zip(data_types, dt.DATA_TYPES.values(), data_type_count_perms):
try:
has_dt_permission: bool = has_permission or (
await can_see_counts(request, create_resource(project, dataset, dt_id)))
dt_response.append(
await make_data_type_response_object(
dt_id,
dt_d,
project.identifier,
dataset.identifier,
include_counts=has_dt_permission,
include_counts=dt_count_perm,
)
)
except ValueError as e:
Expand Down

0 comments on commit 01b9b17

Please sign in to comment.