Ejemplo n.º 1
0
    def test_permission_convert_from_json(self):
        """
        Conversion by JSON definition.

        Validate various implicit conversion of permission JSON definition.
        """
        utils.warn_version(__meta__.__version__,
                           "permission set conversion",
                           "3.0",
                           skip=True)

        perm = PermissionSet({
            "name": Permission.WRITE,
            "access": Access.DENY,
            "scope": Scope.MATCH
        })
        utils.check_val_equal(perm.name, Permission.WRITE)
        utils.check_val_equal(perm.access, Access.DENY)
        utils.check_val_equal(perm.scope, Scope.MATCH)
        perm = PermissionSet({
            "name": Permission.WRITE.value,
            "access": Access.DENY.value,
            "scope": Scope.MATCH.value
        })
        utils.check_val_equal(perm.name, Permission.WRITE)
        utils.check_val_equal(perm.access, Access.DENY)
        utils.check_val_equal(perm.scope, Scope.MATCH)
Ejemplo n.º 2
0
    def test_permission_convert_from_acl(self):
        """
        Conversion from Pyramid ACL definition.

        Validate various implicit conversion of permission elements to explicit definition.
        """
        utils.warn_version(__meta__.__version__,
                           "permission set conversion",
                           "3.0",
                           skip=True)

        perm = PermissionSet((Allow, 1, "write-deny-match"))
        utils.check_val_equal(perm.name, Permission.WRITE)
        utils.check_val_equal(
            perm.access,
            Access.DENY,
            msg="Explicit permission name prioritized over ACL access")
        utils.check_val_equal(perm.scope, Scope.MATCH)
        utils.check_val_equal(perm.type,
                              PermissionType.DIRECT,
                              msg="Should infer direct from user ID.")
        perm = PermissionSet((Deny, "group:1", "read"))
        utils.check_val_equal(perm.name, Permission.READ)
        utils.check_val_equal(perm.access, Access.DENY)
        utils.check_val_equal(perm.scope, Scope.RECURSIVE)
        utils.check_val_equal(
            perm.type,
            PermissionType.INHERITED,
            msg="Should infer inherited from group specifier.")
        utils.check_raises(
            lambda: PermissionSet((Allow, "group:1", ALL_PERMISSIONS)),
            TypeError,
            msg=
            "Don't allow any object that makes the permission name not explicitly defined."
        )
Ejemplo n.º 3
0
    def test_permission_convert_from_string(self):
        """
        Conversion by implicit/explicit name.

        Validate various implicit conversion of permission name string to explicit definition.
        """
        utils.warn_version(__meta__.__version__,
                           "permission set conversion",
                           "3.0",
                           skip=True)

        perm = PermissionSet("read")  # old implicit format
        utils.check_val_equal(perm.name, Permission.READ)
        utils.check_val_equal(perm.access, Access.ALLOW)
        utils.check_val_equal(perm.scope, Scope.RECURSIVE)
        perm = PermissionSet("read-match")  # old format
        utils.check_val_equal(perm.name, Permission.READ)
        utils.check_val_equal(perm.access, Access.ALLOW)
        utils.check_val_equal(perm.scope, Scope.MATCH)
        perm = PermissionSet("read-allow-match")  # new explicit format
        utils.check_val_equal(perm.name, Permission.READ)
        utils.check_val_equal(perm.access, Access.ALLOW)
        utils.check_val_equal(perm.scope, Scope.MATCH)
        perm = PermissionSet("read-allow-recursive")
        utils.check_val_equal(perm.name, Permission.READ)
        utils.check_val_equal(perm.access, Access.ALLOW)
        utils.check_val_equal(perm.scope, Scope.RECURSIVE)
        perm = PermissionSet("read-deny-match")
        utils.check_val_equal(perm.name, Permission.READ)
        utils.check_val_equal(perm.access, Access.DENY)
        utils.check_val_equal(perm.scope, Scope.MATCH)
Ejemplo n.º 4
0
 def get_grp_res_perm(grp, db, res_ids, res_types):
     res_perms_tup = GroupService.resources_with_possible_perms(
         grp, resource_ids=res_ids, resource_types=res_types, db_session=db)
     res_perms_dict = {}
     for res_perm in res_perms_tup:
         if res_perm.resource.resource_id not in res_perms_dict:
             res_perms_dict[res_perm.resource.resource_id] = [
                 PermissionSet(res_perm)
             ]
         else:
             res_perms_dict[res_perm.resource.resource_id].append(
                 PermissionSet(res_perm))
     return res_perms_dict
Ejemplo n.º 5
0
def resolve_user_group_permissions(resource_permission_list):
    # type: (List[ResolvablePermissionType]) -> Iterable[PermissionSet]
    """
    Reduces overlapping user :term:`Inherited Permissions` for corresponding resources/services amongst the given list.

    User :term:`Direct Permissions` have the top-most priority and are therefore selected first if permissions are
    found for corresponding resource. In such case, only one entry is possible (it is invalid to have more than one
    combination of ``(User, Resource, Permission)``, including modifiers, as per validation during their creation).

    Otherwise, for corresponding :term:`Inherited Permissions`, resolve the prioritized permission across every group.
    Similarly to users, :func:`magpie.groups.group_utils.get_similar_group_resource_permission` validate that only one
    combination of ``(Group, Resource, Permission)`` can exist including permission modifiers. Only, cross-group
    memberships for a given resource must then be computed.

    Priority of combined *group-only* permissions follows 3 conditions:
        1. Permissions inherited from special group :py:data:`MAGPIE_ANONYMOUS_GROUP` have lower priority than any
           other more explicit group membership, regardless of permission modifiers applied on it.
        2. Permissions of same group priority with :attr:`Access.DENY` are prioritized over :attr:`Access.ALLOW`.
        3. Permissions of same group priority with :attr:`Scope.RECURSIVE` are prioritized over :attr:`Access.MATCH` as
           they affect a larger range of resources when :term:`Effective Permissions` are eventually requested.

    .. note::
        Resource tree inherited resolution is not considered here (no recursive :term:`Effective Permissions` computed).
        Only same-level scope of every given resource is processed independently. The intended behaviour here is
        therefore to help illustrate in responses *how deep* is a given permission going to have an impact onto
        lower-level resources, making :attr:`Scope.RECURSIVE` more important than specific instance :attr:`Scope.MATCH`.

    .. seealso::
        - Sorting methods of :class:`magpie.permissions.PermissionSet` that orders the permissions with desired result.
        - :func:`magpie.groups.group_utils.get_similar_group_resource_permission`
        - :func:`magpie.users.user_utils.get_similar_user_resource_permission`
    """
    # convert all first to avoid re-doing it each iteration for comparisons
    res_perm_sets = [PermissionSet(perm) for perm in resource_permission_list]

    # quickly return if there are no conflict to resolve
    res_perms = [(perm.perm_tuple.resource.resource_id, perm.name) for perm in res_perm_sets]
    if len(set(res_perms)) == len(res_perms):
        return res_perm_sets

    # combine overlapping resource/permission
    combo_perms = {}
    for perm in res_perm_sets:
        res_id = perm.perm_tuple.resource.resource_id
        perm_key = (res_id, perm.name)
        prev_perm = combo_perms.get(perm_key)
        if not prev_perm:
            combo_perms[perm_key] = perm
            continue
        combo_perms[perm_key] = PermissionSet.resolve(perm, prev_perm)
    return list(combo_perms.values())
Ejemplo n.º 6
0
def upgrade():
    context = get_context()
    session = Session(bind=op.get_bind())
    if not isinstance(context.connection.engine.dialect, PGDialect):
        return

    grp_res_perms = GroupResourcePermissionService.base_query(
        db_session=session)
    usr_res_perms = UserResourcePermissionService.base_query(
        db_session=session)

    for perm_list in [grp_res_perms, usr_res_perms]:
        perm_map = [(perm, PermissionSet(perm.perm_name))
                    for perm in perm_list]
        perm_rm = set()
        perm_keep = set()
        # find any user/group-resource that has both recursive and match permissions simultaneously
        # this is not allowed anymore, so remove the match access that is redundant anyway
        for perm_db, perm_set in perm_map:
            perm_dup = False
            for other_db, other_set in perm_map:
                if perm_set is other_set:
                    continue
                if perm_db.resource_id == other_db.resource_id and perm_set.like(
                        other_set):
                    if perm_set.scope == Scope.RECURSIVE:
                        perm_keep.add(perm_db)
                        perm_rm.add(other_db)
                    else:
                        perm_keep.add(other_db)
                        perm_rm.add(perm_db)
                    perm_dup = True
                    break
            if not perm_dup:
                perm_keep.add(perm_db)

        # apply changes
        for perm in perm_keep:
            perm_name_raw = perm.perm_name
            perm_scope = Scope.RECURSIVE
            if perm_name_raw.endswith("-" + Scope.MATCH.value):
                perm_name_raw = perm_name_raw.rsplit("-", 1)[0]
                perm_scope = Scope.MATCH
            perm.perm_name = str(PermissionSet(perm_name_raw,
                                               scope=perm_scope))
        for perm in perm_rm:
            session.delete(perm)

    session.commit()
Ejemplo n.º 7
0
 def is_similar_permission():
     perms_dict = get_group_resources_permissions_dict(
         group, resource_ids=[res_id], db_session=db_session)
     perms_list = perms_dict.get(res_id, [])
     return [
         PermissionSet(perm) for perm in perms_list if perm.like(permission)
     ]
Ejemplo n.º 8
0
def get_permission_multiformat_body_checked(request, service_or_resource):
    # type: (Request, ServiceOrResourceType) -> PermissionSet
    """
    Retrieves the permission from the body and validates that it is allowed for the specified `service` or `resource`.

    Validation combines basic field checks followed by contextual values applicable for the `service` or `resource`.
    The permission can be provided either by literal string name (explicit or implicit format) or JSON object.

    .. seealso::
        - :func:`get_value_multiformat_body_checked`
    """
    # import here to avoid circular import error with undefined functions between (api_request, resource_utils)
    from magpie.api.management.resource.resource_utils import check_valid_service_or_resource_permission

    perm_key = "permission"
    permission = get_multiformat_body(request, perm_key)
    if not permission:
        perm_key = "permission_name"
        permission = get_multiformat_body(request, perm_key)
    if isinstance(permission, six.string_types):
        check_value(permission, perm_key)
    elif isinstance(permission, dict) and len(permission):
        for perm_sub_key, perm_sub_val in permission.items():
            if perm_sub_val is not None:
                check_value(perm_sub_val, "{}.{}".format(perm_key, perm_sub_key))
    else:
        ax.raise_http(http_error=HTTPBadRequest, content={perm_key: str(permission)},
                      detail=s.Permission_Check_BadRequestResponseSchema.description)
    perm = ax.evaluate_call(lambda: PermissionSet(permission),
                            http_error=HTTPUnprocessableEntity, content={perm_key: str(permission)},
                            msg_on_fail=s.UnprocessableEntityResponseSchema.description)
    check_valid_service_or_resource_permission(perm.name, service_or_resource, request.db)
    return perm
Ejemplo n.º 9
0
 def get_grp_res_perms(grp, res, db):
     if res.owner_group_id == grp.id:
         # FIXME: no 'magpie.models.Resource.permissions' - ok for now because no owner handling...
         return models.RESOURCE_TYPE_DICT[res.type].permissions
     perms = db.query(models.GroupResourcePermission) \
               .filter(models.GroupResourcePermission.resource_id == res.resource_id) \
               .filter(models.GroupResourcePermission.group_id == grp.id)
     return [PermissionSet(p, typ=PermissionType.APPLIED) for p in perms]
Ejemplo n.º 10
0
    def test_permission_convert_from_tuple(self):
        """
        Conversion from Ziggurat :class:`PermissionTuple` object.

        Validate various implicit conversion of permission elements to explicit definition.
        """
        utils.warn_version(__meta__.__version__,
                           "permission set conversion",
                           "3.0",
                           skip=True)

        perm = PermissionSet(
            PermissionTuple(
                "user-name",
                "write-deny-match",
                "user",  # important: perm-name & type
                "group_id",
                "resource_id",
                "owner",
                "allowed"))  # these doesn't matter
        utils.check_val_equal(perm.name, Permission.WRITE)
        utils.check_val_equal(perm.access, Access.DENY)
        utils.check_val_equal(perm.scope, Scope.MATCH)
        utils.check_val_equal(
            perm.type,
            PermissionType.DIRECT,
            msg="PermissionTuple should also help identify type")
        perm = PermissionSet(
            PermissionTuple(
                "user-name",
                "write-deny-match",
                "group",  # important: perm-name & type
                "group_id",
                "resource_id",
                "owner",
                "allowed"))  # these doesn't matter
        utils.check_val_equal(perm.name, Permission.WRITE)
        utils.check_val_equal(perm.access, Access.DENY)
        utils.check_val_equal(perm.scope, Scope.MATCH)
        utils.check_val_equal(
            perm.type,
            PermissionType.INHERITED,
            msg="PermissionTuple should also help identify type")
Ejemplo n.º 11
0
 def get_grp_svc_perms():
     if service.owner_group_id == group.id:
         perm_type = PermissionType.OWNED
         perms = SERVICE_TYPE_DICT[service.type].permissions
     else:
         perm_type = PermissionType.APPLIED
         perms = db_session.query(models.GroupResourcePermission) \
                           .filter(models.GroupResourcePermission.resource_id == service.resource_id) \
                           .filter(models.GroupResourcePermission.group_id == group.id)
     return [PermissionSet(perm, typ=perm_type) for perm in perms]
Ejemplo n.º 12
0
    def test_permission_convert_from_enum(self):
        """
        Conversion by enum values.

        Validate various implicit conversion of permission name string to explicit definition.
        """
        utils.warn_version(__meta__.__version__,
                           "permission set conversion",
                           "3.0",
                           skip=True)

        perm = PermissionSet(Permission.READ)
        utils.check_val_equal(perm.name, Permission.READ)
        utils.check_val_equal(perm.access, Access.ALLOW)
        utils.check_val_equal(perm.scope, Scope.RECURSIVE)
        perm = PermissionSet(Permission.WRITE, access="deny")
        utils.check_val_equal(perm.name, Permission.WRITE)
        utils.check_val_equal(perm.access, Access.DENY)
        utils.check_val_equal(perm.scope, Scope.RECURSIVE)
Ejemplo n.º 13
0
def get_user_service_permissions(user, service, request,
                                 inherit_groups_permissions=True, resolve_groups_permissions=False):
    # type: (models.User, models.Service, Request, bool, bool) -> List[PermissionSet]
    if service.owner_user_id == user.id:
        perm_type = PermissionType.OWNED
        usr_svc_perms = service_factory(service, request).permissions
    else:
        if inherit_groups_permissions or resolve_groups_permissions:
            perm_type = PermissionType.INHERITED
            usr_svc_perms = ResourceService.perms_for_user(service, user, db_session=request.db)
        else:
            perm_type = PermissionType.DIRECT
            usr_svc_perms = ResourceService.direct_perms_for_user(service, user, db_session=request.db)
    return [PermissionSet(p, typ=perm_type) for p in usr_svc_perms]
Ejemplo n.º 14
0
def get_permission_matchdict_checked(request, service_or_resource):
    # type: (Request, models.Resource) -> PermissionSet
    """
    Obtains the permission specified in the request path variable and validates that :paramref:`service_or_resource`
    allows it.

    The :paramref:`service_or_resource` can be top-level `service` or a children `resource`.

    Allowed permissions correspond to the *direct* `service` permissions or restrained permissions of the `resource`
    under its root `service`. The permission name can be provided either by implicit or explicit string representation.

    :returns: found permission name if valid for the service/resource
    """
    # pylint: disable=C0415  # avoid circular import
    from magpie.api.management.resource.resource_utils import check_valid_service_or_resource_permission
    perm_name = get_value_matchdict_checked(request, "permission_name")
    perm = ax.evaluate_call(lambda: PermissionSet(perm_name), http_error=HTTPUnprocessableEntity,
                            content={"permission_name": str(perm_name)},
                            msg_on_fail=s.UnprocessableEntityResponseSchema.description)
    check_valid_service_or_resource_permission(perm.name, service_or_resource, request.db)
    return perm
Ejemplo n.º 15
0
def regroup_permissions_by_resource(resource_permissions, resolve=False):
    # type: (Iterable[ResolvablePermissionType], bool) -> ResourcePermissionMap
    """
    Regroups multiple uncategorized permissions into a dictionary of corresponding resource IDs.

    While regrouping the various permissions (both :term:`Direct Permissions` and any amount of groups
    :term:`Inherited Permissions`) under their respective resource by ID, optionally resolve overlapping or conflicting
    permissions by name such that only one permission persists for that resource and name.

    .. seealso::
        :func:`resolve_user_group_permissions`

    :param resource_permissions:
        List of resource permissions to process.
        Can include both user :term:`Direct Permissions` and its groups :term:`Inherited Permissions`.
    :param resolve:
        When ``False``, only mapping by resource ID is accomplished. Full listing of permissions is returned.
        Otherwise, resolves the corresponding resource permissions (by same ID) considering various priority rules to
        obtain unique permission names per resource.
    :return: resolved permission
    """

    # regroup by resource
    resources_permissions_dict = {}
    for res_perm in resource_permissions:
        res_perm = PermissionSet(res_perm)
        res_id = res_perm.perm_tuple.resource.resource_id
        if res_id not in resources_permissions_dict:
            resources_permissions_dict[res_id] = [res_perm]
        else:
            resources_permissions_dict[res_id].append(res_perm)

    # remove any duplicates that could be incorporated by multiple groups as requested
    if resolve:
        for res_id in resources_permissions_dict:
            resources_permissions_dict[res_id] = resolve_user_group_permissions(resources_permissions_dict[res_id])
    return resources_permissions_dict
Ejemplo n.º 16
0
def downgrade():
    context = get_context()
    session = Session(bind=op.get_bind())
    if not isinstance(context.connection.engine.dialect, PGDialect):
        return

    # two following lines avoids double "DELETE" erroneous call (ignore duplicate)
    # https://stackoverflow.com/questions/28824401
    context.connection.engine.dialect.supports_sane_rowcount = False
    context.connection.engine.dialect.supports_sane_multi_rowcount = False

    grp_res_perms = GroupResourcePermissionService.base_query(
        db_session=session)
    usr_res_perms = UserResourcePermissionService.base_query(
        db_session=session)

    for perm_list in [grp_res_perms, usr_res_perms]:
        for perm in perm_list:
            perm_set = PermissionSet(perm.perm_name)
            if perm_set.implicit_permission is None:
                session.delete(perm)
            else:
                perm.perm_name = perm_set.implicit_permission
    session.commit()
Ejemplo n.º 17
0
    def test_format_permissions_allowed(self):
        """
        Validate that formats are also respected, but with additional auto-expansion of all *modifier* combinations on
        permission names when requesting :attr:`PermissionType.ALLOWED` permissions.

        .. seealso::
            :meth:`test_format_permissions_applied`
        """
        utils.warn_version(__meta__.__version__,
                           "permission format validation",
                           "3.0",
                           skip=True)

        # add duplicates with extra modifiers only to test removal
        # provide in random order to validate proper sorting
        # use multiple permission implementation to validate they are still handled
        test_perms = [
            PermissionSet(Permission.READ, Access.DENY, Scope.RECURSIVE),
            PermissionSet(Permission.READ, Access.DENY, Scope.MATCH),
            PermissionSet(Permission.READ, Access.ALLOW, Scope.RECURSIVE),
            PermissionSet(Permission.READ, Access.ALLOW, Scope.MATCH),
            Permission.READ,
            "write-match",  # old implicit format
            Permission.WRITE,
        ]
        format_perms = format_permissions(test_perms, PermissionType.ALLOWED)
        expect_names = [
            Permission.READ.value + "-" + Scope.MATCH.value,
            str(PermissionSet(Permission.READ, Access.ALLOW, Scope.MATCH)),
            Permission.READ.value,
            str(PermissionSet(Permission.READ, Access.ALLOW, Scope.RECURSIVE)),
            # no implicit name for denied
            str(PermissionSet(Permission.READ, Access.DENY, Scope.MATCH)),
            str(PermissionSet(Permission.READ, Access.DENY, Scope.RECURSIVE)),
            Permission.WRITE.value + "-" + Scope.MATCH.value,
            str(PermissionSet(Permission.WRITE, Access.ALLOW, Scope.MATCH)),
            Permission.WRITE.value,
            str(PermissionSet(Permission.WRITE, Access.ALLOW,
                              Scope.RECURSIVE)),
            # no implicit name for denied
            str(PermissionSet(Permission.WRITE, Access.DENY, Scope.MATCH)),
            str(PermissionSet(Permission.WRITE, Access.DENY, Scope.RECURSIVE)),
        ]
        expect_perms = [
            PermissionSet(Permission.READ, Access.ALLOW, Scope.MATCH,
                          PermissionType.ALLOWED).json(),
            PermissionSet(Permission.READ, Access.ALLOW, Scope.RECURSIVE,
                          PermissionType.ALLOWED).json(),
            PermissionSet(Permission.READ, Access.DENY, Scope.MATCH,
                          PermissionType.ALLOWED).json(),
            PermissionSet(Permission.READ, Access.DENY, Scope.RECURSIVE,
                          PermissionType.ALLOWED).json(),
            PermissionSet(Permission.WRITE, Access.ALLOW, Scope.MATCH,
                          PermissionType.ALLOWED).json(),
            PermissionSet(Permission.WRITE, Access.ALLOW, Scope.RECURSIVE,
                          PermissionType.ALLOWED).json(),
            PermissionSet(Permission.WRITE, Access.DENY, Scope.MATCH,
                          PermissionType.ALLOWED).json(),
            PermissionSet(Permission.WRITE, Access.DENY, Scope.RECURSIVE,
                          PermissionType.ALLOWED).json(),
        ]
        utils.check_all_equal(format_perms["permission_names"],
                              expect_names,
                              any_order=False)
        utils.check_all_equal(format_perms["permissions"],
                              expect_perms,
                              any_order=False)
Ejemplo n.º 18
0
    def test_permission_resolve_raised_invalid(self):
        """
        Ensure that resolution with completely invalid/impossible cases are raised instead of failing silently.
        """
        perm_r = PermissionSet(Permission.READ, Access.ALLOW, Scope.RECURSIVE,
                               PermissionType.DIRECT)
        res1 = MockObject(resource_name="mock-resource1", resource_id=987)
        res2 = MockObject(resource_name="mock-resource2", resource_id=101)
        usr = MockObject(user_name="mock-user", user_id=123)
        grp = MockObject(group_name="mock-group", user_id=456)
        usr_tup = PermissionTuple(usr, perm_r.explicit_permission, "user",
                                  None, res1, False, True)
        usr_perm = PermissionSet(usr_tup)

        # two same user permissions of identical name cannot exist
        utils.check_raises(
            lambda: PermissionSet.resolve(usr_perm, usr_perm),
            ValueError,
            msg=
            "Should raise permissions corresponding by name and user, cannot exist simultaneously."
        )

        # various values that are valid to create 'PermissionSet', but insufficient for resolution
        valid_only_for_repr = [
            Permission.READ.value,
            Permission.READ.value + "-" + Scope.MATCH.value,
            Permission.READ.value + "-" + Access.ALLOW.value + "-" +
            Scope.MATCH.value,
            {
                "name": Permission.READ,
                "access": Access.DENY,
                "scope": Scope.MATCH
            },
            PermissionSet(Permission.READ.value, Access.ALLOW.value,
                          Scope.MATCH.value)  # missing 'perm_tuple'
        ]
        for perm1, perm2 in itertools.permutations(valid_only_for_repr, 2):
            utils.check_raises(
                lambda: PermissionSet.resolve(perm1, perm2),
                ValueError,
                msg=
                "Permission that do not provide comparison elements for resolution must be raised, "
                "although they are normally valid for simple permission representation."
            )

        # valid user and group tuples (both provide tuples, point to same resource, and don't refer to same user-perm)
        # but mismatching permission names
        perm_w = PermissionSet(Permission.WRITE, Access.ALLOW, Scope.RECURSIVE,
                               PermissionType.DIRECT)
        grp_tup = PermissionTuple(usr, perm_w.explicit_permission, "group",
                                  grp, res1, False, True)
        grp_perm = PermissionSet(grp_tup)
        utils.check_raises(
            lambda: PermissionSet.resolve(usr_perm, grp_perm),
            ValueError,
            msg=
            "Mismatching permission names should be raised as they cannot be resolved together."
        )

        # same perm name (read), both tuple provided, not both user-perms, but not referring to same resource
        grp_tup = PermissionTuple(usr, perm_r.explicit_permission, "group",
                                  grp, res2, False, True)
        grp_perm = PermissionSet(grp_tup)
        utils.check_raises(
            lambda: PermissionSet.resolve(usr_perm, grp_perm),
            ValueError,
            msg=
            "Mismatching resources should be raised as they cannot be resolved together."
        )
        utils.check_no_raise(
            lambda: PermissionSet.resolve(
                usr_perm, grp_perm, context=PermissionType.EFFECTIVE),
            msg=
            "Mismatching resources should resolved when requested explicitly (effective)."
        )
Ejemplo n.º 19
0
    def test_compare_and_sort_operations(self):
        perm_ram = PermissionSet(Permission.READ, Access.ALLOW, Scope.MATCH)
        perm_rar = PermissionSet(Permission.READ, Access.ALLOW,
                                 Scope.RECURSIVE)
        perm_rdm = PermissionSet(Permission.READ, Access.DENY, Scope.MATCH)
        perm_rdr = PermissionSet(Permission.READ, Access.DENY, Scope.RECURSIVE)
        perm_wam = PermissionSet(Permission.WRITE, Access.ALLOW, Scope.MATCH)
        perm_war = PermissionSet(Permission.WRITE, Access.ALLOW,
                                 Scope.RECURSIVE)
        perm_wdm = PermissionSet(Permission.WRITE, Access.DENY, Scope.MATCH)
        perm_wdr = PermissionSet(Permission.WRITE, Access.DENY,
                                 Scope.RECURSIVE)
        perm_sorted = [
            perm_ram, perm_rar, perm_rdm, perm_rdr, perm_wam, perm_war,
            perm_wdm, perm_wdr
        ]
        perm_random = [
            perm_rdr, perm_wam, perm_wdm, perm_rar, perm_war, perm_ram,
            perm_wdr, perm_rdm
        ]

        utils.check_val_equal(
            perm_rar,
            Permission.READ,
            msg="Should be equal because of defaults and name conversion")
        utils.check_val_not_equal(perm_rar,
                                  Permission.WRITE,
                                  msg="Not equal because of mismatch name")
        utils.check_val_equal(
            perm_rar,
            perm_rar.json(),
            msg="Pre-convert any known representation before equal check")

        utils.check_all_equal(list(sorted(perm_random)),
                              perm_sorted,
                              any_order=False)

        # cannot sort elements with other type representations although 'PermissionSet' can, because they don't know it
        # (ie: str.__lt__ and dict.__ls__ could be called with 'other' being 'PermissionSet' depending on random order)
        # can still do the individual compares though if the reference object is 'PermissionSet' and other is anything
        utils.check_val_equal(perm_sorted[0] < perm_sorted[1], True)
        utils.check_val_equal(perm_sorted[0] < perm_sorted[1].json(), True)
        utils.check_val_equal(
            perm_sorted[0] < perm_sorted[1].implicit_permission, True)
        utils.check_val_equal(
            perm_sorted[0] < perm_sorted[1].explicit_permission, True)
        utils.check_val_equal(perm_sorted[0] > perm_sorted[1], False)
        utils.check_val_equal(perm_sorted[0] > perm_sorted[1].json(), False)
        utils.check_val_equal(
            perm_sorted[0] > perm_sorted[1].implicit_permission, False)
        utils.check_val_equal(
            perm_sorted[0] > perm_sorted[1].explicit_permission, False)

        # validate similarity comparison with inplace conversion at the same time
        for perm_1, perm_2 in itertools.product(perm_sorted[0:4],
                                                perm_sorted[0:4]):
            utils.check_val_equal(perm_1.like(perm_2.json()),
                                  True,
                                  msg="Expected {!r} ~ {!r}".format(
                                      perm_1, perm_2))
        for perm_1, perm_2 in itertools.product(perm_sorted[0:4],
                                                perm_sorted[4:8]):
            utils.check_val_equal(perm_1.like(perm_2.json()),
                                  False,
                                  msg="Expected {!r} !~ {!r}".format(
                                      perm_1, perm_2))
Ejemplo n.º 20
0
    def test_register_permissions_missing_group_create_new_entries(self):
        utils.TestSetup.create_TestService(
            self,
            override_service_name=self.test_perm_svc_name,
            override_service_type=ServiceAPI.service_type)
        utils.TestSetup.delete_TestGroup(
            self, override_group_name=self.test_perm_grp_name)
        session = get_db_session_from_settings(self.app.app.registry.settings)

        svc_perm = "read-match"  # using string for backward compatibility
        res1_perm = Permission.READ
        res2_perm = Permission.WRITE
        res3_perm = PermissionSet(Permission.READ, Access.DENY,
                                  Scope.RECURSIVE)
        res4_perm = PermissionSet(Permission.WRITE, Access.DENY, Scope.MATCH)
        res1_name = "test-resource"
        res2_name = "sub-test-resource"
        res3_name = "test-resource-json-fields"
        res4_name = "test-resource-str-explicit"
        perm_config = {
            "permissions": [{
                "service": self.test_perm_svc_name,
                "permission": svc_perm,
                "action": "create",
                "group": self.test_perm_grp_name,
            }, {
                "service": self.test_perm_svc_name,
                "resource": res1_name,
                "permission": res1_perm.value,
                "action": "create",
                "group": self.test_perm_grp_name,
            }, {
                "service": self.test_perm_svc_name,
                "resource": res1_name + "/" + res2_name,
                "permission": res2_perm.value,
                "action": "create",
                "group": self.test_perm_grp_name,
            }, {
                "service": self.test_perm_svc_name,
                "resource": res3_name,
                "permission": res3_perm.json(),
                "action": "create",
                "group": self.test_perm_grp_name,
            }, {
                "service": self.test_perm_svc_name,
                "resource": res4_name,
                "permission": str(res4_perm),
                "action": "create",
                "group": self.test_perm_grp_name,
            }]
        }
        utils.check_no_raise(
            lambda: register.magpie_register_permissions_from_config(
                perm_config, db_session=session))

        # check that all service, resources, group are created correctly
        services = utils.TestSetup.get_RegisteredServicesList(self)
        utils.check_val_is_in(self.test_perm_svc_name,
                              [s["service_name"] for s in services])
        groups = utils.TestSetup.get_RegisteredGroupsList(self)
        utils.check_val_is_in(self.test_perm_grp_name, groups)
        resp = utils.test_request(
            self.app, "GET",
            "/services/{}/resources".format(self.test_perm_svc_name))
        body = utils.check_response_basic_info(resp)
        svc_res = body[self.test_perm_svc_name]["resources"]  # type: JSON
        svc_res_id = body[self.test_perm_svc_name]["resource_id"]
        utils.check_val_is_in(res1_name,
                              [svc_res[r]["resource_name"] for r in svc_res])
        res1_id = [
            svc_res[r]["resource_id"] for r in svc_res
            if svc_res[r]["resource_name"] == res1_name
        ][0]
        res3_id = [
            svc_res[r]["resource_id"] for r in svc_res
            if svc_res[r]["resource_name"] == res3_name
        ][0]
        res4_id = [
            svc_res[r]["resource_id"] for r in svc_res
            if svc_res[r]["resource_name"] == res4_name
        ][0]
        res1_sub = svc_res[str(res1_id)]["children"]  # type: JSON
        utils.check_val_is_in(res2_name,
                              [res1_sub[r]["resource_name"] for r in res1_sub])
        res2_id = [
            res1_sub[r]["resource_id"] for r in res1_sub
            if res1_sub[r]["resource_name"] == res2_name
        ][0]

        # check that all permissions are created correctly
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, svc_res_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in(svc_perm, body["permission_names"])
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res1_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in(res1_perm.value, body["permission_names"])
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res2_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in(res2_perm.value, body["permission_names"])
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res3_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in(str(res3_perm), body["permission_names"])
        path = "/groups/{}/resources/{}/permissions".format(
            self.test_perm_grp_name, res4_id)
        resp = utils.test_request(self.app, "GET", path)
        body = utils.check_response_basic_info(resp)
        utils.check_val_is_in(str(res4_perm), body["permission_names"])
Ejemplo n.º 21
0
    def test_format_permissions_applied(self):
        """
        Validate that provided permission sets are formatted as intended, with both implicit and explicit variants, and
        with both name strings and detailed JSON objects.
        """
        utils.warn_version(__meta__.__version__,
                           "permission format validation",
                           "3.0",
                           skip=True)

        usr_perm = models.UserPermission()
        usr_perm.perm_name = Permission.GET_FEATURE.value
        grp_perm = models.GroupPermission()
        grp_perm.perm_name = "write-match"  # using string for backward compatibility
        dup_perm = Permission.READ.value  # only one should remain in result
        dup_usr_perm = models.UserPermission()
        dup_usr_perm.perm_name = dup_perm  # also only one remains although different type
        only_perm = Permission.GET_CAPABILITIES
        deny_match_perm = PermissionSet(Permission.GET_LEGEND_GRAPHIC,
                                        Access.DENY, Scope.MATCH)
        deny_str_perm = Permission.GET_MAP.value + "-" + Access.DENY.value
        deny_recur_perm = PermissionSet(Permission.GET_METADATA, Access.DENY,
                                        Scope.RECURSIVE)
        # purposely use a random order to test sorting simultaneously to duplicate removal
        any_perms = [
            deny_match_perm, dup_perm, only_perm, usr_perm, dup_usr_perm,
            grp_perm, deny_str_perm, deny_recur_perm, deny_recur_perm
        ]

        perm_type = PermissionType.DIRECT  # anything else than 'allowed' to only get 'applied' permissions
        format_perms = format_permissions(any_perms, perm_type)
        expect_names = [
            # both implicit/explicit variants added for backward compatibility and new format for each applicable case
            only_perm.value,
            str(PermissionSet(only_perm, Access.ALLOW, Scope.RECURSIVE)),
            usr_perm.perm_name,
            str(PermissionSet(usr_perm.perm_name)),
            # deny only have explicit representation
            str(deny_match_perm),
            str(PermissionSet(deny_str_perm, Access.DENY, Scope.RECURSIVE)),
            str(
                PermissionSet(deny_recur_perm.name, deny_recur_perm.access,
                              Scope.RECURSIVE)),
            dup_perm,  # only one, other not present
            str(PermissionSet(dup_perm, Access.ALLOW, Scope.RECURSIVE)),
            grp_perm.perm_name,
            str(PermissionSet(grp_perm.perm_name, Access.ALLOW, Scope.MATCH)),
        ]
        expect_perms = [
            PermissionSet(Permission.GET_CAPABILITIES, Access.ALLOW,
                          Scope.RECURSIVE, perm_type).json(),
            PermissionSet(Permission.GET_FEATURE, Access.ALLOW,
                          Scope.RECURSIVE, perm_type).json(),
            PermissionSet(Permission.GET_LEGEND_GRAPHIC, Access.DENY,
                          Scope.MATCH, perm_type).json(),
            PermissionSet(Permission.GET_MAP, Access.DENY, Scope.RECURSIVE,
                          perm_type).json(),
            PermissionSet(Permission.GET_METADATA, Access.DENY,
                          Scope.RECURSIVE, perm_type).json(),
            PermissionSet(Permission.READ, Access.ALLOW, Scope.RECURSIVE,
                          perm_type).json(),
            PermissionSet(Permission.WRITE, Access.ALLOW, Scope.MATCH,
                          perm_type).json(),
        ]
        utils.check_all_equal(format_perms["permission_names"],
                              expect_names,
                              any_order=False)
        utils.check_all_equal(format_perms["permissions"],
                              expect_perms,
                              any_order=False)
Ejemplo n.º 22
0
 def is_similar_permission():
     perms_list = ResourceService.direct_perms_for_user(resource, user, db_session=db_session)
     perms_list = [PermissionSet(perm) for perm in perms_list]
     return [perm for perm in perms_list if perm.like(permission)]
Ejemplo n.º 23
0
    def effective_permissions(self,
                              user,
                              resource,
                              permissions=None,
                              allow_match=True):
        # type: (models.User, ServiceOrResourceType, Optional[Collection[Permission]], bool) -> List[PermissionSet]
        """
        Obtains the effective permissions the user has over the specified resource.

        Recursively rewinds the resource tree from the specified resource up to the top-most parent service the resource
        resides under (or directly if the resource is the service) and retrieve permissions along the way that should be
        applied to children when using scoped-resource inheritance. Rewinding of the tree can terminate earlier when
        permissions can be immediately resolved such as when more restrictive conditions enforce denied access.

        Both user and group permission inheritance is resolved simultaneously to tree hierarchy with corresponding
        allow and deny conditions. User :term:`Direct Permissions` have priority over all its groups
        :term:`Inherited Permissions`, and denied permissions have priority over allowed access ones.

        All applicable permissions on the resource (as defined by :meth:`allowed_permissions`) will have their
        resolution (Allow/Deny) provided as output, unless a specific subset of permissions is requested using
        :paramref:`permissions`. Other permissions are ignored in this case to only resolve requested ones.
        For example, this parameter can be used to request only ACL resolution from specific permissions applicable
        for a given request, as obtained by :meth:`permission_requested`.

        Permissions scoped as `match` can be ignored using :paramref:`allow_match`, such as when the targeted resource
        does not exist.

        .. seealso::
            - :meth:`ServiceInterface.resource_requested`
        """
        if not permissions:
            permissions = self.allowed_permissions(resource)
        requested_perms = set(permissions)  # type: Set[Permission]
        effective_perms = dict()  # type: Dict[Permission, PermissionSet]

        # immediately return all permissions if user is an admin
        db_session = self.request.db
        admin_group = get_constant("MAGPIE_ADMIN_GROUP", self.request)
        admin_group = GroupService.by_group_name(admin_group,
                                                 db_session=db_session)
        if admin_group in user.groups:  # noqa
            return [
                PermissionSet(perm,
                              access=Access.ALLOW,
                              scope=Scope.MATCH,
                              typ=PermissionType.EFFECTIVE,
                              reason=PERMISSION_REASON_ADMIN)
                for perm in permissions
            ]

        # level at which last permission was found, -1 if not found
        # employed to resolve with *closest* scope and for applicable 'reason' combination on same level
        effective_level = dict()  # type: Dict[Permission, Optional[int]]
        current_level = 1  # one-based to avoid ``if level:`` check failing with zero
        full_break = False
        # current and parent resource(s) recursive-scope
        while resource is not None and not full_break:  # bottom-up until service is reached

            # include both permissions set in database as well as defined directly on resource
            cur_res_perms = ResourceService.perms_for_user(
                resource, user, db_session=db_session)
            cur_res_perms.extend(permission_to_pyramid_acls(resource.__acl__))

            for perm_name in requested_perms:
                if full_break:
                    break
                for perm_tup in cur_res_perms:
                    perm_set = PermissionSet(perm_tup)

                    # if user is owner (directly or via groups), all permissions are set,
                    # but continue processing this resource until end in case user explicit deny reverts it
                    if perm_tup.perm_name == ALL_PERMISSIONS:
                        # FIXME:
                        #   This block needs to be validated if support of ownership rules are added.
                        #   Conditions must be revised according to wanted behaviour...
                        #   General idea for now is that explict user/group deny should be prioritized over resource
                        #   ownership permissions since these can be attributed to *any user* while explicit deny are
                        #   definitely set by an admin-level user.
                        for perm in requested_perms:
                            if perm_set.access == Access.DENY:
                                all_perm = PermissionSet(
                                    perm, perm_set.access, perm.scope,
                                    PermissionType.OWNED)
                                effective_perms[perm] = all_perm
                            else:
                                all_perm = PermissionSet(
                                    perm, perm_set.access, perm.scope,
                                    PermissionType.OWNED)
                                effective_perms.setdefault(perm, all_perm)
                        full_break = True
                        break
                    # skip if the current permission must not be processed (at all or for the moment until next 'name')
                    if perm_set.name not in requested_perms or perm_set.name != perm_name:
                        continue
                    # only first resource can use match (if even enabled with found one), parents are recursive-only
                    if not allow_match and perm_set.scope == Scope.MATCH:
                        continue
                    # pick the first permission if none was found up to this point
                    prev_perm = effective_perms.get(perm_name)
                    scope_level = effective_level.get(perm_name)
                    if not prev_perm:
                        effective_perms[perm_name] = perm_set
                        effective_level[perm_name] = current_level
                        continue

                    # user direct permissions have priority over inherited ones from groups
                    # if inherited permission was found during previous iteration, override it with direct permission
                    if perm_set.type == PermissionType.DIRECT:
                        # - reset resolution scope of previous permission attributed to group as it takes precedence
                        # - since there can't be more than one user permission-name per resource on a given level,
                        #   scope resolution is done after applying this *closest* permission, ignore higher level ones
                        if prev_perm.type == PermissionType.INHERITED or not scope_level:
                            effective_perms[perm_name] = perm_set
                            effective_level[perm_name] = current_level
                        continue  # final decision for this user, skip any group permissions

                    # resolve prioritized permission according to ALLOW/DENY, scope and group priority
                    # (see 'PermissionSet.resolve' method for extensive details)
                    # skip if last permission is not on group to avoid redundant USER > GROUP check processed before
                    if prev_perm.type == PermissionType.INHERITED:
                        # - If new permission to process is done against the previous permission from *same* tree-level,
                        #   there is a possibility to combine equal priority groups. In such case, reason is 'MULTIPLE'.
                        # - If not of equal priority, the appropriate permission is selected and reason is overridden
                        #   accordingly by the new higher priority permission.
                        # - If no permission was defined at all (first occurrence), also set it using current permission
                        if scope_level in [None, current_level]:
                            resolved_perm = PermissionSet.resolve(
                                perm_set,
                                prev_perm,
                                context=PermissionType.EFFECTIVE)
                            effective_perms[perm_name] = resolved_perm
                            effective_level[perm_name] = current_level
                        # - If new permission is at *different* tree-level, it applies only if the group has higher
                        #   priority than the previous one, to respect the *closest* scope to the target resource.
                        #   Same priorities are ignored as they were already resolved by *closest* scope above.
                        # - Reset scope level with new permission such that another permission of same group priority as
                        #   that could be processed in next iteration can be compared against it, to resolve 'access'
                        #   priority between them.
                        elif perm_set.group_priority > prev_perm.group_priority:
                            effective_perms[perm_name] = perm_set
                            effective_level[perm_name] = current_level

            # don't bother moving to parent if everything is resolved already
            #   can only assume nothing left to resolve if all permissions are direct on user (highest priority)
            #   if any found permission is group inherited, higher level user permission could still override it
            if (len(effective_perms) == len(requested_perms)
                    and all(perm.type == PermissionType.DIRECT
                            for perm in effective_perms.values())):
                break
            # otherwise, move to parent if any available, since we are not done rewinding the resource tree
            allow_match = False  # reset match not applicable anymore for following parent resources
            current_level += 1
            if resource.parent_id:
                resource = ResourceService.by_resource_id(
                    resource.parent_id, db_session=db_session)
            else:
                resource = None

        # set deny for all still unresolved permissions from requested ones
        resolved_perms = set(effective_perms)
        missing_perms = set(permissions) - resolved_perms
        final_perms = set(effective_perms.values())  # type: Set[PermissionSet]
        for perm_name in missing_perms:
            perm = PermissionSet(perm_name,
                                 access=Access.DENY,
                                 scope=Scope.MATCH,
                                 typ=PermissionType.EFFECTIVE,
                                 reason=PERMISSION_REASON_DEFAULT)
            final_perms.add(perm)
        # enforce type and scope (use MATCH to make it explicit that it applies specifically for this resource)
        for perm in final_perms:
            perm.type = PermissionType.EFFECTIVE
            perm.scope = Scope.MATCH
        return list(final_perms)
Ejemplo n.º 24
0
    def test_EditUser_ApplyPermissions(self):
        """
        Verifies that UI button operations are working for the following workflow:
            0. Goto Edit User page.
            1. Change ``service-type`` tab to display services of type :class:`ServiceAPI`.
            2. Set new permissions onto an existing resources and submit them with ``Apply`` button.
            3. Verify the permissions are selected and displayed on page reload.
            4. Remove and modify permission from existing resource and submit.
            5. Validate that changes are reflected.

        Note:
            Only implemented locally with form submission of ``TestApp``.
        """
        utils.warn_version(self,
                           "update permission modifiers with option select",
                           "3.0",
                           skip=True)

        # make sure any sub-resource are all deleted to avoid conflict, then recreate service to add sub-resource
        utils.TestSetup.delete_TestService(self)
        body = utils.TestSetup.create_TestService(self)
        info = utils.TestSetup.get_ResourceInfo(self, override_body=body)
        svc_id, svc_name = info["resource_id"], info["service_name"]
        res_name = "resource1"
        sub_name = "resource2"
        body = utils.TestSetup.create_TestResource(
            self, parent_resource_id=svc_id, override_resource_name=res_name)
        info = utils.TestSetup.get_ResourceInfo(self, override_body=body)
        res_id = info["resource_id"]
        body = utils.TestSetup.create_TestResource(
            self, parent_resource_id=res_id, override_resource_name=sub_name)
        info = utils.TestSetup.get_ResourceInfo(self, override_body=body)
        sub_id = info["resource_id"]
        utils.TestSetup.create_TestGroup(self)
        utils.TestSetup.create_TestUser(self)

        # utilities for later tests
        def to_ui_permission(permission):
            # type: (Union[Str, PermissionSet]) -> Str
            return permission.explicit_permission if isinstance(
                permission, PermissionSet) else ""

        def check_ui_resource_permissions(perm_form, resource_id, permissions):
            select_res_id = "permission_resource_{}".format(resource_id)
            select_options = perm_form.fields[
                select_res_id]  # contains multiple select, one per applicable permission
            utils.check_val_equal(
                len(select_options),
                2,
                msg="Always 2 select combobox expected (read and write).")
            select_values = [select.value for select in select_options]
            if not permissions:
                utils.check_all_equal(
                    ["", ""],
                    select_values,
                    msg="When no permission is selected, values are empty")
                return
            # value must match exactly the *explicit* permission representation
            for r_perm in permissions:
                utils.check_val_is_in(r_perm, select_values)

        def check_api_resource_permissions(resource_permissions):
            for _r_id, _r_perms in resource_permissions:
                urp_path = "/users/{}/resources/{}/permissions".format(
                    self.test_user_name, _r_id)
                urp_resp = utils.test_request(self, "GET", urp_path)
                urp_body = utils.check_response_basic_info(urp_resp)
                ur_perms = [
                    perm.json() for perm in _r_perms
                    if isinstance(perm, PermissionSet)
                ]
                for perm in ur_perms:
                    perm["type"] = PermissionType.DIRECT.value
                permissions = urp_body["permissions"]
                for perm in permissions:
                    perm.pop("reason",
                             None)  # >= 3.4, don't care for this test
                utils.check_all_equal(permissions, ur_perms, any_order=True)

        # 0. goto user edit page (default first service selected)
        path = "/ui/users/{}/default".format(self.test_user_name)
        resp = utils.test_request(self, "GET", path)
        # 1. change to 'api' service-type, validate created test resources are all displayed in resource tree
        resp = resp.click(self.test_service_type
                          )  # tabs are '<a href=...>{service-type}</a>'
        body = utils.check_ui_response_basic_info(resp)
        text = body.replace("\n", "").replace("  ",
                                              "")  # ignore HTML formatting
        tree_keys = re.findall(r"<div class=\"tree-key\">(.*?)</div>", text)
        for r_name in [svc_name, res_name, sub_name]:
            utils.check_val_is_in(
                r_name,
                tree_keys,
                msg="Resource '{}' expected to be listed in tree.".format(
                    r_name))

        # 2. apply new permissions
        # 2.1 validate all initial values of permissions are empty
        res_form_name = "resources_permissions"  # form that wraps the displayed resource tree
        res_form_submit = "edit_permissions"  # id of the Apply 'submit' input of the form
        res_perm_form = resp.forms[res_form_name]
        for r_id in [svc_id, res_id, sub_id]:
            check_ui_resource_permissions(res_perm_form, r_id, [])
        # 2.2 apply new
        # NOTE: because tree-view is created by reversed order of permissions, we must provide them as [WRITE, READ]
        svc_perm1 = PermissionSet(Permission.READ, Access.ALLOW,
                                  Scope.RECURSIVE)
        svc_perms = ["", svc_perm1]
        res_perm1 = PermissionSet(Permission.READ, Access.DENY,
                                  Scope.RECURSIVE)
        res_perm2 = PermissionSet(Permission.WRITE, Access.ALLOW,
                                  Scope.RECURSIVE)
        res_perms = [res_perm2, res_perm1]
        sub_perm1 = PermissionSet(Permission.READ, Access.ALLOW, Scope.MATCH)
        sub_perms = ["", sub_perm1]
        data = {
            # set value for following correspond to 'select' option that was chosen
            "permission_resource_{}".format(svc_id):
            [to_ui_permission(perm) for perm in svc_perms],
            "permission_resource_{}".format(res_id):
            [to_ui_permission(perm) for perm in res_perms],
            "permission_resource_{}".format(sub_id):
            [to_ui_permission(perm) for perm in sub_perms],
        }
        resp = utils.TestSetup.check_FormSubmit(self,
                                                previous_response=resp,
                                                form_match=res_perm_form,
                                                form_submit=res_form_submit,
                                                form_data=data)

        # 3. validate result
        # 3.1 validate displayed UI permissions
        res_perm_form = resp.forms[res_form_name]
        check_ui_resource_permissions(
            res_perm_form, svc_id,
            [to_ui_permission(perm) for perm in svc_perms])
        check_ui_resource_permissions(
            res_perm_form, res_id,
            [to_ui_permission(perm) for perm in res_perms])
        check_ui_resource_permissions(
            res_perm_form, sub_id,
            [to_ui_permission(perm) for perm in sub_perms])
        # 3.2 validate applied using API (to make sure that changes are not only reflected in UI)
        check_api_resource_permissions([(svc_id, svc_perms),
                                        (res_id, res_perms),
                                        (sub_id, sub_perms)])

        # 4. remove permission: this is equivalent to re-apply the ones we want to keep without the one to remove
        #    modify permission: this is detected by combo of (res-id, perm-name) with different (access, scope)
        svc_perms_mod = ["",
                         ""]  # remove the previous (READ, ALLOW, RECURSIVE)
        res_perm1_mod = PermissionSet(Permission.READ, Access.ALLOW,
                                      Scope.RECURSIVE)  # DENY -> ALLOW
        res_perms_mod = [res_perm2,
                         res_perm1_mod]  # second WRITE permission is unchanged
        sub_perms_mod = sub_perms  # all unchanged for this resource
        data = {
            "permission_resource_{}".format(svc_id):
            [to_ui_permission(perm) for perm in svc_perms_mod],
            "permission_resource_{}".format(res_id):
            [to_ui_permission(perm) for perm in res_perms_mod],
            "permission_resource_{}".format(sub_id):
            [to_ui_permission(perm) for perm in sub_perms_mod],
        }
        resp = utils.TestSetup.check_FormSubmit(self,
                                                previous_response=resp,
                                                form_match=res_perm_form,
                                                form_submit=res_form_submit,
                                                form_data=data)

        # 5. validate applied permissions modifications
        res_perm_form = resp.forms[res_form_name]
        check_ui_resource_permissions(
            res_perm_form, svc_id,
            [to_ui_permission(perm) for perm in svc_perms_mod])
        check_ui_resource_permissions(
            res_perm_form, res_id,
            [to_ui_permission(perm) for perm in res_perms_mod])
        check_ui_resource_permissions(
            res_perm_form, sub_id,
            [to_ui_permission(perm) for perm in sub_perms_mod])
        check_api_resource_permissions([(svc_id, svc_perms_mod),
                                        (res_id, res_perms_mod),
                                        (sub_id, sub_perms_mod)])