Skip to content

Commit

Permalink
add POST/DELETE PermissionSet request support
Browse files Browse the repository at this point in the history
  • Loading branch information
fmigneault committed Sep 24, 2020
1 parent b10cacb commit e1339b7
Show file tree
Hide file tree
Showing 12 changed files with 419 additions and 140 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ Features / Changes
*explicit* string representations (see above) combined with the older *implicit* representation still returned
in ``permission_names`` field for backward compatibility
(note: ``DENY`` elements are only represented as *explicit* as there was no such *implicit* permissions before).
* Add ``DELETE`` request views with ``permission`` object provided in body to allow deletion using ``PermissionSet``
JSON representation instead of literal string by path variable.
Still support ``permission_name`` path variable requests for backward compatibility for equivalent names.
* Add ``POST`` request support of ``permission`` JSON representation of ``PermissionSet`` provided in request body.
Fallback to ``permission_name`` field for backward compatibility if equivalent ``permission`` is not found.
* | Upgrade migration script is added to convert existing implicit names to new explicit permission names.
|
| **WARNING**:
Expand Down
14 changes: 13 additions & 1 deletion config/permissions.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,25 @@
# to process all '.cfg' files found under it one-by-one as separate 'permissions' configurations.
#
# Parameters:
# -----------
# service: service name to receive the permission (directly on it if no 'resource' mentioned, must exist)
# resource (optional): tree path of the service's resource (ex: /res1/sub-res2/sub-sub-res3)
# user and/or group: user/group for which to apply the permission (create if missing, see below)
# permission: name of the permission to be applied (see 'magpie/permissions.py' for supported values)
# permission: name or object of the permission to be applied (see 'magpie.permissions' for supported values)
# action: one of [create, remove] (default: create)
#
# Permission:
# -----------
# When provided as string name, it is better to provide the explicit format "[name]-[access]-[scope]" to ensure
# correct interpretation, although implicit permission string is supported. Object definition is also possible:
#
# permission:
# name: name of the permission as allowed for the service/resource (e.g.: read, write, etc.)
# access: access rule for the permission (e.g.: allow/deny)
# scope: scope of permission, for tree inheritance or explicitly for resource (e.g.: recursive/match)
#
# Default behaviour:
# ------------------
# - create missing resources if supported by the service (and tree automatically resolvable), then apply permissions.
# - create missing user/group if required (default user created: (group: anonymous, password: 12345).
# - applicable service, user or group is missing, corresponding permissions are ignored and not updated.
Expand Down
21 changes: 10 additions & 11 deletions magpie/api/management/group/group_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,28 +109,27 @@ def create_group(group_name, description, discoverable, db_session):


def create_group_resource_permission_response(group, resource, permission, db_session):
# type: (models.Group, ServiceOrResourceType, Permission, Session) -> HTTPException
# type: (models.Group, ServiceOrResourceType, PermissionSet, Session) -> HTTPException
"""
Creates a permission on a group/resource combination if it is permitted and not conflicting.
:returns: valid HTTP response on successful operations.
:raises HTTPException: error HTTP response of corresponding situation.
"""
resource_id = resource.resource_id
check_valid_service_or_resource_permission(permission.value, resource, db_session)
perm_content = {"permission_name": str(permission.value),
check_valid_service_or_resource_permission(permission.name, resource, db_session)
perm_content = {"permission_name": str(permission), "permission": permission.json(),
"resource": format_resource(resource, basic_info=True),
"group": format_group(group, basic_info=True)}
existing_perm = ax.evaluate_call(
lambda: GroupResourcePermissionService.get(group.id, resource_id, permission.value, db_session=db_session),
lambda: GroupResourcePermissionService.get(group.id, resource_id, str(permission), db_session=db_session),
fallback=lambda: db_session.rollback(), http_error=HTTPForbidden,
msg_on_fail=s.GroupResourcePermissions_POST_ForbiddenGetResponseSchema.description, content=perm_content
)
ax.verify_param(existing_perm, is_none=True, http_error=HTTPConflict, content=perm_content,
msg_on_fail=s.GroupResourcePermissions_POST_ConflictResponseSchema.description)
new_perm = ax.evaluate_call(
lambda: models.GroupResourcePermission(
resource_id=resource_id, group_id=group.id, perm_name=permission.value), # noqa
lambda: models.GroupResourcePermission(resource_id=resource_id, group_id=group.id, perm_name=str(permission)),
fallback=lambda: db_session.rollback(), http_error=HTTPForbidden, content=perm_content,
msg_on_fail=s.GroupResourcePermissions_POST_ForbiddenCreateResponseSchema.description)
ax.evaluate_call(lambda: db_session.add(new_perm), fallback=lambda: db_session.rollback(),
Expand Down Expand Up @@ -193,20 +192,20 @@ def get_grp_res_perms(grp, res, db):


def delete_group_resource_permission_response(group, resource, permission, db_session):
# type: (models.Group, ServiceOrResourceType, Permission, Session) -> HTTPException
# type: (models.Group, ServiceOrResourceType, PermissionSet, Session) -> HTTPException
"""
Get validated response on deleted group resource permission.
:returns: valid HTTP response on successful operations.
:raises HTTPException: error HTTP response of corresponding situation.
"""
resource_id = resource.resource_id
check_valid_service_or_resource_permission(permission.value, resource, db_session)
perm_content = {"permission_name": permission.value,
check_valid_service_or_resource_permission(permission.name, resource, db_session)
perm_content = {"permission_name": str(permission), "permission": permission.json(),
"resource": format_resource(resource, basic_info=True),
"group": format_group(group, basic_info=True)}
del_perm = ax.evaluate_call(
lambda: GroupResourcePermissionService.get(group.id, resource_id, permission.value, db_session=db_session),
lambda: GroupResourcePermissionService.get(group.id, resource_id, str(permission), db_session=db_session),
fallback=lambda: db_session.rollback(), http_error=HTTPForbidden,
msg_on_fail=s.GroupServicePermission_DELETE_ForbiddenGetResponseSchema.description, content=perm_content
)
Expand Down Expand Up @@ -254,7 +253,7 @@ def get_group_services_response(group, db_session):


def get_group_service_permissions(group, service, db_session):
# type: (models.Group, models.Service, Session) -> List[Permission]
# type: (models.Group, models.Service, Session) -> List[PermissionSet]
"""
Get all permissions the group has on a specific service.
"""
Expand Down
44 changes: 35 additions & 9 deletions magpie/api/management/group/group_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,25 @@ def create_group_service_permission_view(request):
return gu.create_group_resource_permission_response(group, service, permission, db_session=request.db)


@s.GroupServicePermissionAPI.delete(schema=s.GroupServicePermission_DELETE_RequestSchema(), tags=[s.GroupsTag],
response_schemas=s.GroupServicePermission_DELETE_responses)
@view_config(route_name=s.GroupServicePermissionAPI.name, request_method="DELETE")
@s.GroupServicePermissionsAPI.delete(schema=s.GroupServicePermissions_DELETE_RequestSchema(), tags=[s.GroupsTag],
response_schemas=s.GroupServicePermissions_DELETE_responses)
@view_config(route_name=s.GroupServicePermissionsAPI.name, request_method="DELETE")
def delete_group_service_permission_view(request):
"""
Delete a permission from a specific service for a group.
Delete a permission from a specific resource for a group.
"""
group = ar.get_group_matchdict_checked(request)
service = ar.get_service_matchdict_checked(request)
permission = ar.get_permission_multiformat_body_checked(request, service)
return gu.delete_group_resource_permission_response(group, service, permission, db_session=request.db)


@s.GroupServicePermissionAPI.delete(schema=s.GroupServicePermissionName_DELETE_RequestSchema(), tags=[s.GroupsTag],
response_schemas=s.GroupServicePermissionName_DELETE_responses)
@view_config(route_name=s.GroupServicePermissionAPI.name, request_method="DELETE")
def delete_group_service_permission_name_view(request):
"""
Delete a permission by name from a specific service for a group.
"""
group = ar.get_group_matchdict_checked(request)
service = ar.get_service_matchdict_checked(request)
Expand Down Expand Up @@ -200,7 +213,7 @@ def get_group_resource_permissions_view(request):
@s.GroupResourcePermissionsAPI.post(schema=s.GroupResourcePermissions_POST_RequestSchema(), tags=[s.GroupsTag],
response_schemas=s.GroupResourcePermissions_POST_responses)
@view_config(route_name=s.GroupResourcePermissionsAPI.name, request_method="POST")
def create_group_resource_permission_view(request):
def create_group_resource_permissions_view(request):
"""
Create a permission on a specific resource for a group.
"""
Expand All @@ -210,15 +223,28 @@ def create_group_resource_permission_view(request):
return gu.create_group_resource_permission_response(group, resource, permission, db_session=request.db)


@s.GroupResourcePermissionAPI.delete(schema=s.GroupResourcePermission_DELETE_RequestSchema(), tags=[s.GroupsTag],
response_schemas=s.GroupResourcePermission_DELETE_responses)
@view_config(route_name=s.GroupResourcePermissionAPI.name, request_method="DELETE")
def delete_group_resource_permission_view(request):
@s.GroupResourcePermissionsAPI.delete(schema=s.GroupResourcePermissions_DELETE_RequestSchema(), tags=[s.GroupsTag],
response_schemas=s.GroupResourcePermissions_DELETE_responses)
@view_config(route_name=s.GroupResourcePermissionsAPI.name, request_method="DELETE")
def delete_group_resource_permissions_view(request):
"""
Delete a permission from a specific resource for a group.
"""
group = ar.get_group_matchdict_checked(request)
resource = ar.get_resource_matchdict_checked(request)
permission = ar.get_permission_multiformat_body_checked(request, resource)
return gu.create_group_resource_permission_response(group, resource, permission, db_session=request.db)


@s.GroupResourcePermissionAPI.delete(schema=s.GroupResourcePermissionName_DELETE_RequestSchema(), tags=[s.GroupsTag],
response_schemas=s.GroupResourcePermissionName_DELETE_responses)
@view_config(route_name=s.GroupResourcePermissionAPI.name, request_method="DELETE")
def delete_group_resource_permission_name_view(request):
"""
Delete a permission by name from a specific resource for a group.
"""
group = ar.get_group_matchdict_checked(request)
resource = ar.get_resource_matchdict_checked(request)
permission = ar.get_permission_matchdict_checked(request, resource)
return gu.delete_group_resource_permission_response(group, resource, permission, db_session=request.db)

Expand Down
5 changes: 3 additions & 2 deletions magpie/api/management/resource/resource_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

if TYPE_CHECKING:
# pylint: disable=W0611,unused-import
from typing import List, Optional, Tuple, Type
from typing import List, Optional, Tuple, Type, Union

from pyramid.httpexceptions import HTTPException
from pyramid.request import Request
Expand All @@ -35,14 +35,15 @@


def check_valid_service_or_resource_permission(permission_name, service_or_resource, db_session):
# type: (Str, ServiceOrResourceType, Session) -> Optional[Permission]
# type: (Union[Str, Permission], 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
:raises HTTPBadRequest: if the permission is not valid for the targeted service/resource
"""
svc_res_permissions = get_resource_permissions(service_or_resource, db_session=db_session)
svc_res_type = service_or_resource.resource_type
Expand Down
6 changes: 3 additions & 3 deletions magpie/api/management/service/service_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
from sqlalchemy.orm.session import Session

from magpie.models import Resource, Service
from magpie.permissions import Permission
from magpie.permissions import PermissionSet
from magpie.services import ServiceInterface
from magpie.typedefs import JSON, ResourcePermissionMap


def format_service(service, permissions=None, permission_type=PermissionType.ALLOWED,
show_private_url=False, show_resources_allowed=False):
# type: (Service, Optional[List[Permission]], Optional[PermissionType], bool, bool) -> JSON
# type: (Service, Optional[List[PermissionSet]], Optional[PermissionType], bool, bool) -> JSON
"""
Formats the ``service`` information into JSON.
Expand Down Expand Up @@ -61,7 +61,7 @@ def fmt_svc(svc, perms):

def format_service_resources(service, # type: Service
db_session, # type: Session
service_perms=None, # type: Optional[List[Permission]]
service_perms=None, # type: Optional[List[PermissionSet]]
resources_perms_dict=None, # type: Optional[ResourcePermissionMap]
show_all_children=False, # type: bool
show_private_url=True, # type: bool
Expand Down
29 changes: 15 additions & 14 deletions magpie/api/management/user/user_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,29 +118,29 @@ def _add_to_group(usr, grp):


def create_user_resource_permission_response(user, resource, permission, db_session):
# type: (models.User, ServiceOrResourceType, Permission, Session) -> HTTPException
# type: (models.User, ServiceOrResourceType, PermissionSet, Session) -> HTTPException
"""
Creates a permission on a user/resource combination if it is permitted and not conflicting.
:returns: valid HTTP response on successful operation.
"""
ru.check_valid_service_or_resource_permission(permission.value, resource, db_session)
ru.check_valid_service_or_resource_permission(permission.name, resource, db_session)
res_id = resource.resource_id
existing_perm = UserResourcePermissionService.by_resource_user_and_perm(
user_id=user.id, resource_id=res_id, perm_name=permission.value, db_session=db_session)
ax.verify_param(existing_perm, is_none=True, with_param=False, http_error=HTTPConflict,
content={"resource_id": res_id, "user_id": user.id, "permission_name": permission.value},
user_id=user.id, resource_id=res_id, perm_name=str(permission), db_session=db_session)
err_content = {"resource_id": res_id, "user_id": user.id,
"permission_name": str(permission), "permission": permission.json()}
ax.verify_param(existing_perm, is_none=True, with_param=False, http_error=HTTPConflict, content=err_content,
msg_on_fail=s.UserResourcePermissions_POST_ConflictResponseSchema.description)

new_perm = models.UserResourcePermission(resource_id=res_id, user_id=user.id, perm_name=permission.value) # noqa
usr_res_data = {"resource_id": res_id, "user_id": user.id, "permission_name": permission.value}
ax.verify_param(new_perm, not_none=True, http_error=HTTPForbidden,
content={"resource_id": res_id, "user_id": user.id},
msg_on_fail=s.UserResourcePermissions_POST_ForbiddenResponseSchema.description)
ax.evaluate_call(lambda: db_session.add(new_perm), fallback=lambda: db_session.rollback(),
http_error=HTTPForbidden, content=usr_res_data,
http_error=HTTPForbidden, content=err_content,
msg_on_fail=s.UserResourcePermissions_POST_ForbiddenResponseSchema.description)
return ax.valid_http(http_success=HTTPCreated, content=usr_res_data,
return ax.valid_http(http_success=HTTPCreated, content=err_content,
detail=s.UserResourcePermissions_POST_CreatedResponseSchema.description)


Expand All @@ -167,21 +167,22 @@ def del_usr_grp(usr, grp):


def delete_user_resource_permission_response(user, resource, permission, db_session):
# type: (models.User, ServiceOrResourceType, Permission, Session) -> HTTPException
# type: (models.User, ServiceOrResourceType, PermissionSet, Session) -> HTTPException
"""
Get validated response on deleted user resource permission.
:returns: valid HTTP response on successful operations.
:raises HTTPException: error HTTP response of corresponding situation.
"""
ru.check_valid_service_or_resource_permission(permission.value, resource, db_session)
ru.check_valid_service_or_resource_permission(permission.name, resource, db_session)
res_id = resource.resource_id
del_perm = UserResourcePermissionService.get(user.id, res_id, permission.value, db_session)
del_perm = UserResourcePermissionService.get(user.id, res_id, str(permission), db_session)
ax.evaluate_call(lambda: db_session.delete(del_perm), fallback=lambda: db_session.rollback(),
http_error=HTTPNotFound,
msg_on_fail=s.UserResourcePermissions_DELETE_NotFoundResponseSchema.description,
content={"resource_id": res_id, "user_id": user.id, "permission_name": permission.value})
return ax.valid_http(http_success=HTTPOk, detail=s.UserResourcePermissions_DELETE_OkResponseSchema.description)
msg_on_fail=s.UserResourcePermissionName_DELETE_NotFoundResponseSchema.description,
content={"resource_id": res_id, "user_id": user.id,
"permission_name": str(permission), "permission": permission.json()})
return ax.valid_http(http_success=HTTPOk, detail=s.UserResourcePermissionName_DELETE_OkResponseSchema.description)


def filter_user_permission(resource_permission_list, user):
Expand Down
Loading

0 comments on commit e1339b7

Please sign in to comment.