magpie.api.management.user.user_utils ===================================== .. py:module:: magpie.api.management.user.user_utils Attributes ---------- .. autoapisummary:: magpie.api.management.user.user_utils.SERVICE_TYPE_DICT magpie.api.management.user.user_utils.LOGGER magpie.api.management.user.user_utils.USERNAME_REGEX Classes ------- .. autoapisummary:: magpie.api.management.user.user_utils.WebhookAction magpie.api.management.user.user_utils.TemporaryToken magpie.api.management.user.user_utils.TokenOperation magpie.api.management.user.user_utils.PermissionSet magpie.api.management.user.user_utils.PermissionType magpie.api.management.user.user_utils.BaseViews Functions --------- .. autoapisummary:: magpie.api.management.user.user_utils.format_service magpie.api.management.user.user_utils.get_email_template magpie.api.management.user.user_utils.send_email magpie.api.management.user.user_utils.generate_callback_url magpie.api.management.user.user_utils.get_permission_update_params magpie.api.management.user.user_utils.process_webhook_requests magpie.api.management.user.user_utils.get_constant magpie.api.management.user.user_utils.format_permissions magpie.api.management.user.user_utils.service_factory magpie.api.management.user.user_utils.get_logger magpie.api.management.user.user_utils.get_settings_from_config_ini magpie.api.management.user.user_utils.create_user magpie.api.management.user.user_utils.update_user magpie.api.management.user.user_utils.create_user_resource_permission_response magpie.api.management.user.user_utils.assign_user_group magpie.api.management.user.user_utils.send_group_terms_email magpie.api.management.user.user_utils.create_pending_or_assign_user_group magpie.api.management.user.user_utils.handle_user_group_terms_confirmation magpie.api.management.user.user_utils.delete_user_group magpie.api.management.user.user_utils.delete_user_resource_permission_response magpie.api.management.user.user_utils.get_similar_user_resource_permission magpie.api.management.user.user_utils.get_user_resource_permissions magpie.api.management.user.user_utils.get_user_resource_permissions_response magpie.api.management.user.user_utils.get_user_services magpie.api.management.user.user_utils.get_user_service_permissions magpie.api.management.user.user_utils.filter_user_permission magpie.api.management.user.user_utils.resolve_user_group_permissions magpie.api.management.user.user_utils.regroup_permissions_by_resource magpie.api.management.user.user_utils.get_user_resources_permissions_dict magpie.api.management.user.user_utils.get_user_service_resources_permissions_dict magpie.api.management.user.user_utils.check_user_info magpie.api.management.user.user_utils.check_user_editable magpie.api.management.user.user_utils.get_user_groups_checked Module Contents --------------- .. py:function:: format_service(service: magpie.models.Service, permissions: Optional[List[magpie.permissions.PermissionSet]] = None, permission_type: Optional[magpie.permissions.PermissionType] = None, show_private_url: bool = False, show_public_url: bool = True, show_resources_allowed: bool = False, show_configuration: bool = False, basic_info: bool = False, dotted: bool = False) -> magpie.typedefs.JSON Formats a :term:`Service` information into JSON. .. note:: Automatically finds :paramref:`permissions` of the :paramref:`service` if not specified. To preserve `empty` permissions such as during listing of `user`/`group` resource permissions, an empty ``list`` should be specified. :param service: :term:`Service` to be formatted. :param permissions: Permissions to list along with the :paramref:`resource`. By default, these are the applicable permissions for that corresponding resource type. :param permission_type: Override indication of provenance to apply to :paramref:`permissions`. Only applicable when they are provided. :param show_private_url: Display the protected and private URL employed at service registration. :param show_public_url: Display the generated public URL from configured :ref:`config_twitcher`. :param show_resources_allowed: Display children resource details. :param show_configuration: Display the applicable configuration of the :term:`Service` if it supports it. :param basic_info: If ``True``, return only sufficient details to identify the service, without any additional details about :paramref:`permissions`, children resources or configuration information is returned. :param dotted: Employ a dot (``.``) instead of underscore (``_``) to separate :term:`Service` from its basic information. .. seealso:: :func:`magpie.api.management.resource.resource_formats.format_resource` .. py:function:: get_email_template(template_constant: magpie.typedefs.Str, container: Optional[magpie.typedefs.AnySettingsContainer] = None) -> mako.template.Template Retrieves the template file with email content matching the custom application setting or the corresponding default. Allowed values of :paramref:`template_constant` are: - :envvar:`MAGPIE_GROUP_TERMS_APPROVED_EMAIL_TEMPLATE` - :envvar:`MAGPIE_GROUP_TERMS_SUBMISSION_EMAIL_TEMPLATE` - :envvar:`MAGPIE_USER_REGISTRATION_SUBMISSION_EMAIL_TEMPLATE` - :envvar:`MAGPIE_USER_REGISTRATION_APPROVAL_EMAIL_TEMPLATE` - :envvar:`MAGPIE_USER_REGISTRATION_APPROVED_EMAIL_TEMPLATE` - :envvar:`MAGPIE_USER_REGISTRATION_DECLINED_EMAIL_TEMPLATE` - :envvar:`MAGPIE_USER_REGISTRATION_NOTIFY_EMAIL_TEMPLATE` :raises IOError: if an explicit override value of the requested template cannot be located. :returns: template formatter from the requested template file. .. py:function:: send_email(recipient: magpie.typedefs.Str, container: magpie.typedefs.AnySettingsContainer, template: mako.template.Template, parameters: Optional[TemplateParameters] = None) -> bool Send email notification using provided template and parameters. The preparation steps of the email (retrieve SMTP configuration, setup the SMTP connection, define email content parameters and attempt template generation) will directly raise if invalid as they correspond to incorrect application code or configuration settings. Following step to send the email with the established SMTP connection is caught and logged if raising an exception. This is to allow the calling operation to ignore failing email notification and act accordingly using the resulting email status. :param recipient: Email address of the intended recipient to which the email must be sent. :param template: Mako template used for the email contents. :param container: Any container to retrieve application settings. :param parameters: Parameters to provide for templating email contents. They are applied on top of various defaults values provided to all emails. :raises: any SMTP server configuration, template generator or parameter parsing error for email setup. :returns: success status of the notification email (sent without error, no guarantee of reception). .. py:class:: WebhookAction Bases: :py:obj:`magpie.utils.ExtendedEnum` Supported :term:`Webhook` actions. .. py:attribute:: CREATE_USER :value: 'create_user' Triggered when a new :term:`User` gets successfully created. .. seealso:: :ref:`webhook_user_create` .. py:attribute:: DELETE_USER :value: 'delete_user' Triggered when an existing :term:`User` gets successfully deleted. .. seealso:: :ref:`webhook_user_delete` .. py:attribute:: UPDATE_USER_STATUS :value: 'update_user_status' Triggered when an existing :term:`User` status gets successfully updated. .. seealso:: :ref:`webhook_user_update_status` .. py:attribute:: CREATE_USER_PERMISSION :value: 'create_user_permission' Triggered when a :term:`Permission` onto a :term:`Service` or :term:`Resource` gets created for a :term:`User`. .. seealso:: :ref:`webhook_permission_updates` .. py:attribute:: DELETE_USER_PERMISSION :value: 'delete_user_permission' Triggered when a :term:`Permission` onto a :term:`Service` or :term:`Resource` gets deleted for a :term:`User`. .. seealso:: :ref:`webhook_permission_updates` .. py:attribute:: CREATE_GROUP_PERMISSION :value: 'create_group_permission' Triggered when a :term:`Permission` onto a :term:`Service` or :term:`Resource` gets created for a :term:`Group`. .. seealso:: :ref:`webhook_permission_updates` .. py:attribute:: DELETE_GROUP_PERMISSION :value: 'delete_group_permission' Triggered when a :term:`Permission` onto a :term:`Service` or :term:`Resource` gets deleted for a :term:`Group`. .. seealso:: :ref:`webhook_permission_updates` .. py:function:: generate_callback_url(operation: magpie.models.TokenOperation, db_session: sqlalchemy.orm.session.Session, user: Optional[magpie.models.AnyUser] = None, group: Optional[magpie.models.Group] = None) -> magpie.typedefs.Str Generates a callback URL using `Magpie` temporary tokens for use by the webhook implementation. :param operation: targeted operation that employs the callback URL for reference. :param db_session: database session to store the generated temporary token. :param user: user reference associated to the operation as applicable. :param group: group reference associated to the operation as applicable. :return: generated callback URL. .. py:function:: get_permission_update_params(target: Union[magpie.models.User, magpie.models.Group], resource: magpie.typedefs.ServiceOrResourceType, permission: magpie.permissions.PermissionSet, db_session: sqlalchemy.orm.session.Session) -> magpie.typedefs.WebhookTemplateParameters Generates the :term:`Webhook` parameters based on provided references. .. py:function:: process_webhook_requests(action: WebhookAction, params: magpie.typedefs.WebhookTemplateParameters, update_user_status_on_error: bool = False, settings: Optional[magpie.typedefs.AnySettingsContainer] = None) -> None Checks the config for any webhooks that correspond to the input action, and prepares corresponding requests. :param action: tag identifying which webhooks to use in the config :param params: Dictionary containing the required parameters and associated values for the request following the event action. Parameters will replace *templates* found in the ``payload`` definition of the webhook. :param update_user_status_on_error: update the user status or not in case of a webhook error. :param settings: application settings where webhooks configuration can be retrieved. .. 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:: TemporaryToken(*_, **__) Bases: :py:obj:`ziggurat_foundations.models.base.BaseModel`, :py:obj:`Base` Model that defines a token for temporary URL completion of a given pending operation. .. py:attribute:: __tablename__ :value: 'tmp_tokens' .. py:attribute:: token .. py:attribute:: operation .. py:attribute:: created .. py:attribute:: user_id .. py:attribute:: _user .. py:attribute:: user_pending_id .. py:attribute:: _pending_user .. py:attribute:: group_id .. py:attribute:: group .. py:method:: user() -> AnyUser .. py:method:: url(settings: magpie.typedefs.AnySettingsContainer = None) -> magpie.typedefs.Str .. py:method:: expired() -> bool .. py:method:: by_token(token: Union[magpie.typedefs.Str, sqlalchemy.dialects.postgresql.UUID], db_session: Optional[sqlalchemy.orm.session.Session] = None) -> Optional[TemporaryToken] :staticmethod: .. py:method:: by_user(user: AnyUser, db_session: Optional[sqlalchemy.orm.session.Session] = None) -> Optional[sqlalchemy.orm.query.Query] :staticmethod: .. py:method:: json() -> magpie.typedefs.JSON .. py:class:: TokenOperation Bases: :py:obj:`magpie.utils.ExtendedEnum` Supported operations by the temporary tokens. .. py:attribute:: GROUP_ACCEPT_TERMS :value: 'group-accept-terms' Temporary token associated to an URL endpoint called by a user that accepts the terms and conditions (T&C) to join a particular group. .. py:attribute:: USER_PASSWORD_RESET :value: 'user-password-reset' Temporary token associated to an URL endpoint to request a user password reset. .. py:attribute:: USER_REGISTRATION_CONFIRM_EMAIL :value: 'user-registration-confirm-email' Temporary token associated to a pending user registration that requires email validation by visiting the link. .. py:attribute:: USER_REGISTRATION_ADMIN_APPROVE :value: 'user-registration-admin-approve' Temporary token associated to a pending user registration that will be approved by an administrator when visited. .. py:attribute:: USER_REGISTRATION_ADMIN_DECLINE :value: 'user-registration-admin-decline' Temporary token associated to a pending user registration that will be declined by an administrator when visited. .. py:attribute:: WEBHOOK_USER_STATUS_ERROR :value: 'webhook-user-status-error' Temporary token employed to provide a callback URL that a registered webhook can call following the triggered event to indicate that the corresponding operation resulted into an invalid user status. .. 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:class:: PermissionType Bases: :py:obj:`magpie.utils.ExtendedEnum` Applicable types of :term:`Permission` according to context. .. py:attribute:: ACCESS :value: 'access' .. py:attribute:: ALLOWED :value: 'allowed' .. py:attribute:: APPLIED :value: 'applied' .. py:attribute:: DIRECT :value: 'direct' .. py:attribute:: INHERITED :value: 'inherited' .. py:attribute:: EFFECTIVE :value: 'effective' .. py:attribute:: OWNED :value: 'owned' .. py:function:: format_permissions(permissions: Optional[Collection[magpie.typedefs.AnyPermissionType]], permission_type: Optional[PermissionType] = None, force_unique: bool = True) -> Dict[magpie.typedefs.Str, Union[List[magpie.typedefs.Str], magpie.typedefs.PermissionDict, magpie.typedefs.Str]] Obtains the formatted permission representations after validation that each of their name is a known member of :class:`Permission` enum, and optionally with modifiers as defined by :class:`PermissionSet`. The returned lists are sorted alphabetically by permission *name*, and then in order of resolution priority (from highest to lowest) for each subset or corresponding *name*. The permissions are cleaned from any duplicate entries, unless :paramref:`force_unique` is specified to allow it. If no or empty :paramref:`permissions` is provided, empty lists are returned. .. note:: Field ``permission_names`` provides both the *older* implicit permission names and the *newer* explicit name representation. For this reason, there will be semantically "duplicate" permissions in that list, but there will not be any literal string duplicates. Implicit names are immediately followed by their explicit name, unless implicit names do not apply for the given permission (e.g.: when :attr:`Access.DENY` did not exist). Only detailed and explicit JSON representations are provided in the ``permissions`` list. When :paramref:`permission_type` is equal to :attr:`PermissionType.ALLOWED`, the collection of every applicable :class:`PermissionSet` is automatically generated by expanding all combinations of :class:`Access` and :class:`Scope` with every provided :class:`Permission` name in :paramref:`permissions`. This allows more concise definition of allowed permissions under :class:`magpie.services.Services` and their children :term:`Resource` by only defining :class:`Permission` names without manually listing all variations of :class:`PermissionSet`. For other :paramref:`permission_type` values, which represent :term:`Applied Permission` only explicitly provided :paramref:`permissions` are returned, to effectively return the collection of *active* permissions. :param permissions: multiple permissions of any implementation and type, to be rendered both as names and JSON. :param permission_type: indication of the represented permissions to be formatted, for informative indication. :param force_unique: whether to remove duplicate entries by association of name, access and scope or not. :returns: JSON with the permissions listed as implicit+explicit names, as permission set objects, and their type. .. py:data:: SERVICE_TYPE_DICT .. py:function:: service_factory(service: magpie.models.Service, request: pyramid.request.Request) -> ServiceInterface Retrieve the specific service class from the provided database service entry. .. py:class:: BaseViews(request) Bases: :py:obj:`object` Base methods for Magpie UI pages. .. py:attribute:: MAGPIE_FIXED_GROUP_MEMBERSHIPS :value: [] Special :term:`Group` memberships that cannot be edited. .. py:attribute:: MAGPIE_FIXED_GROUP_EDITS :value: [] Special :term:`Group` details that cannot be edited. .. py:attribute:: MAGPIE_FIXED_USERS :value: [] Special :term:`User` details that cannot be edited. .. py:attribute:: MAGPIE_FIXED_USERS_REFS :value: [] Special :term:`User` that cannot have any relationship edited. This includes both :term:`Group` memberships and :term:`Permission` references. .. py:attribute:: MAGPIE_USER_PWD_LOCKED :value: [] Special :term:`User` that *could* self-edit themselves, but is disabled since conflicting with other policies. .. py:attribute:: MAGPIE_USER_PWD_DISABLED :value: [] Special :term:`User` where password cannot be edited (managed by `Magpie` configuration settings). .. py:attribute:: MAGPIE_ANONYMOUS_GROUP :value: None Reference to :py:data:`magpie.constants.MAGPIE_ANONYMOUS_GROUP` for convenience in UI pages. .. py:method:: add_template_data(data: Optional[Dict[magpie.typedefs.Str, Any]] = None) -> Dict[magpie.typedefs.Str, Any] Adds required template data for the 'heading' mako template applied to every UI page. .. py:method:: render(template: magpie.typedefs.Str, data: Optional[Dict[magpie.typedefs.Str, Any]] = None) -> pyramid.response.Response Render the response with an explicit Mako template reference. Views that are decorated by :func:`pyramid.view.view_config` or registered by :meth:`pyramid.config.Configurator.add_view` with a ``renderer`` parameter do not require to call this function as it is auto-resolved with the submitted :paramref:`data`. .. 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:function:: get_settings_from_config_ini(config_ini_path: magpie.typedefs.Str, ini_main_section_name: magpie.typedefs.Str = 'app:magpie_app') -> magpie.typedefs.SettingsType .. py:data:: LOGGER .. py:data:: USERNAME_REGEX .. py:function:: create_user(user_name: magpie.typedefs.Str, password: Optional[magpie.typedefs.Str], email: magpie.typedefs.Str, group_name: Optional[magpie.typedefs.Str], db_session: sqlalchemy.orm.session.Session, **extra_fields) -> pyramid.httpexceptions.HTTPException Creates a :term:`User` if it is permitted and not conflicting with existing ones. Password must be set to ``None`` if using an external identity or skip its encrypted value generation. Created :term:`User` will immediately be assigned membership to the group matching :paramref:`group_name` (can be :py:data:`MAGPIE_ANONYMOUS_GROUP` for minimal access). If no group is provided, this anonymous group will be applied by default, creating a user effectively without any permissions other than ones set directly for him and inherited from :ref:`perm_public_access`. Furthermore, the :term:`User` *always* gets associated with :py:data:`MAGPIE_ANONYMOUS_GROUP` (if not already explicitly or implicitly requested with :paramref:`group_name`) to allow access to resources with public permission. This means that when :paramref:`group_name` is provided with another name than :py:data:`MAGPIE_ANONYMOUS_GROUP`, the :term:`User` will have two memberships initially. Argument :paramref:`group_name` **MUST** be an existing group if provided. .. note:: In order to properly handle subscribed :term:`Webhook` that could request to change the user status to an error following a failing external operation, the created user is immediately committed. This way, following requests will have access to the instance from the database. Because of this requirement, any operation that desire an handle to the created :class:`User` instance should retrieve it again from the database session. :param user_name: Unique name of the user to validate and employ for creation. :param password: Raw password of the user to validate and employ for creation. If Skipped if ``None``. Otherwise, apply hash encryption on the value. :param email: User email to be validated and employed for creation. :param group_name: Group name to associate the user with at creation time. :param db_session: database connection. :param extra_fields: Additional fields that should be set for the user. Must be known properties of the instance. :returns: valid HTTP response on successful operation, or the :class:`User` when requested. .. py:function:: update_user(user: magpie.models.User, request: pyramid.request.Request, new_user_name: Optional[magpie.typedefs.Str] = None, new_password: Optional[magpie.typedefs.Str] = None, new_email: Optional[magpie.typedefs.Str] = None, new_status: Optional[magpie.models.UserStatuses] = None) -> None Applies updates of user details with specified values after validation. :param user: targeted user to update . :param request: request that produced this update operation. :param new_user_name: new name to apply (if provided). :param new_password: new password to apply (if provided). :param new_email: new email to apply (if provided). :param new_status: new status to apply (if provided). :return: None if update was successful. .. py:function:: create_user_resource_permission_response(user: magpie.models.User, resource: magpie.typedefs.ServiceOrResourceType, permission: magpie.permissions.PermissionSet, db_session: sqlalchemy.orm.session.Session, overwrite: bool = False) -> pyramid.httpexceptions.HTTPException Creates a permission on a user/resource combination if it is permitted, and optionally not conflicting. :param user: user for which to create/update the permission. :param resource: service or resource for which to create the permission. :param permission: permission with modifiers to be applied. :param db_session: database connection. :param overwrite: If the corresponding `(user, resource, permission[name])` exists, there is a conflict. Conflict is considered only by permission-name regardless of other modifiers. If permissions match exactly (including modifiers), conflict is raised regardless of overwrite value. If overwrite is ``False``, the conflict will be raised and not be applied. If overwrite is ``True``, the permission modifiers will be replaced by the new ones, or created if missing. :returns: valid HTTP response on successful operation. .. py:function:: assign_user_group(user: magpie.models.User, group: magpie.models.Group, db_session: sqlalchemy.orm.session.Session) -> None Creates a user-group relationship (user membership to a group). :returns: nothing - user-group is created. :raises HTTPError: corresponding error matching problem encountered. .. py:function:: send_group_terms_email(user: magpie.models.User, group: magpie.models.Group, db_session: sqlalchemy.orm.session.Session) -> pyramid.httpexceptions.HTTPSuccessful Sends an email for terms and conditions confirmation. Terms and conditions email are sent in the case of a request for the creation of a user-group relationship where the group requires a terms and conditions confirmation. :returns: valid HTTP response on successful operations. :raises HTTPError: corresponding error matching problem encountered. .. py:function:: create_pending_or_assign_user_group(user: magpie.models.User, group: magpie.models.Group, db_session: sqlalchemy.orm.session.Session) -> pyramid.httpexceptions.HTTPSuccessful Associates the pending user or existing user to the group. Creates either a new user-group relationship (user membership to a group) or a pending terms and conditions confirmation. If the group requires a T&C confirmation, sends an email for T&C confirmation, else, the user is assigned directly to the group. :returns: valid HTTP response on successful operations. :raises HTTPError: corresponding error matching problem encountered. .. py:function:: handle_user_group_terms_confirmation(tmp_token: magpie.models.TemporaryToken, request: pyramid.request.Request) -> pyramid.response.Response Handles the confirmation of a user to accept the terms and conditions of a group. Generates the appropriate response that will be displayed to the user. .. py:function:: delete_user_group(user: magpie.models.User, group: magpie.models.Group, db_session: sqlalchemy.orm.session.Session) -> None Deletes a user-group relationship (user membership to a group). :returns: nothing - user-group is deleted. :raises HTTPNotFound: if the combination cannot be found. .. py:function:: delete_user_resource_permission_response(user: magpie.models.User, resource: magpie.typedefs.ServiceOrResourceType, permission: magpie.permissions.PermissionSet, db_session: sqlalchemy.orm.session.Session, similar: bool = True) -> pyramid.httpexceptions.HTTPException Get validated response on deleted user resource permission. :param user: user for which to delete the permission. :param resource: service or resource for which to delete the permission. :param permission: permission with modifiers to be deleted. :param db_session: database connection. :param similar: Allow matching provided permission against any similar database permission. Otherwise, must match exactly. :returns: valid HTTP response on successful operations. :raises HTTPException: error HTTP response of corresponding situation. .. py:function:: get_similar_user_resource_permission(user: magpie.models.User, resource: magpie.typedefs.ServiceOrResourceType, permission: magpie.permissions.PermissionSet, db_session: sqlalchemy.orm.session.Session) -> Optional[magpie.permissions.PermissionSet] Obtains the user service/resource permission that corresponds to the provided one. Lookup considers only *similar* applied permission such that other permission modifiers don't affect comparison. .. py:function:: get_user_resource_permissions(user: magpie.models.User, resource: magpie.models.Resource, db_session: sqlalchemy.orm.session.Session, inherit: bool = False, resolve: bool = False) -> Tuple[List[magpie.permissions.PermissionSet], magpie.permissions.PermissionType, bool] Retrieves user resource permissions applied directly, with inherited group permissions, or resolve between them. :param user: user for which to retrieve permissions for the resource, and optionally its groups as well. :param resource: resource for which permissions to retrieve are applied on. :param db_session: database session :param inherit: obtain permissions with user's group inheritance (duplicate permissions possible across user/groups) :param resolve: resolve permissions across user/groups to obtain a single highest priority permission on resource. .. py:function:: get_user_resource_permissions_response(user: magpie.models.User, resource: magpie.typedefs.ServiceOrResourceType, request: pyramid.request.Request, inherit_groups_permissions: bool = True, resolve_groups_permissions: bool = False, effective_permissions: bool = False) -> pyramid.httpexceptions.HTTPException Retrieves user resource permissions with or without inherited group permissions. Alternatively retrieves the effective user resource permissions, where group permissions are implied as `True`. .. warning:: Does not consider direct :term:`Resource` ownership. .. seealso:: - :func:`get_direct_inherited_resolved_resource_permissions` - :func:`get_user_service_permissions` :returns: valid HTTP response on successful operations. :raises HTTPException: error HTTP response of corresponding situation. .. py:function:: get_user_services(user: magpie.models.User, request: pyramid.request.Request, cascade_resources: bool = False, format_as_list: bool = False, inherit_groups_permissions: bool = False, resolve_groups_permissions: bool = False, service_types: Optional[List[magpie.typedefs.Str]] = None) -> magpie.typedefs.UserServicesType Returns services by type with corresponding services by name containing sub-dict information. .. seealso:: :func:`regroup_permissions_by_resource` :param user: user for which to find services :param request: request with database session connection :param cascade_resources: If ``False``, return only services which the :term:`User` has :ref:`Immediate Permissions ` on specialized top-level resources corresponding to a :term:`Service`. Otherwise, return every service that has at least one sub-resource with permissions (children at any-level). In both cases, the *permissions* looked for consider either only :term:`Direct Permissions` or any :term:`Inherited Permission` according to the value of :paramref:`inherit_groups_permissions`. :param inherit_groups_permissions: If ``False``, return only user-specific service/sub-resources :term:`Direct Permissions`. Otherwise, resolve :term:`Inherited Permissions ` using all groups the user is member of. :param resolve_groups_permissions: Whether to combine :term:`Direct Permissions ` and :term:`Inherited Permissions ` for respective resources or not. :param format_as_list: Returns as list of service dict information (not grouped by type and by name) :param service_types: Filter list of service types for which to return details. All service types are used if omitted. :return: Only services which the user as :term:`Direct Permissions` or considering all tree hierarchy, and for each case, either considering only user permissions or every :term:`Inherited Permission`, according to provided options. :rtype: Mapping of services by type to corresponding services by name containing each sub-mapping of their information, unless :paramref:`format_as_list` is ``True``, in which case a flat list of service information is returned. .. py:function:: get_user_service_permissions(user: magpie.models.User, service: magpie.models.Service, request: pyramid.request.Request, inherit_groups_permissions: bool = True, resolve_groups_permissions: bool = False) -> List[magpie.permissions.PermissionSet] Retrieve the permissions the user has directly on a service or inherited permissions by its group memberships. .. warning:: - Does not consider :term:`Effective Permissions ` ownership. - Considers direct :term:`Service` ownership, but not implemented everywhere (not operational). .. seealso:: - :func:`get_user_resource_permissions` - :func:`get_user_resource_permissions_response` .. py:function:: filter_user_permission(resource_permission_list: List[ziggurat_foundations.permissions.PermissionTuple], user: magpie.models.User) -> Iterable[ziggurat_foundations.permissions.PermissionTuple] Retrieves only user :term:`Direct Permissions` amongst a list of user/group resource/service permissions. .. py:function:: resolve_user_group_permissions(resource_permission_list: List[magpie.typedefs.ResolvablePermissionType]) -> Iterable[magpie.permissions.PermissionSet] Reduces overlapping user :term:`Inherited Permission` 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 :term:`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 Permission` 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` .. py:function:: regroup_permissions_by_resource(resource_permissions: Iterable[magpie.typedefs.ResolvablePermissionType], resolve: bool = False) -> magpie.typedefs.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 .. py:function:: get_user_resources_permissions_dict(user: magpie.models.User, request: pyramid.request.Request, resource_types: Optional[List[magpie.typedefs.Str]] = None, resource_ids: Optional[List[int]] = None, inherit_groups_permissions: bool = True, resolve_groups_permissions: bool = False) -> magpie.typedefs.ResourcePermissionMap Creates a dictionary of resources ID with corresponding permissions of the user. .. seealso:: :func:`regroup_permissions_by_resource` :param user: user for which to find resources permissions :param request: request with database session connection :param resource_types: filter the search query with only the specified resource types :param resource_ids: filter the search query to only the specified resource IDs :param inherit_groups_permissions: Whether to include group inherited permissions from user memberships or not. If ``False``, return only user-specific resource permissions. Otherwise, resolve inherited permissions using all groups the user is member of. :param resolve_groups_permissions: whether to combine corresponding user/group permissions into one or not. :return: Only resources which the user has permissions on, or including all :term:`Inherited Permissions `, according to :paramref:`inherit_groups_permissions` argument. .. py:function:: get_user_service_resources_permissions_dict(user: magpie.models.User, service: magpie.models.Service, request: pyramid.request.Request, inherit_groups_permissions: bool = True, resolve_groups_permissions: bool = False) -> magpie.typedefs.ResourcePermissionMap Retrieves all permissions the user has for every :term:`Resource` nested under the :term:`Service`. The retrieved permissions can either include only :term:`Direct Permissions` or a combination of user and group :term:`Inherited Permissions ` accordingly to provided options. .. seealso:: :func:`get_user_resources_permissions_dict` :func:`regroup_permissions_by_resource` :returns: dictionary of resource IDs with corresponding permissions. .. py:function:: check_user_info(user_name: magpie.typedefs.Str = None, email: magpie.typedefs.Str = None, password: magpie.typedefs.Str = None, group_name: magpie.typedefs.Str = None, check_name: bool = True, check_email: bool = True, check_password: bool = True, check_group: bool = True) -> None Validates provided user information to ensure they are adequate for user creation. Using ``check_`` prefixed arguments, individual field checks can be disabled (check all by default). :raises HTTPException: appropriate error for the invalid field value or format that was checked as applicable. :returns: nothing if all enabled checks are successful. .. py:function:: check_user_editable(user: magpie.models.User, container: magpie.typedefs.AnySettingsContainer) -> None Verify if the specified user is allowed to receive modifications (to it directly or any resource referring to it). :param user: User to validate. :param container: Any container to retrieve application settings. :raises HTTPForbidden: When user is not allowed to be edited. :return: Nothing if allowed edition. .. py:function:: get_user_groups_checked(user: magpie.models.User, db_session: sqlalchemy.orm.session.Session) -> List[magpie.typedefs.Str] Obtains the validated list of group names from a pre-validated user.