Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User 'effective' resource permissions query #135

Merged
merged 4 commits into from
Dec 6, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ History
* add service resource auto-sync feature
* return user/group services if any sub-resource has permissions
* add inherited resource permission with querystring (deprecate `inherited_<>` routes warnings)
* add flag to return `effective` permissions from user resource permissions requests
* hide service private URL on non administrator level requests
* fix external providers login support (validated for DKRZ, GitHub and WSO2)
* make cookies expire-able by setting `MAGPIE_COOKIE_EXPIRE` and provide cookie only on http (JS CSRF attack protection)
Expand Down Expand Up @@ -64,7 +65,7 @@ History
* ncWMS support for getmap, getcapabilities, getmetadata on thredds resource
* ncWMS2 added to default providers
* add geoserverwms
* remove load balanced mallefowl and catalog
* remove load balanced Malleefowl and Catalog
* push service provider updates to phoenix on service modification or initial setup with getcapabilities for anonymous
* major update of `Magpie REST API 0.2.x documentation`_ to match returned codes/messages from 0.2.0 changes
* normalise additional HTTP request responses omitted from 0.2.0 (404, 500, and other missed responses)
Expand Down
2 changes: 1 addition & 1 deletion magpie/__meta__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
General meta information on the magpie package.
"""

__version__ = '0.7.11'
__version__ = '0.7.12'
__author__ = "Francois-Xavier Derue, Francis Charette-Migneault"
__maintainer__ = "Francis Charette-Migneault"
__email__ = 'francis.charette-migneault@crim.ca'
Expand Down
2 changes: 1 addition & 1 deletion magpie/adapter/magpieprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ def is_visible_by_user(self, ems_processes_id, process_id, request):
return False

# use inherited flag to consider both user and group permissions on the resource
path = '{host}/users/{usr}/resources/{res}/permissions?inherit=true' \
path = '{host}/users/{usr}/resources/{res}/permissions?inherit=true&effective=true' \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a comment also on the effective flag...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in below commits

.format(host=self.magpie_url, usr=self.magpie_current, res=process_res_id)
resp = requests.get(path, cookies=request.cookies, headers=self.json_headers, verify=self.twitcher_ssl_verify)
resp.raise_for_status()
Expand Down
11 changes: 8 additions & 3 deletions magpie/api/api_rest_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,17 @@ class HeaderRequestSchema(colander.MappingSchema):
content_type.name = 'Content-Type'


QueryEffectivePermissions = colander.SchemaNode(
colander.Boolean(), default=False, missing=colander.drop,
description="User groups effective permissions resolved with corresponding service inheritance functionality. "
"(Note: group inheritance is enforced regardless of any `inherit` flag).")
QueryInheritGroupsPermissions = colander.SchemaNode(
colander.Boolean(), default=False, missing=colander.drop,
description='User groups memberships inheritance to resolve service resource permissions.')
description="User groups memberships inheritance to resolve service resource permissions.")
QueryCascadeResourcesPermissions = colander.SchemaNode(
colander.Boolean(), default=False, missing=colander.drop,
description='Display any service that has at least one sub-resource user permission, '
'or only services that have user permissions directly set on them.', )
description="Display any service that has at least one sub-resource user permission, "
"or only services that have user permissions directly set on them.", )


class BaseResponseBodySchema(colander.MappingSchema):
Expand Down Expand Up @@ -1467,6 +1471,7 @@ class UserResources_GET_NotFoundResponseSchema(colander.MappingSchema):

class UserResourcePermissions_GET_QuerySchema(colander.MappingSchema):
inherit = QueryInheritGroupsPermissions
effective = QueryEffectivePermissions


class UserResourcePermissions_GET_RequestSchema(colander.MappingSchema):
Expand Down
78 changes: 54 additions & 24 deletions magpie/api/management/user/user_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from magpie.api.management.resource.resource_utils import check_valid_service_resource_permission
from magpie.api.management.user.user_formats import *
from magpie.definitions.ziggurat_definitions import *
from magpie.services import service_type_dict
from magpie.definitions.pyramid_definitions import Request
from magpie.services import service_factory, ResourcePermissionType, ServiceI
from magpie import models
from typing import Any, AnyStr, Dict, List, Optional, Union


def create_user(user_name, password, email, group_name, db_session):
Expand Down Expand Up @@ -73,29 +75,51 @@ def delete_user_resource_permission(permission_name, resource, user_id, db_sessi
return valid_http(httpSuccess=HTTPOk, detail=UserResourcePermissions_DELETE_OkResponseSchema.description)


def filter_user_permission(resource_permission_tuple_list, user):
def get_resource_root_service(resource, request):
# type: (models.Resource, Request) -> ServiceI
res_root_svc = ResourceService.by_resource_id(resource.root_service_id, db_session=request.db)
return service_factory(res_root_svc, request)


def filter_user_permission(resource_permission_list, user):
# type: (List[ResourcePermissionType], models.User) -> List[ResourcePermissionType]
return filter(lambda perm: perm.group is None and perm.type == u'user' and perm.user.user_name == user.user_name,
resource_permission_tuple_list)
resource_permission_list)


def get_user_resource_permissions(user, resource, db_session, inherit_groups_permissions=True):
def get_user_resource_permissions(user, resource, request,
inherit_groups_permissions=True, effective_permissions=False):
# type: (models.User, models.Resource, Request, bool, bool) -> List[AnyStr]
"""
Retrieves user resource permissions with or without inherited group permissions.
Alternatively retrieves the effective user resource permissions, where group permissions are implied as `True`.
"""
if resource.owner_user_id == user.id:
permission_names = models.resource_type_dict[resource.type].permission_names
else:
res_perm_tuple_list = ResourceService.perms_for_user(resource, user, db_session=db_session)
if not inherit_groups_permissions:
res_perm_tuple_list = filter_user_permission(res_perm_tuple_list, user)
permission_names = [permission.perm_name for permission in res_perm_tuple_list]
db_session = request.db
if effective_permissions:
svc = get_resource_root_service(resource, request)
res_perm_list = svc.effective_permissions(resource, user)
else:
res_perm_list = ResourceService.perms_for_user(resource, user, db_session=db_session)
if not inherit_groups_permissions:
res_perm_list = filter_user_permission(res_perm_list, user)
permission_names = [permission.perm_name for permission in res_perm_list]
return sorted(set(permission_names)) # remove any duplicates that could be incorporated by multiple groups


def get_user_services(user, db_session, cascade_resources=False,
UserServices = Union[Dict[AnyStr, Dict[AnyStr, Any]], List[Dict[AnyStr, Any]]]


def get_user_services(user, request, cascade_resources=False,
inherit_groups_permissions=False, format_as_list=False):
# type: (models.User, Request, Optional[bool], Optional[bool], Optional[bool]) -> UserServices
"""
Returns services by type with corresponding services by name containing sub-dict information.

:param user: user for which to find services
:param db_session: database session connection
:param request: request with database session connection
:param cascade_resources:
If `False`, return only services with *Direct* user permissions on their corresponding service-resource.
Otherwise, return every service that has at least one sub-resource with user permissions.
Expand All @@ -109,16 +133,17 @@ def get_user_services(user, db_session, cascade_resources=False,
dict of services by type with corresponding services by name containing sub-dict information,
unless `format_as_dict` is `True`
"""
db_session = request.db
resource_type = None if cascade_resources else ['service']
res_perm_dict = get_user_resources_permissions_dict(user, resource_types=resource_type, db_session=db_session,
res_perm_dict = get_user_resources_permissions_dict(user, resource_types=resource_type, request=request,
inherit_groups_permissions=inherit_groups_permissions)

services = {}
for resource_id, perms in res_perm_dict.items():
svc = ResourceService.by_resource_id(resource_id=resource_id, db_session=db_session)
if svc.resource_type != 'service' and cascade_resources:
svc = ResourceService.by_resource_id(resource_id=svc.root_service_id, db_session=db_session)
perms = service_type_dict[svc.type].permission_names
svc = get_resource_root_service(svc, request)
perms = svc.permission_names
if svc.type not in services:
services[svc.type] = {}
if svc.resource_name not in services[svc.type]:
Expand All @@ -134,26 +159,28 @@ def get_user_services(user, db_session, cascade_resources=False,
return services_list


def get_user_service_permissions(user, service, db_session, inherit_groups_permissions=True):
def get_user_service_permissions(user, service, request, inherit_groups_permissions=True):
# type: (models.User, models.Service, Request, Optional[bool]) -> List[AnyStr]
if service.owner_user_id == user.id:
permission_names = service_type_dict[service.type].permission_names
permission_names = service_factory(service, request).permission_names
else:
svc_perm_tuple_list = ResourceService.perms_for_user(service, user, db_session=db_session)
svc_perm_tuple_list = ResourceService.perms_for_user(service, user, db_session=request.db)
if not inherit_groups_permissions:
svc_perm_tuple_list = filter_user_permission(svc_perm_tuple_list, user)
permission_names = [permission.perm_name for permission in svc_perm_tuple_list]
return sorted(set(permission_names)) # remove any duplicates that could be incorporated by multiple groups


def get_user_resources_permissions_dict(user, db_session, resource_types=None,
def get_user_resources_permissions_dict(user, request, resource_types=None,
resource_ids=None, inherit_groups_permissions=True):
# type: (models.User, Request, Optional[List[AnyStr]], Optional[List[int]], Optional[bool]) -> Dict[AnyStr, Any]
"""
Creates a dictionary of resources by id with corresponding permissions of the user.

:param user: user for which to find services
:param db_session: database session connection
:param resource_types: (list) filter the search query with specified resource types
:param resource_ids: (list) filter the search query with specified resource ids
:param request: request with database session connection
:param resource_types: filter the search query with specified resource types
:param resource_ids: filter the search query with specified resource ids
:param inherit_groups_permissions:
If `False`, return only user-specific resource permissions.
Otherwise, resolve inherited permissions using all groups the user is member of.
Expand All @@ -162,7 +189,7 @@ def get_user_resources_permissions_dict(user, db_session, resource_types=None,
verify_param(user, notNone=True, httpError=HTTPNotFound,
msgOnFail=UserResourcePermissions_GET_NotFoundResponseSchema.description)
res_perm_tuple_list = UserService.resources_with_possible_perms(
user, resource_ids=resource_ids, resource_types=resource_types, db_session=db_session)
user, resource_ids=resource_ids, resource_types=resource_types, db_session=request.db)
if not inherit_groups_permissions:
res_perm_tuple_list = filter_user_permission(res_perm_tuple_list, user)
resources_permissions_dict = {}
Expand All @@ -179,15 +206,17 @@ def get_user_resources_permissions_dict(user, db_session, resource_types=None,
return resources_permissions_dict


def get_user_service_resources_permissions_dict(user, service, db_session, inherit_groups_permissions=True):
def get_user_service_resources_permissions_dict(user, service, request, inherit_groups_permissions=True):
# type: (models.User, models.Service, Request, bool) -> Dict[AnyStr, Any]
resources_under_service = models.resource_tree_service.from_parent_deeper(parent_id=service.resource_id,
db_session=db_session)
db_session=request.db)
resource_ids = [resource.Resource.resource_id for resource in resources_under_service]
return get_user_resources_permissions_dict(user, db_session, resource_types=None, resource_ids=resource_ids,
return get_user_resources_permissions_dict(user, request, resource_types=None, resource_ids=resource_ids,
inherit_groups_permissions=inherit_groups_permissions)


def check_user_info(user_name, email, password, group_name):
# type: (Any, Any, Any, Any) -> None
verify_param(user_name, notNone=True, notEmpty=True, httpError=HTTPBadRequest,
paramName=u'user_name', msgOnFail=Users_CheckInfo_Name_BadRequestResponseSchema.description)
verify_param(len(user_name), isIn=True, httpError=HTTPBadRequest,
Expand All @@ -204,6 +233,7 @@ def check_user_info(user_name, email, password, group_name):


def get_user_groups_checked(request, user):
# type: (Request, models.User) -> List[AnyStr]
verify_param(user, notNone=True, httpError=HTTPNotFound,
msgOnFail=Groups_CheckInfo_NotFoundResponseSchema.description)
db = request.db
Expand Down
18 changes: 10 additions & 8 deletions magpie/api/management/user/user_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ def build_json_user_resource_tree(usr):
json_res = {}
for svc in models.Service.all(db_session=db):
svc_perms = get_user_service_permissions(
user=usr, service=svc, db_session=db, inherit_groups_permissions=inherit_groups_perms)
user=usr, service=svc, request=request, inherit_groups_permissions=inherit_groups_perms)
if svc.type not in json_res:
json_res[svc.type] = {}
res_perms_dict = get_user_service_resources_permissions_dict(
user=usr, service=svc, db_session=db, inherit_groups_permissions=inherit_groups_perms)
user=usr, service=svc, request=request, inherit_groups_permissions=inherit_groups_perms)
json_res[svc.type][svc.resource_name] = format_service_resources(
svc,
db_session=db,
Expand Down Expand Up @@ -217,8 +217,10 @@ def get_user_resource_permissions_view(request):
user = get_user_matchdict_checked_or_logged(request)
resource = get_resource_matchdict_checked(request, 'resource_id')
inherit_groups_perms = str2bool(get_query_param(request, 'inherit'))
perm_names = get_user_resource_permissions(resource=resource, user=user, db_session=request.db,
inherit_groups_permissions=inherit_groups_perms)
effective_perms = str2bool(get_query_param(request, 'effective'))
perm_names = get_user_resource_permissions(resource=resource, user=user, request=request,
inherit_groups_permissions=inherit_groups_perms,
effective_permissions=effective_perms)
return valid_http(httpSuccess=HTTPOk, detail=UserResourcePermissions_GET_OkResponseSchema.description,
content={u'permission_names': sorted(perm_names)})

Expand Down Expand Up @@ -275,7 +277,7 @@ def get_user_services_view(request):
inherit_groups_perms = str2bool(get_query_param(request, 'inherit'))
format_as_list = str2bool(get_query_param(request, 'list'))

svc_json = get_user_services(user, db_session=request.db,
svc_json = get_user_services(user, request=request,
cascade_resources=cascade_resources,
inherit_groups_permissions=inherit_groups_perms,
format_as_list=format_as_list)
Expand Down Expand Up @@ -324,7 +326,7 @@ def get_user_service_permissions_view(request):
user = get_user_matchdict_checked_or_logged(request)
service = get_service_matchdict_checked(request)
inherit_groups_perms = str2bool(get_query_param(request, 'inherit'))
perms = evaluate_call(lambda: get_user_service_permissions(service=service, user=user, db_session=request.db,
perms = evaluate_call(lambda: get_user_service_permissions(service=service, user=user, request=request,
inherit_groups_permissions=inherit_groups_perms),
fallback=lambda: request.db.rollback(), httpError=HTTPNotFound,
msgOnFail=UserServicePermissions_GET_NotFoundResponseSchema.description,
Expand Down Expand Up @@ -372,9 +374,9 @@ def get_user_service_resources_view(request):
user = get_user_matchdict_checked_or_logged(request)
service = get_service_matchdict_checked(request)
service_perms = get_user_service_permissions(
user, service, db_session=request.db, inherit_groups_permissions=inherit_groups_perms)
user, service, request=request, inherit_groups_permissions=inherit_groups_perms)
resources_perms_dict = get_user_service_resources_permissions_dict(
user, service, db_session=request.db, inherit_groups_permissions=inherit_groups_perms)
user, service, request=request, inherit_groups_permissions=inherit_groups_perms)
user_svc_res_json = format_service_resources(
service=service,
db_session=request.db,
Expand Down
1 change: 1 addition & 0 deletions magpie/definitions/pyramid_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from pyramid.registry import Registry
from pyramid.settings import asbool
from pyramid.registry import Registry
from pyramid.request import Request
from pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy
from pyramid.response import Response, FileResponse
from pyramid.view import (
Expand Down
Loading