from magpie.api import requests as ar, exception as ax, schemas as s
from magpie.api.management.resource.resource_formats import format_resource
from magpie.definitions.ziggurat_definitions import ResourceService
from magpie.definitions.pyramid_definitions import (
asbool,
HTTPOk,
HTTPCreated,
HTTPBadRequest,
HTTPForbidden,
HTTPNotFound,
HTTPConflict,
HTTPInternalServerError,
)
from magpie import models
from magpie.permissions import Permission
from magpie.register import sync_services_phoenix
from magpie.services import SERVICE_TYPE_DICT
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from magpie.definitions.pyramid_definitions import HTTPException # noqa: F401
from magpie.definitions.sqlalchemy_definitions import Session # noqa: F401
from magpie.definitions.typedefs import List, Str, Optional, Tuple, Type, ServiceOrResourceType # noqa: F401
from magpie.services import ServiceInterface # noqa: F401
[docs]def check_valid_service_or_resource_permission(permission_name, service_or_resource, db_session):
# type: (Str, ServiceOrResourceType, Session) -> Optional[Permission]
"""
Checks if a permission is valid to be applied to a specific `service` or a `resource` under a root service.
:param permission_name: permission name to be validated
:param service_or_resource: resource item corresponding to either a Service or a Resource
:param db_session: db connection
:return: valid Permission if allowed by the service/resource
"""
svc_res_permissions = get_resource_permissions(service_or_resource, db_session=db_session)
svc_res_type = service_or_resource.resource_type
svc_res_name = service_or_resource.resource_name
svc_res_perm = Permission.get(permission_name)
ax.verify_param(svc_res_perm, paramName=u"permission_name", paramCompare=svc_res_permissions, isIn=True,
httpError=HTTPBadRequest,
content={u"resource_type": str(svc_res_type), u"resource_name": str(svc_res_name)},
msgOnFail=s.UserResourcePermissions_POST_BadRequestResponseSchema.description)
return svc_res_perm
[docs]def check_valid_service_resource(parent_resource, resource_type, db_session):
"""
Checks if a new Resource can be contained under a parent Resource given the requested type and the corresponding
Service under which the parent Resource is already assigned.
:param parent_resource: Resource under which the new resource of `resource_type` must be placed
:param resource_type: desired resource type
:param db_session:
:return: root Service if all checks were successful
"""
parent_type = parent_resource.resource_type_name
ax.verify_param(models.RESOURCE_TYPE_DICT[parent_type].child_resource_allowed, isEqual=True,
paramCompare=True, httpError=HTTPBadRequest,
msgOnFail="Child resource not allowed for specified parent resource type '{}'".format(parent_type))
root_service = get_resource_root_service(parent_resource, db_session=db_session)
ax.verify_param(root_service, notNone=True, httpError=HTTPInternalServerError,
msgOnFail="Failed retrieving 'root_service' from db")
ax.verify_param(root_service.resource_type, isEqual=True, httpError=HTTPInternalServerError,
paramName=u"resource_type", paramCompare=models.Service.resource_type_name,
msgOnFail="Invalid 'root_service' retrieved from db is not a service")
ax.verify_param(SERVICE_TYPE_DICT[root_service.type].child_resource_allowed, isEqual=True,
paramCompare=True, httpError=HTTPBadRequest,
msgOnFail="Child resource not allowed for specified service type '{}'".format(root_service.type))
ax.verify_param(resource_type, isIn=True, httpError=HTTPBadRequest,
paramName=u"resource_type", paramCompare=SERVICE_TYPE_DICT[root_service.type].resource_type_names,
msgOnFail="Invalid 'resource_type' specified for service type '{}'".format(root_service.type))
return root_service
[docs]def crop_tree_with_permission(children, resource_id_list):
for child_id, child_dict in list(children.items()):
new_children = child_dict[u"children"]
children_returned, resource_id_list = crop_tree_with_permission(new_children, resource_id_list)
if child_id not in resource_id_list and not children_returned:
children.pop(child_id)
elif child_id in resource_id_list:
resource_id_list.remove(child_id)
return dict(children), list(resource_id_list)
[docs]def get_resource_path(resource_id, db_session):
parent_resources = models.resource_tree_service.path_upper(resource_id, db_session=db_session)
parent_path = ""
for parent_resource in parent_resources:
parent_path = "/" + parent_resource.resource_name + parent_path
return parent_path
[docs]def get_service_or_resource_types(service_or_resource):
# type: (ServiceOrResourceType) -> Tuple[Type[ServiceInterface], Str]
"""
Obtain the `service` or `resource` class and a corresponding ``"service"`` or ``"resource"`` type identifier.
"""
if isinstance(service_or_resource, models.Service):
svc_res_type_cls = SERVICE_TYPE_DICT[service_or_resource.type]
svc_res_type_str = u"service"
elif isinstance(service_or_resource, models.Resource):
svc_res_type_cls = models.RESOURCE_TYPE_DICT[service_or_resource.resource_type]
svc_res_type_str = u"resource"
else:
ax.raise_http(httpError=HTTPInternalServerError, detail="Invalid service/resource object",
content={u"service_resource": repr(type(service_or_resource))})
# noinspection PyUnboundLocalVariable
return svc_res_type_cls, svc_res_type_str
[docs]def get_resource_permissions(resource, db_session):
# type: (models.Resource, Session) -> List[Permission]
ax.verify_param(resource, notNone=True, httpError=HTTPBadRequest, paramName=u"resource",
msgOnFail=s.UserResourcePermissions_GET_BadRequestResourceResponseSchema.description)
# directly access the service resource
if resource.root_service_id is None:
service = resource
return SERVICE_TYPE_DICT[service.type].permissions
# otherwise obtain root level service to infer sub-resource permissions
service = ResourceService.by_resource_id(resource.root_service_id, db_session=db_session)
ax.verify_param(service.resource_type, isEqual=True, httpError=HTTPBadRequest,
paramName=u"resource_type", paramCompare=models.Service.resource_type_name,
msgOnFail=s.UserResourcePermissions_GET_BadRequestRootServiceResponseSchema.description)
service_class = SERVICE_TYPE_DICT[service.type]
ax.verify_param(resource.resource_type_name, isIn=True, httpError=HTTPBadRequest,
paramName=u"resource_type", paramCompare=service_class.resource_type_names,
msgOnFail=s.UserResourcePermissions_GET_BadRequestResourceTypeResponseSchema.description)
return service_class.get_resource_permissions(resource.resource_type_name)
[docs]def get_resource_root_service(resource, db_session):
# type: (models.Resource, Session) -> Optional[models.Resource]
"""
Recursively rewinds back through the top of the resource tree up to the top-level service-resource.
:param resource: initial resource where to start searching upwards the tree
:param db_session:
:return: resource-tree root service as a resource object
"""
if resource is not None:
if resource.parent_id is None:
return resource
parent_resource = ResourceService.by_resource_id(resource.parent_id, db_session=db_session)
return get_resource_root_service(parent_resource, db_session=db_session)
return None
[docs]def create_resource(resource_name, resource_display_name, resource_type, parent_id, db_session):
# type: (Str, Optional[Str], Str, int, Session) -> HTTPException
ax.verify_param(resource_name, paramName=u"resource_name", notNone=True, notEmpty=True, httpError=HTTPBadRequest,
msgOnFail="Invalid 'resource_name' specified for child resource creation.")
ax.verify_param(resource_type, paramName=u"resource_type", notNone=True, notEmpty=True, httpError=HTTPBadRequest,
msgOnFail="Invalid 'resource_type' specified for child resource creation.")
ax.verify_param(parent_id, paramName=u"parent_id", notNone=True, notEmpty=True, paramCompare=int, ofType=True,
httpError=HTTPBadRequest, msgOnFail="Invalid 'parent_id' specified for child resource creation.")
parent_resource = ax.evaluate_call(lambda: ResourceService.by_resource_id(parent_id, db_session=db_session),
fallback=lambda: db_session.rollback(), httpError=HTTPNotFound,
msgOnFail=s.Resources_POST_NotFoundResponseSchema.description,
content={u"parent_id": str(parent_id), u"resource_name": str(resource_name),
u"resource_type": str(resource_type)})
# verify for valid permissions from top-level service-specific corresponding resources permissions
root_service = check_valid_service_resource(parent_resource, resource_type, db_session)
new_resource = models.resource_factory(resource_type=resource_type,
resource_name=resource_name,
resource_display_name=resource_display_name or resource_name,
root_service_id=root_service.resource_id,
parent_id=parent_resource.resource_id)
# Two resources with the same parent can't have the same name !
tree_struct = models.resource_tree_service.from_parent_deeper(parent_id, limit_depth=1, db_session=db_session)
tree_struct_dict = models.resource_tree_service.build_subtree_strut(tree_struct)
direct_children = tree_struct_dict[u"children"]
ax.verify_param(resource_name, paramName=u"resource_name", notIn=True, httpError=HTTPConflict,
msgOnFail=s.Resources_POST_ConflictResponseSchema.description,
paramCompare=[child_dict[u"node"].resource_name for child_dict in direct_children.values()])
def add_resource_in_tree(new_res, db):
db_session.add(new_res)
total_children = models.resource_tree_service.count_children(new_res.parent_id, db_session=db)
models.resource_tree_service.set_position(resource_id=new_res.resource_id,
to_position=total_children, db_session=db)
ax.evaluate_call(lambda: add_resource_in_tree(new_resource, db_session),
fallback=lambda: db_session.rollback(),
httpError=HTTPForbidden, msgOnFail=s.Resources_POST_ForbiddenResponseSchema.description)
return ax.valid_http(httpSuccess=HTTPCreated, detail=s.Resources_POST_CreatedResponseSchema.description,
content={u"resource": format_resource(new_resource, basic_info=True)})
[docs]def delete_resource(request):
resource = ar.get_resource_matchdict_checked(request)
service_push = asbool(ar.get_multiformat_post(request, "service_push"))
res_content = {u"resource": format_resource(resource, basic_info=True)}
ax.evaluate_call(
lambda: models.resource_tree_service.delete_branch(resource_id=resource.resource_id, db_session=request.db),
fallback=lambda: request.db.rollback(), httpError=HTTPForbidden,
msgOnFail="Delete resource branch from tree service failed.", content=res_content
)
def remove_service_magpie_and_phoenix(res, svc_push, db):
if res.resource_type != "service":
svc_push = False
db.delete(res)
if svc_push:
sync_services_phoenix(db.query(models.Service))
ax.evaluate_call(lambda: remove_service_magpie_and_phoenix(resource, service_push, request.db),
fallback=lambda: request.db.rollback(), httpError=HTTPForbidden,
msgOnFail=s.Resource_DELETE_ForbiddenResponseSchema.description, content=res_content)
return ax.valid_http(httpSuccess=HTTPOk, detail=s.Resource_DELETE_OkResponseSchema.description)