"""Utilities functions for handlers.""" from functools import wraps from http import HTTPStatus from typing import Callable, Optional from flask import request from owsrequest import flask_request from owsrequest.constants import headers from collaborator.constants import error, header from collaborator.models.ows import ows_permissions from collaborator.utils.error import OwsError from collaborator.utils.helpers import get_from_response from collaborator.utils.typing import AuthorizedResources, Resource, User def _get_profile_type_profile_id(current_request): """Return values from profile headers.""" return flask_request.get_profile_headers(current_request) def parse_boolean_parameter(param: Optional[str]) -> Optional[bool]: """Parse a string parameter into a boolean. Args: param (str): Parameter to parse Returns: bool?: None if not able to parse """ if param is None: return None param = param.lower() if param == "true" or param == "1": return True elif param == "false" or param == "0": return False return None def require_body_params(required_params: list) -> Callable: """Ensure that the required body parameters are present. If the required parameters are there it injects them into the function under the name "params". Args: required_params (list): list of the required parameters. Returns: Function: decorated function """ def wrap_function(function: Callable): @wraps(function) def call_function(*args, **kwargs): data = request.get_json() if not data: raise OwsError( code=error.ERROR_CODE_MISSING_PARAMS, message=error.ERROR_MESSAGE_MISSING_PARAMS, ) for param in required_params: if param not in data or not data[param]: raise OwsError( code=error.ERROR_CODE_MISSING_PARAMS, message=error.ERROR_MESSAGE_MISSING_PARAMS, ) kwargs["params"] = data return function(*args, **kwargs) return call_function return wrap_function def _verify_profile_headers(current_request): """Return True if all the required headers are present and valid. Raises OwsError otherwise. """ if not flask_request.verify_profile_headers(current_request): raise OwsError( code=error.ERROR_CODE_BAD_GRASS_REQUEST, message=error.ERROR_MESSAGE_INCOMPLETE_GRASS_HEADERS, ) orchard_identity_id = request.headers.get(headers.ORCHARD_IDENTITY_ID) if not orchard_identity_id: raise OwsError( code=error.ERROR_CODE_BAD_GRASS_REQUEST, message=error.ERROR_MESSAGE_INCOMPLETE_GRASS_HEADERS, ) return True def get_vendor_id_from_request(is_optional: bool = False): """Fetch vendor ID from request. Returns: vendor_id: vendor ID present in the request """ if request.method in ["GET", "DELETE"]: vendor_id = request.args.get("vendor_id") if request.method in ["POST", "PUT"]: data = request.get_json() if type(data) is list: vendor_id = data[0].get("vendor_id") elif data is not None: vendor_id = data.get("vendor_id") if not vendor_id and not is_optional: raise OwsError( code=error.ERROR_CODE_BAD_PARAMS, message=error.ERROR_MESSAGE_BAD_PARAMS ) return vendor_id or None def fetch_profile_resources(profile_type: str, profile_id: str): """Fetch profile resources for a given profile_id of profile_type. Args: profile_id (int): The Profile ID. profile_type (str): The Profile type. Returns: authorized_resources: The AuthorizedResources list. """ authorized_resources = AuthorizedResources() authorized_resources.profile_type = profile_type # Abacus profiles always have full catalog access if profile_type == header.ABACUS_PROFILE: authorized_resources.full_catalog_access = True return authorized_resources # Return early if the resource mapping is invalid resource_type = header.PROFILE_TO_RESOURCE_MAP.get(profile_type, None) if resource_type is None: return None # Fetch resources for profile resources_response = ows_permissions.get_permissions( profile_type, profile_id, resource_type ) resource_items = get_from_response(resources_response, "items") for item in resource_items: resource = Resource(type=resource_type, id=str(item["id"])) if item["id"] == "*": if profile_type in header.FULL_CATALOG_ACCESS_PROFILE_TYPES: authorized_resources.full_catalog_access = True continue authorized_resources.append(resource) return authorized_resources def fetch_authorized_resources(function): """Fetch authorized resources based request headers. Args: Function (func): the handler to decorate with authorized accounts. Returns: Function: The decorated function. """ @wraps(function) def wrapper(*args, **kwargs): user, authorized_resources = (None, AuthorizedResources()) if _verify_profile_headers(request) is True: orchard_identity_id = request.headers[headers.ORCHARD_IDENTITY_ID] user = User(type=headers.ORCHARD_IDENTITY_ID, id=orchard_identity_id) profile_type, profile_id = _get_profile_type_profile_id(request) authorized_resources = fetch_profile_resources(profile_type, profile_id) if authorized_resources is None: raise OwsError( code=error.ERROR_CODE_UNEXPECTED_PROFILE_TYPE, message=error.ERROR_MESSAGE_UNEXPECTED_PROFILE_TYPE, status=HTTPStatus.FORBIDDEN, ) kwargs["user"] = user kwargs["authorized_resources"] = authorized_resources return function(*args, **kwargs) return wrapper def fetch_profile_type(function): """Fetch profile type from header. Args: function (func): The handler to decorate with the profile type. Returns: func: The decorated function. """ @wraps(function) def wrapper(*args, **kwargs): profile_type, _ = _get_profile_type_profile_id(request) kwargs["profile_type"] = profile_type return function(*args, **kwargs) return wrapper