Skip to content

Commit

Permalink
Merge pull request #63 from bento-platform/chore/new-authz
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlougheed authored Nov 29, 2023
2 parents 743af83 + 8f4f153 commit ae0a1ac
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 70 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: "recursive"

- name: Run Bento build action
uses: bento-platform/[email protected].0
uses: bento-platform/[email protected].1
with:
registry: ghcr.io
registry-username: ${{ github.actor }}
Expand Down
10 changes: 5 additions & 5 deletions bento_beacon/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,16 @@
retries = 0
max_retries = current_app.config["MAX_RETRIES_FOR_CENSORSHIP_PARAMS"]
for tries in range(max_retries+1):
current_app.logger.info("calling katsu for censorship parameters")
current_app.logger.info(f"calling katsu for censorship parameters (try={tries})")
try:
max_filters, count_threshold = katsu_censorship_settings()
except APIException:
# If we got values successfully, without an API exception being raised, exit early - even if they're None
break
except APIException as e:
# katsu down or unavailable, details logged when exception thrown
# swallow exception and continue retries
current_app.logger.error("error calling katsu for censorship settings")
current_app.logger.error(f"error calling katsu for censorship settings: {e}")

if max_filters is not None and count_threshold is not None:
break
sleep(5 + 5*tries)

# either params retrieved or we hit max retries
Expand Down
34 changes: 5 additions & 29 deletions bento_beacon/authz/middleware.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
from flask import request
from bento_lib.auth.middleware.flask import FlaskAuthMiddleware
from bento_lib.auth.permissions import Permission
from bento_lib.auth.resources import RESOURCE_EVERYTHING
from ..config_files.config import Config
from ..utils.beacon_response import middleware_meta_callback

__all__ = [
"authz_middleware",
"PERMISSION_QUERY_PROJECT_LEVEL_BOOLEAN",
"PERMISSION_QUERY_DATASET_LEVEL_BOOLEAN",
"PERMISSION_QUERY_PROJECT_LEVEL_COUNTS",
"PERMISSION_QUERY_DATASET_LEVEL_COUNTS",
"PERMISSION_QUERY_DATA",
"PERMISSION_DOWNLOAD_DATA",
"check_permissions",
"check_permission"
"check_permission",
]


Expand All @@ -23,25 +18,6 @@
debug_mode=Config.BENTO_DEBUG
)

# for now, these will go unused - Beacon currently does not have a strong concept of Bento projects/datasets
PERMISSION_QUERY_PROJECT_LEVEL_BOOLEAN = "query:project_level_boolean"
PERMISSION_QUERY_DATASET_LEVEL_BOOLEAN = "query:dataset_level_boolean"
PERMISSION_QUERY_PROJECT_LEVEL_COUNTS = "query:project_level_counts"
PERMISSION_QUERY_DATASET_LEVEL_COUNTS = "query:dataset_level_counts"
# these permissions can open up various aspects of handoff / full-search
PERMISSION_QUERY_DATA = "query:data"
PERMISSION_DOWNLOAD_DATA = "download:data"


def check_permissions(permissions: list[str]) -> bool:
auth_res = authz_middleware.authz_post(request, "/policy/evaluate", body={
"requested_resource": {"everything": True},
"required_permissions": permissions,
},
)["result"]
authz_middleware.mark_authz_done(request)
return auth_res


def check_permission(permission: str) -> bool:
return check_permissions([permission])
def check_permission(permission: Permission) -> bool:
return authz_middleware.evaluate_one(request, RESOURCE_EVERYTHING, permission, mark_authz_done=True)
10 changes: 6 additions & 4 deletions bento_beacon/endpoints/individuals.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from bento_lib.auth.permissions import (
P_DOWNLOAD_DATA,
P_QUERY_DATA,
)
from flask import Blueprint
from functools import reduce
from ..authz.middleware import (
authz_middleware,
PERMISSION_DOWNLOAD_DATA,
PERMISSION_QUERY_DATA,
check_permission
)
from ..utils.beacon_request import (
Expand Down Expand Up @@ -94,7 +96,7 @@ def individuals_full_results(ids):
# if len(ids) > 100:
# return {"message": "too many ids for full response"}

handover_permission = check_permission(PERMISSION_DOWNLOAD_DATA)
handover_permission = check_permission(P_DOWNLOAD_DATA)
handover = handover_for_ids(ids) if handover_permission else {}
phenopackets_by_result_set = phenopackets_for_ids(ids).get("results", {})
result_ids = list(phenopackets_by_result_set.keys())
Expand All @@ -121,7 +123,7 @@ def individuals_full_results(ids):


@individuals.route("/individuals/<id>", methods=['GET', 'POST'])
@authz_middleware.deco_require_permissions_on_resource({PERMISSION_QUERY_DATA})
@authz_middleware.deco_require_permissions_on_resource({P_QUERY_DATA})
def individual_by_id(id):
# forbidden / unauthorized if no permissions
return beacon_result_set_response([id], 1)
Expand Down
7 changes: 4 additions & 3 deletions bento_beacon/utils/beacon_request.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from flask import current_app, request, g
import jsonschema
from bento_lib.auth.permissions import P_QUERY_DATA
from flask import current_app, request, g
from .exceptions import InvalidQuery
from .censorship import reject_if_too_many_filters
from ..authz.middleware import check_permission, PERMISSION_QUERY_DATA
from ..authz.middleware import check_permission


def request_defaults():
Expand Down Expand Up @@ -141,4 +142,4 @@ def summary_stats_requested():

def verify_permissions():
# can do much more here in the future
g.permission_query_data = check_permission(PERMISSION_QUERY_DATA)
g.permission_query_data = check_permission(P_QUERY_DATA)
16 changes: 8 additions & 8 deletions bento_beacon/utils/katsu_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import requests
from flask import current_app
from json import JSONDecodeError
import requests
from urllib.parse import urlsplit, urlunsplit
from .exceptions import APIException, InvalidQuery
from functools import reduce
Expand Down Expand Up @@ -70,8 +70,8 @@ def katsu_network_call(payload, endpoint=None):

except JSONDecodeError:
# katsu returns html for unhandled exceptions, not json
current_app.logger.error("katsu error")
raise APIException()
current_app.logger.error(f"katsu error: JSON decode error with POST {url}")
raise APIException(message="invalid non-JSON response from katsu")
except requests.exceptions.RequestException as e:
current_app.logger.error(f"katsu error: {e}")
raise APIException(message="error calling katsu metadata service")
Expand Down Expand Up @@ -105,8 +105,8 @@ def katsu_get(endpoint, id=None, query=""):

except JSONDecodeError:
# katsu returns html for unhandled exceptions, not json
current_app.logger.error("katsu error")
raise APIException()
current_app.logger.error(f"katsu error: JSON decode error with GET {query_url}")
raise APIException(message="invalid non-JSON response from katsu")
except requests.exceptions.RequestException as e:
current_app.logger.error(f"katsu error: {e}")
raise APIException(message="error calling katsu metadata service")
Expand Down Expand Up @@ -291,17 +291,17 @@ def overview_statistics():
return katsu_get(current_app.config["KATSU_PRIVATE_OVERVIEW"]).get("data_type_specific", {})


def katsu_censorship_settings():
def katsu_censorship_settings() -> tuple[int | None, int | None]:
overview = katsu_get(current_app.config["KATSU_PUBLIC_OVERVIEW"])
max_filters = overview.get("max_query_parameters")
count_threshold = overview.get("count_threshold")
# return even if None
return max_filters, count_threshold


def katsu_not_found(r):
def katsu_not_found(r) -> bool:
if "count" in r:
return r["count"] == 0

# some endpoints return either an object with an id or an error (with no id)
return "id" not in r
return "id" not in r
42 changes: 23 additions & 19 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,48 +1,52 @@
aiohttp==3.8.5
aiohttp==3.9.1
aiosignal==1.3.1
annotated-types==0.6.0
arrow==1.2.3
async-timeout==4.0.2
async-timeout==4.0.3
attrs==22.2.0
autopep8==1.6.0
bento-lib==7.2.0
certifi==2022.9.24
bento-lib==10.1.1
certifi==2023.11.17
cffi==1.15.1
charset-normalizer==2.1.1
click==8.1.3
cryptography==41.0.2
flake8==4.0.1
Flask==2.2.2
click==8.1.7
cryptography==41.0.5
flake8==6.1.0
Flask==2.2.5
Flask-Cors==3.0.10
fqdn==1.5.1
frozenlist==1.4.0
idna==3.4
importlib-metadata==5.0.0
importlib-resources==5.10.2
importlib-metadata==6.8.0
importlib-resources==6.1.1
isoduration==20.11.0
itsdangerous==2.1.2
Jinja2==3.1.2
jsonpointer==2.3
jsonschema==4.17.3
MarkupSafe==2.1.1
mccabe==0.6.1
MarkupSafe==2.1.3
mccabe==0.7.0
multidict==6.0.4
pkgutil_resolve_name==1.3.10
psycopg2-binary==2.9.6
pycodestyle==2.8.0
psycopg2-binary==2.9.9
pycodestyle==2.11.1
pycparser==2.21
pyflakes==2.4.0
pydantic==2.5.2
pydantic_core==2.14.5
pyflakes==3.1.0
PyJWT==2.8.0
pyrsistent==0.19.3
python-dateutil==2.8.2
redis==4.6.0
requests==2.28.1
requests==2.31.0
rfc3339-validator==0.1.4
rfc3987==1.3.8
six==1.16.0
toml==0.10.2
typing_extensions==4.8.0
uri-template==1.2.0
urllib3==1.26.12
urllib3==1.26.18
webcolors==1.12
Werkzeug==2.2.3
yarl==1.9.2
zipp==3.9.0
yarl==1.9.3
zipp==3.17.0

0 comments on commit ae0a1ac

Please sign in to comment.