Permissions

Types of Permissions

Across the documentation and the code, term Permission is often employed interchangeably to represent different more subtle contextual functionalities. This is mostly an abuse of language, but is preserved regardless in order to maintain backward compatibility of features and API response content with older systems that could employ Magpie. Therefore, care must be taken to consider under which context this term is observed to ensure correct interpretation of observed results.

More specifically, following distinction can be considered between different kind of Permission used by Magpie:

  • Allowed Permissions:

    Represents the set of valid Permission values that could be applied to a given Service or Resource, depending on their type’s implementation. Every allowed Permission is one entry from magpie.permissions.Permission, and their set define the schema that will pass validation if applied to corresponding items of magpie.services, but they are not necessarily an active nor existing entry in the database (i.e.: Applied Permissions). In general, these values are obtained from requests scoped under paths /service and /resources.

  • Applied Permissions:

    Represents an active “rule” which defines a combination of (User|Group, Service|Resource, Permission). These entries are validated during requests against the appropriate Allowed Permissions of the targeted item (Service or Resource) to create the “rule” under (for) the targeted User or Group. When executing requests under scopes /users and /groups, responses without query parameter modifiers (see perm_example) are by default Applied Permissions. These are also scoped under a single context at a given time (User or Group), depending on the request path being executed. They determine which access rights will be granted or denied for the respective User or :term`Group`.

  • Direct Permissions:

    Explicitly represents Applied Permissions “rules” in the case of User context, which is, when Group membership are NOT considered (i.e.: Inherited Permissions). Since calling /users scoped requests can lead to all different Permission variations presented here with different contextual information, this term is employed to specifically indicate the situations of the default behaviour of the routes without query parameters.

  • Inherited Permissions:

    Represents the combined set of Applied Permissions from the User context and every one of its Group membership contexts. When requesting a Group’s permissions, only “rules” explicitly set on the given group are returned. The same concept applies when only requesting User permissions. Providing applicable User-scoped requests with inherited=true query parameter will return the merged set of Applied Permissions for that User and all his Group membership simultaneously. See perm_example for complete comparison.

    Changed in version 2.0.0: Prior to this version, inherit (without ed) was employed as query parameter name. This often lead to confusion between expected and returned results due to mistakenly employed adjective. Because they are referred to as Inherited Permissions in the documentation and naturally from a linguistic standpoint, query inherited (with ed) is now the official parameter. The older variant remains supported and equivalent.

  • Effective Permissions:

    Represents all Inherited Permissions of the User and all its Group membership, as well as the extensive resolution of the Service and every children Resource in its hierarchy for the requested Resource scope. Effective permissions automatically imply inherited=True, and can be obtained from User-scoped requests with effective=true query parameter wherever supported. See perm_example for complete comparison.

Route Access

Most of the HTTP routes require by default administrative privileges (i.e.: MAGPIE_ADMIN_PERMISSION or equivalent inferred from MAGPIE_ADMIN_GROUP membership for the Logged User). Exceptions to this are notably requests with User-scoped routes under /users/{user_name} which allow retrieval of Public Resource details (e.g.: obtaining information about what MAGPIE_ANONYMOUS_GROUP members have access to), and informational API routes that are granted full access to anyone such as the Magpie REST API documentation served under a running Magpie instance.

Changed in version 2.0.0: Some routes under /users/{user_name} are also granted more contextual access to self-referencing users using magpie.constants.MAGPIE_LOGGED_PERMISSION. In other words, if the Logged User corresponds to the path variable User, access is also granted to allow that individual to obtain or update its own details. In this situation, allowed routes are controlled on a per-request basis with for the respective contextual operations accomplished by each request. For example, Logged User could be granted access to update its account details, but won’t be able to grant itself more permissions on Service and Resource elements.

Typically, request Access Permissions fall into one of the following category for all API endpoints. Higher permissions in the table imply higher access.

Permission

Request Requirement

magpie.constants.MAGPIE_ADMIN_PERMISSION

Logged User must be a member of Group configured by magpie.constants.MAGPIE_ADMIN_GROUP.

magpie.constants.MAGPIE_LOGGED_PERMISSION

Logged User must at the very refer to itself in the request path variable.

pyramid.security.Authenticated

Logged User must at the very least be Authenticated.

pyramid.security.NO_PERMISSION_REQUIRED

Anyone can access the endpoint, including unauthenticated User session.

When targeting specific User-scoped routes, the following (simplified) operations are applied to determine if access should be granted to execute the request:

  1. Logged User has administrative-level Access Permissions (always granted access).

  2. Logged User corresponds exactly to same User identified from the path variable’s value.

  3. User in path variable is the special keyword magpie.constants.MAGPIE_LOGGED_USER.

  4. User in path variable is special user magpie.constants.MAGPIE_ANONYMOUS_USER.

  5. For the first matched of the above steps, the condition is compared to the specific request requirement. Access is granted or denied according to met condition result.

Note

Whenever one of the User-scoped requests refers to specials keywords such as magpie.constants.MAGPIE_ANONYMOUS_USER or magpie.constants.MAGPIE_ADMIN_GROUP, any operation that has the intention to modify the corresponding User or Group are forbidden. There is therefore some additional request-specific logic (depending on its purpose and resulting actions) for special-cases that is not explicitly detailed in above steps. Some of these special behaviors can be observed across the various tests.

Public Access

In order to achieve publicly accessible Service or Resource functionality by any given individual, the desired Permission must be applied on special Group defined with configuration setting magpie.constants.MAGPIE_ANONYMOUS_GROUP. Since every existing User automatically gets attributed membership to that special Group at creation time, all applied Permission to it are inherited by everyone, making the corresponding Resource effectively Public.

Note that it is VERY important to apply Permission on the Group defined by magpie.constants.MAGPIE_ANONYMOUS_GROUP rather than then User defined by magpie.constants.MAGPIE_ANONYMOUS_USER in order to achieve Public-like access by everyone. This is because using the User instead of the Group would instead make the Resource accessible ONLY while not authenticated (i.e.: when Logged User corresponds to magpie.constants.MAGPIE_ANONYMOUS_USER). Once a real User would authenticate itself, they would suddenly lose the Public Permission since Logged User would not be the special User anymore. That would lead to unexpected behavior where Resource intended to be always Public would contextually change access criteria depending on active Logged User session. More precisely, this would cause confusing situation where an unauthenticated User would be able to see publicly accessible elements, but wouldn’t see them anymore as soon as he would authenticate itself (login). That User would have the impression that its access rights are lowered although they should be increased by authenticating itself.

Special user magpie.constants.MAGPIE_ANONYMOUS_USER is available only for evaluation purpose of Public-only Permission applied to Service and Resource, but is technically not required to execute Magpie application. Effectively, when the active session corresponds to unauthenticated Logged User, it is still allowed to call User-scoped API request paths, which will return details about Public accessible items.

Example to distinguish Applied, Inherited and Effective Permissions

This section intends to provide more insight on the different Types of Permissions using a simplified demonstration of interaction between defined Service, Resource, Group, User and Permission elements.

Let’s say we have some fictive Service that allows the following permission scheme, and that it implements the default hierarchical resolution of Resource items (i.e.: having permissions on resource-type-1 also provides the same ones for child resources resource-type-2).

service-type                [read | write]
    resource-type-1         [read | write]
        resource-type-2     [read | write]

Given that scheme, let’s say that existing elements are defined using the allowed types as follows:

service-1               (service-type)
service-2               (service-type)
    resource-A          (resource-type-1)
service-3               (service-type)
    resource-B1         (resource-type-1)
        resource-B2     (resource-type-2)

Let’s says we also got a example-user that is member of example-group, and that Applied Permissions on them are as follows:

(service-1,   example-user,  write)
(service-2,   example-group, write)
(resource-A,  example-user,  read)
(service-3,   example-user,  write)
(resource-B1, example-group, read)

For simplification purposes, we will use the names directly in following steps, but remember that requests would normally require unique identifiers for Resource resolution. Lets observe what happens using different query parameters with request GET /users/{user_name}/resources/{resource_id}/permissions.

If no query parameter is specified, we obtain permissions as follows:

/users/example-user/resources/service-1/permissions                     => [write]
/users/example-user/resources/service-2/permissions                     => []
/users/example-user/resources/resource-A/permissions                    => [read]
/users/example-user/resources/service-3/permissions                     => [write]
/users/example-user/resources/resource-B1/permissions                   => []
/users/example-user/resources/resource-B2/permissions                   => []

Using inherited option, we obtain the following:

/users/example-user/resources/service-1/permissions?inherited=true      => [write]
/users/example-user/resources/service-2/permissions?inherited=true      => [write]  :sup:`(1)`
/users/example-user/resources/resource-A/permissions?inherited=true     => [read]
/users/example-user/resources/service-3/permissions?inherited=true      => [write]
/users/example-user/resources/resource-B1/permissions?inherited=true    => [read]   :sup:`(1)`
/users/example-user/resources/resource-B2/permissions?inherited=true    => []

As illustrated, requesting for Inherited Permissions now also returns Group-related Permission (1) where they where not returned before with only User-related Permission.

On the other hand, using effective would result in the following:

/users/example-user/resources/service-1/permissions?effective=true      => [write]
/users/example-user/resources/service-2/permissions?effective=true      => [write]          :sup:`(2)`
/users/example-user/resources/resource-A/permissions?effective=true     => [read, write]    :sup:`(3)`
/users/example-user/resources/service-3/permissions?effective=true      => []
/users/example-user/resources/resource-B1/permissions?effective=true    => [read]           :sup:`(2)`
/users/example-user/resources/resource-B2/permissions?effective=true    => [read, write]    :sup:`(4)`

In this case, Resource`s that had :term:`Permission directly set on them (2), whether through User or Group combination, all return the exact same set of Permission. This is because Effective Permissions always imply Inherited Permissions (i.e.: using both query simultaneously is redundant). The reason why we obtain these sets for cases (2) is also because there is no other Permission applied to any of their parent Service or Resource. Contrarily, resource-A (3) now additionally receives Permission read indirectly from its parent service-2 (note: write is redundant here). Similarly, resource-B2 (4) which did not even have any immediate Permission applied to it, now receives both read and write access, respectively from its parents resource-B1 and service-3. This demonstrates why, although Effective Permissions imply Inherited Permissions, they do not necessarily resolve to the same result according to the effective Resource hierarchy and its parent-children resolution implementation.

Using effective query tells Magpie to rewind the Resource tree from the requested Resource up to the top-most Service in order to accumulate all Inherited Permissions observed along the way for every encountered element. All Permission that is applied higher to the requested Resource are considered as if applied directly on it. Query parameter inherited limits itself only to specifically requested Resource, without hierarchy resolution, but still considering Group memberships. For this reason, inherited could look the same to effective results if the Service hierarchy is “flat”, or if all Permission can be found directly on the target Resource, but it is not guaranteed. This is further important if the Service’s type implementation provides custom methodology for parsing the hierarchy resolution (see services for more details).

In summary, effective tells us “which permissions does the user have access to for this resource”, while inherited answers “which permissions does the user have on this resource alone”, and without any query, we obtain “what are the permissions that this user explicitly has on this resource”.

Finding User Permissions

One of the trickiest (and often annoying) situation when we want to figure out which Service a User has any Permission on, is where to actually start looking? Effectively, if we have a vast amount of registered Service each with a immense hierarchy of Resource, doing an exhaustive search can be quite daunting, not to mention costly in terms of request lookup and resources.

For this purpose, there is one query parameter named cascade that can be employed with request GET /users/{user_name}/services. In normal condition (without the parameter), this request responds with every Service where the user has Immediate Permissions on (doesn’t lookup the whole tree hierarchy). With the added query parameter, it tells Magpie to recursively search the hierarchy of Applied Permissions and return all Service instances that possess any Permission given to at least one child Resource at any level. Furthermore, the cascade query can be combined with inherited query to search for all combinations of Inherited Permissions instead of (by default) only for the User’s Direct Permissions.

This query can be extremely useful to quickly answer “does the user have any permission at all on this service”, without needing to manually execute multiple successive lookup requests with all combinations of Resource identifiers in the hierarchy.