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 informative
API routes that are granted Public Access to anyone such as the Magpie REST API documentation served under
a running Magpie instance or the instance’s version route.
Changed in version 2.0.0: Some routes under /users/{user_name}
are also granted more contextual access than the default admin-only
access requirement to allow self-referencing user operations. Using a combination of view configurations with
magpie.constants.MAGPIE_LOGGED_PERMISSION
and
magpie.constants.MAGPIE_CONTEXT_PERMISSION
, the permitted functionalities are controlled according to
the actual procedure being executed. In other words, if the Request User corresponds to the path variable
Context User, access could 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 a given Service or Resource.
Typically, request Access Permissions fall into one of the following category for all API endpoints. Higher permissions in the table typically imply higher access conditions.
View Permission |
Request Requirement |
---|---|
Logged User must be a member of Group configured by
|
|
Logged User must at the very least refer to itself in the request path variable and MUST be authenticated with an active session. |
|
Request User must refer to itself as Context User, but CAN be authenticated or not. |
|
|
Logged User must at the very least be Authenticated, but CAN refer to any other Context User or even none at all. |
|
Anyone can access the endpoint (i.e.: Public Access), including unauthenticated Request 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).
Context User corresponds exactly to same Request User identified from the path variable.
Context User in path variable is the special keyword
magpie.constants.MAGPIE_LOGGED_USER
.Context 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 or insufficient condition result.
Every time a User-scoped request is executed, the targeted Context User is resolved accordingly to
either the explicit {user_name}}
value provided, or the auto-resolved magpie.constants.MAGPIE_LOGGED_USER
value that implicitly retrieves the Request User as the Context User.
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.
Finally, it is worth further detailing the small distinction between
magpie.constants.MAGPIE_LOGGED_PERMISSION
and magpie.constants.MAGPIE_CONTEXT_PERMISSION
,
provided that they act almost the same way. More precisely, they both work in the exact situation where
Request User is equal to Context User, but each for different sets of applicable values for those
User references.
When a route is attributed magpie.constants.MAGPIE_LOGGED_PERMISSION
, it means that the Request User
must absolutely be authenticated (i.e.: not None
), while magpie.constants.MAGPIE_CONTEXT_PERMISSION
does
not enforce this criteria. The contextual permission is an extended set of the logged one with two exceptions, which
are when the Request User is unauthenticated and/or when the referenced Context User is resolved to the
unauthenticated User defined by magpie.constants.MAGPIE_ANONYMOUS_USER
.
An example where such distinction is important goes as follows. A request that requires to update User
details typically minimally requires a Logged User because it does not make sense to attempt modification
of an undefined User. If the magpie.constants.MAGPIE_CONTEXT_PERMISSION
requirement was applied, it
would imply that unauthenticated Context User could update itself, which is obviously wrong. On the other
hand, it makes sense to allow the Request User to update its own details. In this case, the applicable view
configuration is magpie.constants.MAGPIE_LOGGED_PERMISSION
so that it immediately forbids the operation if
the Request User did not accomplish prior Authentication. As counter example, requesting details about
resources that are Public (more details in Public Access for this), makes sense even when we did not
complete prior Authentication, as they are accessible to everyone. The view configuration in this case should
employ magpie.constants.MAGPIE_CONTEXT_PERMISSION
so that Context User referring to unauthenticated
User will be permitted. They cannot be set to pyramid.security.Authenticated
, as this would enforce
the need to signin in first, while pyramid.security.NO_PERMISSION_REQUIRED
would fully open all requests
targeting for example an administrator as Context User. It is important to distinguish in this situation between
Access Permissions of the view configuration and listed Applied Permissions on resources.
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] (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] (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] (2)
/users/example-user/resources/resource-A/permissions?effective=true => [read, write] (3)
/users/example-user/resources/service-3/permissions?effective=true => []
/users/example-user/resources/resource-B1/permissions?effective=true => [read] (2)
/users/example-user/resources/resource-B2/permissions?effective=true => [read, write] (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.