magpie.api.requests =================== .. py:module:: magpie.api.requests Attributes ---------- .. autoapisummary:: magpie.api.requests.CONTENT_TYPE_JSON magpie.api.requests.LOGGER Classes ------- .. autoapisummary:: magpie.api.requests.PermissionSet Functions --------- .. autoapisummary:: magpie.api.requests.get_constant magpie.api.requests.get_logger magpie.api.requests.check_value magpie.api.requests.get_request_method_content magpie.api.requests.get_multiformat_body magpie.api.requests.get_permission_multiformat_body_checked magpie.api.requests.get_value_multiformat_body_checked magpie.api.requests.get_principals magpie.api.requests.has_admin_access magpie.api.requests.get_logged_user magpie.api.requests.get_user magpie.api.requests.get_user_matchdict_checked_or_logged magpie.api.requests.get_user_matchdict_checked magpie.api.requests.get_group_matchdict_checked magpie.api.requests.get_resource_matchdict_checked magpie.api.requests.get_service_matchdict_checked magpie.api.requests.get_permission_matchdict_checked magpie.api.requests.get_value_matchdict_checked magpie.api.requests.get_query_param Module Contents --------------- .. py:function:: get_constant(constant_name: magpie.typedefs.Str, settings_container: Optional[magpie.typedefs.AnySettingsContainer] = None, settings_name: Optional[magpie.typedefs.Str] = None, default_value: Optional[magpie.typedefs.SettingValue] = None, raise_not_set: bool = True, raise_missing: bool = True, print_missing: bool = False, empty_missing: bool = False) -> magpie.typedefs.SettingValue Search in order for matched value of :paramref:`constant_name`: 1. search in :py:data:`MAGPIE_CONSTANTS` 2. search in settings if specified 3. search alternative setting names (see below) 4. search in :mod:`magpie.constants` definitions 5. search in environment variables Parameter :paramref:`constant_name` is expected to have the format ``MAGPIE_[VARIABLE_NAME]`` although any value can be passed to retrieve generic settings from all above-mentioned search locations. If :paramref:`settings_name` is provided as alternative name, it is used as is to search for results if :paramref:`constant_name` was not found. Otherwise, ``magpie.[variable_name]`` is used for additional search when the format ``MAGPIE_[VARIABLE_NAME]`` was used for :paramref:`constant_name` (i.e.: ``MAGPIE_ADMIN_USER`` will also search for ``magpie.admin_user`` and so on for corresponding constants). :param constant_name: key to search for a value :param settings_container: WSGI application settings container (if not provided, uses found one in current thread) :param settings_name: alternative name for `settings` if specified :param default_value: default value to be returned if not found anywhere, and exception raises are disabled. :param raise_not_set: raise an exception if the found key is ``None``, search until last case if others are ``None`` :param raise_missing: raise exception if key is not found anywhere :param print_missing: print message if key is not found anywhere, return ``None`` :param empty_missing: consider an empty value for an existing key as if it was missing (i.e.: as if not set). :returns: found value or `default_value` :raises ValueError: if resulting value is invalid based on options (by default raise missing/empty/``None`` value) :raises LookupError: if no appropriate value could be found from all search locations (according to options) .. py:class:: PermissionSet(permission: magpie.typedefs.AnyPermissionType, access: Optional[Union[Access, magpie.typedefs.Str]] = None, scope: Optional[Union[Scope, magpie.typedefs.Str]] = None, typ: Optional[PermissionType] = None, reason: Optional[magpie.typedefs.Str] = None) Bases: :py:obj:`object` Explicit definition of a :class:`Permission` with applicable :class:`Access` and :class:`Scope` to resolve it. The :class:`Permission` is the *name* of the applicable permission on the :class:`magpie.models.Resource`. The :class:`Scope` defines how the :class:`Permission` should impact the resolution of the perceived :term:`Effective Permissions ` over a :class:`magpie.models.Resource` tree hierarchy. The :class:`Access` defines how the :class:`Permission` access should be interpreted (granted or denied). Optionally, a :class:`PermissionType` can be provided to specifically indicate which kind of permission this set represents. This type is only for informative purposes, and is not saved to database nor displayed by the explicit string representation. It is returned within JSON representation and can be employed by :term:`Effective Permissions ` resolution to be more verbose about returned results. On missing :class:`Access` or :class:`Scope` specifications, they default to :attr:`Access.ALLOW` and :attr:`Scope.RECURSIVE` to handle backward compatible naming convention of plain ``permission_name``. Initializes the permission definition, possibly using required conversion from other implementations. :param permission: Name of the permission, or any other implementation from which the name can be inferred. :param access: Effective behaviour of the permissions. Generally, grant or deny the specified permission. :param scope: Scope for which the permission affects hierarchical resources. Important for effective resolution. :param typ: Type of permission being represented. Informative only, does not impact behavior if omitted. :param reason: Slightly more indicative information on why the current permission-type has this value. Value should be either explicitly provided or will be inferred if converted from input PermissionTuple. .. seealso:: :meth:`PermissionSet._convert` .. py:attribute:: __slots__ :value: ['_name', '_access', '_scope', '_tuple', '_type', '_reason'] .. py:method:: __eq__(other: Any) -> bool Return self==value. .. py:method:: __ne__(other: Any) -> bool Return self!=value. .. py:method:: __lt__(other: Any) -> bool Ascending sort of permission according to their name, access and scope modifiers. First sort by permission name alphabetically, followed by increasing *restrictive access* and increasing *range of scoped resources*. Using this sorting methodology, similar permissions by name are grouped together first, and permissions of same name with modifiers are then ordered, the first having less priority when selecting a single item to display with conflicting possibilities. Respecting :attr:`Access.DENY` is more important than :attr:`Access.ALLOW` (to protect the :term:`Resource`), and :attr:`Scope.MATCH` is *closer* to the actual :term:`Resource` than :attr:`Scope.RECURSIVE` permission received from a *farther* parent in the hierarchy. Sorted explicit string representation becomes:: [name1]-[allow]-[match] [name1]-[allow]-[recursive] [name1]-[deny]-[match] [name1]-[deny]-[recursive] [name2]-[allow]-[match] [name2]-[allow]-[recursive] [name2]-[deny]-[match] [name2]-[deny]-[recursive] ... We then obtain two **crucial** ordering results: 1. We can easily pick the last sorted item with highest resolution priority to find the final result of corresponding permissions. (note: final result for same user or group, their direct/inherited resolution is not considered here). 2. Picking the first element with lowest priority also displays the permission that impacts the widest range of resources. For instance in Magpie UI, indicating that a permission as :attr:`Scope.RECURSIVE` is more verbose as it tell other resources under it are also receive the specified :class:`Access` modifier rather than only the punctual resource. .. warning:: Alphabetically sorting permissions by string representation (implicit/explicit) is not equivalent to sorting them according to :term:`Permission` priority according to how modifiers are resolved. To obtain the prioritized sorting as strings, a list of :class:`PermissionSet` (with the strings as input) should be used to convert and correctly interpreted the raw strings, and then be converted back after sorting. .. code-block:: python # valid priority-sorted strings [str(perm) for perm in sorted(PermissionSet(p) for p in permission_strings)] # not equivalent to raw sorting list(sorted(permission_strings)) .. py:method:: __hash__() -> int Return hash(self). .. py:method:: __str__() -> magpie.typedefs.Str Obtains the compound literal representation of the :class:`PermissionSet`. Employed for database storage supporting ``ziggurat`` format. .. py:method:: __repr__() -> magpie.typedefs.Str Obtains the visual representation of the :class:`PermissionSet`. .. py:method:: like(other: Any) -> bool Evaluates if one permission is *similar* to another permission definition regardless of *modifiers*. This is different than ``==`` operator which will evaluate *exactly* equal permission definitions. .. py:method:: json() -> magpie.typedefs.PermissionDict Obtains the JSON representation of this :class:`PermissionSet`. .. py:method:: webhook_params() -> magpie.typedefs.JSON Obtain JSON representation employed for :term:`Webhook` reference. .. py:method:: ace(user_or_group: Optional[Union[magpie.models.User, magpie.models.Group]]) -> magpie.typedefs.AccessControlEntryType Converts the :class:`PermissionSet` into an :term:`ACE` that :mod:`pyramid` can understand. .. py:property:: reason :type: Optional[magpie.typedefs.Str] Indicative reason of the returned value defined by :meth:`type` or inferred by the :class:`PermissionTuple`. .. seealso:: :meth:`combine` :returns: Single string that describes the reason (source) of the permission, or multiple strings if updated by combination of multiple permissions. .. py:method:: resolve(permission1: magpie.typedefs.ResolvablePermissionType, permission2: magpie.typedefs.ResolvablePermissionType, context: PermissionType = PermissionType.INHERITED, multiple_choice: Optional[magpie.typedefs.Str] = None) -> magpie.typedefs.ResolvablePermissionType :classmethod: Resolves provided permissions into a single one considering various modifiers and groups for a resource. Permissions **MUST** have the same :term:`Permission` name. The associated :term:`Resource` on which the two compared permissions are applied on should also be the same This method **SHOULD NOT** be used by itself to obtain for :term:`Effective Permission` since it does not handle multi-level :term:`Resource` resolution. Resolution is accomplished in this case only for a given level in the tree hierarchy. The comparison considers both the :class:`Access` and :class:`Scope` of every :term:`Inherited Permission` of the :term:`User`, as well as its :term:`Group` memberships sorted by their priority. .. seealso:: - :meth:`magpie.services.ServiceInterface.effective_permissions` - :func:`magpie.api.management.user.user_utils.combine_user_group_permissions` - :meth:`PermissionSet.__lt__` :param permission1: Permission to compare. :param permission2: Permission to compare. :param context: Control the resolution context (local/effective) of the permissions (safeguard against invalid definitions). :param multiple_choice: Alternate explanation to default :data:`PERMISSION_REASON_MULTIPLE` applied if multiple :term:`Permission` refer to distinct :term:`Group` of equal priority and equivalent access definitions, meaning they are interchangeable without impacting resolution to access the same target :term:`Resource`. :returns: Permission with highest priority to resolve access a resource without considering scope. .. py:property:: group_priority :type: Optional[magpie.typedefs.GroupPriority] Priority accessor in case of group inherited permission resolved by :class:`PermissionTuple`. .. py:property:: perm_tuple :type: Optional[ziggurat_foundations.permissions.PermissionTuple] Get the original :class:`PermissionTuple` if available (:class:`PermissionSet` must have been created by one). .. py:property:: implicit_permission :type: Optional[magpie.typedefs.Str] Obtain the implicit string representation of the :class:`PermissionSet` as plain :class:`Permission` name. This representation is backward compatible with prior versions of `Magpie` where explicit representation of permission names in the database did not exist. If the contained modifiers of the :class:`PermissionSet` (notably the :attr:`Access.DENY`) result in a string representation that is *not possible* according to non existing permissions for older `Magpie` instances, the returned value will be ``None``. .. seealso:: - :meth:`explicit_permission` for the new representation. .. py:property:: explicit_permission :type: magpie.typedefs.Str Obtain the explicit string representation of the :class:`PermissionSet`. This format is always guaranteed to be completely defined contrary to :meth:`implicit_permission`. .. seealso:: - :meth:`__str__` (default string value). - :meth:`implicit_permission` for the old representation. .. py:property:: name :type: Permission .. py:attribute:: permission .. py:property:: access :type: Access .. py:property:: scope :type: Scope .. py:property:: type :type: Optional[PermissionType] .. py:method:: _convert(permission: magpie.typedefs.AnyPermissionType) -> Optional[PermissionSet] :classmethod: Converts any permission representation to the :class:`PermissionSet` with applicable enum members. Supports older :class:`Permission` representation such that implicit conversion of permission name without :attr:`access` and :attr:`scope` values are padded with defaults. Also, pre-defined partial or full definition from literal string representation are parsed to generate the :class:`PermissionSet` instance. :param permission: implicit or explicit permission name string, or any other known permission implementation :raises ValueError: when the permission name cannot be identified or parsed .. py:data:: CONTENT_TYPE_JSON :value: 'application/json' .. py:function:: get_logger(name: magpie.typedefs.Str, level: Optional[int] = None, force_stdout: bool = None, message_format: Optional[magpie.typedefs.Str] = None, datetime_format: Optional[magpie.typedefs.Str] = None) -> logging.Logger Immediately sets the logger level to avoid duplicate log outputs from the `root logger` and `this logger` when `level` is ``logging.NOTSET``. .. py:data:: LOGGER .. py:function:: check_value(value: Any, param_name: magpie.typedefs.Str, check_type: Any = six.string_types, pattern: Optional[Union[magpie.typedefs.Str, bool]] = ax.PARAM_REGEX) -> None Validates the value against specified type and pattern. :param value: value to validate. :param check_type: verify that parameter value is of specified type. Set to ``None`` to disable check. :param pattern: regex pattern to validate the input with. If value evaluates to ``False``, skip this kind of validation (default: :py:data:`ax.PARAM_REGEX`). :param param_name: path variable key. :return: None. :raises HTTPUnprocessableEntity: if the key is not an applicable path variable for this request. .. py:function:: get_request_method_content(request: pyramid.request.Request) -> Dict[magpie.typedefs.Str, Any] Obtain request content from property according to submitted method. Requests with HTTP ``GET`` store content into ``GET`` property, while other methods are in ``POST`` property. .. py:function:: get_multiformat_body(request: pyramid.request.Request, key: magpie.typedefs.Str, default: Optional[Any] = None) -> Any Obtains the value of :paramref:`key` element from the request body according to specified `Content-Type` header. .. seealso:: - :func:`get_multiformat_body_checked` - :func:`get_permission_multiformat_body_checked` - :func:`get_value_multiformat_body_checked` .. py:function:: get_permission_multiformat_body_checked(request: pyramid.request.Request, service_or_resource: magpie.typedefs.ServiceOrResourceType) -> magpie.permissions.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` .. py:function:: get_value_multiformat_body_checked(request: pyramid.request.Request, key: magpie.typedefs.Str, default: Any = None, check_type: Any = six.string_types, pattern: Optional[Union[magpie.typedefs.Str, bool]] = ax.PARAM_REGEX) -> magpie.typedefs.Str Obtains and validates the matched value under :paramref:`key` element from the request body. Parsing of the body is accomplished according to ``Content-Type`` header. :param request: request from which to retrieve the key. :param key: body key variable. :param default: value to return instead if not found. If this default is ``None``, it will raise. :param check_type: verify that parameter value is of specified type. Set to ``None`` to disable check. :param pattern: regex pattern to validate the input with. If value evaluates to ``False``, skip this kind of validation (default: :py:data:`magpie.api.exception.PARAM_REGEX`). :return: matched path variable value. :raises HTTPBadRequest: if the key could not be retrieved from the request body and has no provided default value. :raises HTTPUnprocessableEntity: if the retrieved value from the key is invalid for this request. .. seealso:: - :func:`get_multiformat_body` .. py:function:: get_principals(request: pyramid.request.Request) -> List[magpie.typedefs.AnyAccessPrincipalType] Obtains the list of effective principals according to detected request session user. .. py:function:: has_admin_access(request: pyramid.request.Request) -> bool Verifies if the authenticated user doing the request has administrative access. .. note:: Any request view that does not explicitly override ``permission`` by another value than the default :envvar:`MAGPIE_ADMIN_PERMISSION` will already automatically guarantee that the request user is an administrator since HTTP [403] Forbidden would have been otherwise replied. This method is indented for operations that are more permissive and require conditional validation of administrator access. .. seealso:: Definitions in :class:`magpie.models.RootFactory` and :class:`magpie.models.UserFactory` define conditional principals and :term:`ACL` based on the request. .. py:function:: get_logged_user(request: pyramid.request.Request) -> Optional[magpie.models.User] .. py:function:: get_user(request: pyramid.request.Request, user_name_or_token: Optional[magpie.typedefs.Str] = None, user_status: Optional[magpie.models.UserStatuses] = None) -> magpie.models.AnyUser Obtains the user corresponding to the provided user-name, token or via lookup of the logged user request session. :param request: request from which to obtain application settings and session user as applicable. :param user_name_or_token: reference value to employ for lookup of the user. :param user_status: filter search based on a user status. Ignored if no user name or token is provided. :returns: found user. :raises HTTPForbidden: if the requesting user does not have sufficient permission to execute this request. :raises HTTPNotFound: if the specified user name or token does not correspond to any existing user. .. py:function:: get_user_matchdict_checked_or_logged(request: pyramid.request.Request, user_name_key: magpie.typedefs.Str = 'user_name', user_status: Optional[magpie.models.UserStatuses] = None) -> magpie.models.AnyUser Obtains either the explicit or logged user specified in the request path variable. :returns found user. :raises HTTPForbidden: if the requesting user does not have sufficient permission to execute this request. :raises HTTPNotFound: if the specified user name or logged user keyword does not correspond to any existing user. .. py:function:: get_user_matchdict_checked(request: pyramid.request.Request, user_name_key: magpie.typedefs.Str = 'user_name', user_status: Optional[magpie.models.UserStatuses] = None) -> magpie.models.AnyUser Obtains the user matched against the specified request path variable. :returns: found user. :raises HTTPForbidden: if the requesting user does not have sufficient permission to execute this request. :raises HTTPNotFound: if the specified user name does not correspond to any existing user. .. seealso:: - :func:`get_value_matchdict_checked` - :func:`get_user` .. py:function:: get_group_matchdict_checked(request: pyramid.request.Request, group_name_key: magpie.typedefs.Str = 'group_name') -> magpie.models.Group Obtains the group matched against the specified request path variable. :returns: found group. :raises HTTPForbidden: if the requesting user does not have sufficient permission to execute this request. :raises HTTPNotFound: if the specified group name does not correspond to any existing group. .. py:function:: get_resource_matchdict_checked(request: pyramid.request.Request, resource_name_key: magpie.typedefs.Str = 'resource_id') -> magpie.models.Resource Obtains the resource matched against the specified request path variable. :returns: found resource. :raises HTTPForbidden: if the requesting user does not have sufficient permission to execute this request. :raises HTTPNotFound: if the specified resource ID does not correspond to any existing resource. .. py:function:: get_service_matchdict_checked(request: pyramid.request.Request, service_name_key: magpie.typedefs.Str = 'service_name') -> magpie.models.Service Obtains the service matched against the specified request path variable. :returns: found service. :raises HTTPForbidden: if the requesting user does not have sufficient permission to execute this request. :raises HTTPNotFound: if the specified service name does not correspond to any existing service. .. py:function:: get_permission_matchdict_checked(request: pyramid.request.Request, service_or_resource: magpie.models.Resource) -> magpie.permissions.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 .. py:function:: get_value_matchdict_checked(request: pyramid.request.Request, key: magpie.typedefs.Str, check_type: Any = six.string_types, pattern: Optional[Union[magpie.typedefs.Str, bool]] = ax.PARAM_REGEX) -> magpie.typedefs.Str Obtains the matched value located at the expected position of the specified path variable. :param request: request from which to retrieve the key. :param key: path variable key. :param check_type: verify that parameter value is of specified type. Set to ``None`` to disable check. :param pattern: regex pattern to validate the input with. If value evaluates to ``False``, skip this kind of validation (default: :py:data:`ax.PARAM_REGEX`). :return: matched path variable value. :raises HTTPUnprocessableEntity: if the key is not an applicable path variable for this request. .. py:function:: get_query_param(request: pyramid.request.Request, case_insensitive_key: Union[magpie.typedefs.Str, Iterable[magpie.typedefs.Str]], default: Optional[Any] = None) -> Any Retrieves a query string value by name (case insensitive), or returns the default if not present.