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 ofmagpie.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.
- Immediate Permissions:
Represents a “rule” combination that was explicitly applied to a Service. Rules applied to children Resource are NOT considered Immediate Permissions (they are simply Applied Permissions without any special connotation). Note that Immediate Permissions are still Applied Permissions. They are a special subset of Applied Permissions matching how Service are a specialized implementation of Resource (see:
magpie.models.Service
). This kind of Permissions is notably referred to by requests for Finding User Permissions as they provide useful and unique properties.
- 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
(withouted
) 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, queryinherited
(withed
) 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 witheffective=true
query parameter wherever supported. See perm_example for complete comparison.
- Access Permissions:
Represents the required level of Permission needed to access Magpie API routes to request details. These can be referred to as “roles”, but are inferred by Group memberships of the Logged User attempting to complete the request. It is the only kind of Permission which the values are not retrieved from the enum
magpie.permissions.Permission
, but rather from a combination of special Group and Configuration and defaults constants. See Route Access for more details.
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 |
---|---|
Logged User must be a member of Group configured by
|
|
Logged User must at the very refer to itself in the request path variable. |
|
|
Logged User must at the very least be Authenticated. |
|
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:
Logged User has administrative-level Access Permissions (always granted access).
Logged User corresponds exactly to same User identified from the path variable’s value.
User in path variable is the special keyword
magpie.constants.MAGPIE_LOGGED_USER
.User in path variable is special user
magpie.constants.MAGPIE_ANONYMOUS_USER
.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.