Permissions

This chapter describes the various Permission types, format representation, and usage. For details regarding Authentication, please refer to Authentication and Authorization instead.

Types of Permissions

Across the documentation and the code, term Permission is often employed interchangeably to represent different and 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 employed to ensure correct interpretation of observed results.

Changed in version 3.0: Following introduction of Permission Representations as JSON objects with this version, a new magpie.permissions.PermissionType enum was added to make following types more explicit. Responses from the API will include a type field that indicates precisely the type of Permission returned, for each specific item presented below.

More specifically, following distinctions and terminology can be observed between different kind of Permission employed 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 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 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 complete set of Applied Permissions for that User and all its Group membership simultaneously. See Permission Types example for comparison of results with different query parameters.

    Changed in version 2.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.

  • Resolved Permissions:

    Specific interpretation of Inherited Permissions when there are multiple Applied Permissions combinations to the User and/or its Group memberships. The resolution of all those definitions are interpreted on a per-Resource basis to obtain an equivalent and unique Permission matching the one with highest priority, only for that localized scope. This resulting resolved Permission reduces the set of defined Inherited Permissions such that other entries on the same Resource can be ignored as they are either redundant or conflicting but of lesser priority. The resolution considers the various priorities according to their associated User, Group, Access and Scope attributes. See Extended Representation section for details.

    Added in version 3.5: The concept did not exist before this version as every Group was considered equal, whether they were with a special connotation (e.g.: MAGPIE_ANONYMOUS_GROUP) or any other generic Group.

  • Effective Permissions:

    Represents all Resolved 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 resolved=True, and can be obtained only from User-scoped requests with effective=true query parameter wherever supported. See Permission Types 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 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: 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 be granted to allow that individual to obtain or update its own details. In this situation, allowed routes are controlled on a per-request basis 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 categories for all API endpoints. Permissions listed in the table typically imply descending access conditions, the first being the most restrictive access (or requiring the highest privileges), and the last being more permissive to the open public.

Request Access Conditions

View 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 least refer to itself in the request path variable and MUST be authenticated with an active session.

magpie.constants.MAGPIE_CONTEXT_PERMISSION

Request User must refer to itself as Context User, but CAN be authenticated or not.

pyramid.security.Authenticated

Logged User must at the very least be Authenticated, but CAN refer to any other Context User or even none at all.

pyramid.security.NO_PERMISSION_REQUIRED

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:

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

  2. verify if Context User corresponds exactly to same Request User identified from the path variable.

  3. verify if Context User in path variable is special keyword magpie.constants.MAGPIE_LOGGED_USER.

  4. verify if 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 respectively to met or insufficient privileges against the Request Access Conditions table.

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 equal to 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 are 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 with magpie.constants.MAGPIE_LOGGED_PERMISSION access requirement, it means that the Request User must absolutely be authenticated (i.e.: not None) to be granted access, while magpie.constants.MAGPIE_CONTEXT_PERMISSION does not specifically 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 as the special User defined by magpie.constants.MAGPIE_ANONYMOUS_USER that also represents unauthenticated User.

An example where such distinction is important goes as follows. A request that requires to update User details 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 special Context User could update itself, which is obviously wrong since it does not represent a real User account. On the other hand, it makes sense to allow some Request User to update its personal details. In this case, the applicable view configuration should be 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. For previous cases, Access Permissions cannot be defined using pyramid.security.Authenticated, as this would enforce requirement to login first (when not always needed), while pyramid.security.NO_PERMISSION_REQUIRED would fully open all requests, including ones targeting for example an administrator as Context User which should be masked to non-administrators.

For all presented reasons above, it is important to distinguish between Access Permissions applied to request view configuration and Applied Permissions on resources, and they conceptually represent completely different operations, but are managed according to overlapping User and Group definitions.

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 Permissions to it are inherited by everyone, making the corresponding Resource effectively Public.

It is VERY important to apply Permission on the special Group defined by magpie.constants.MAGPIE_ANONYMOUS_GROUP rather than then special 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). Doing so would cause unnatural situations. If the Permission was applied for 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 (as if login to a different account). 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 the same person wouldn’t retain access to the same resources anymore as soon as he would authenticate itself (login). That User would have the impression that its access rights are lowered although they should naturally expect increased privileges after authenticating itself.

Changed in version 3.22: Starting with this version, modifications to magpie.constants.MAGPIE_ANONYMOUS_USER itself, or any Service, Resource or Permission with references to that special User will be explicitly forbidden by the API to avoid above mentioned ambiguities.

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.

Permission Definition and Modifiers

Added in version 3.0: Previous versions of Magpie employed literal [permission_name] and [permission_name]-match strings to respectively represent recursive and exact match scope over the tree hierarchy of Resource. All -match suffixed Permission names are now deprecated in favor of modifiers presented in this section. Furthermore, the Access.DENY concept is introduced via access field, which did not exist at all in previous versions.

When applying a Permission on a Service or Resource for a User or Group, there are 3 components considered to interpret its definition:

  1. name

  2. access

  3. scope

These concepts are implemented using magpie.permissions.PermissionSet.

The name represents the actual operation that is being attributed. For example, read and write would be different name that could be applied on a Resource that represents a file. All allowed name values are defined by magpie.permissions.Permission enum, but the subset of Allowed Permissions are controlled per specific Service and children Resource implementations.

The access component is defined by magpie.permissions.Access enum. This specifies whether the Permission should be allowed or denied. More specifically, it provides flexibility to administrators to correspondingly grant or remove the Permission for previously denied or allowed User or Group when resolving the Resource tree hierarchy. This helps solving special use cases where different inheritance conditions must be applied at different hierarchy levels. By default, if no access indication is provided when creating a new Permission, Access.ALLOW is employed since Magpie resolves all access to a Resource as Access.DENY unless explicitly granted. In other words, Magpie assumes that administrators adding new Permission entries intend to grant Service or Resource access for the targeted User or Group. Any Permission specifically created using Access.DENY should be involved only to revert a previously resolved Access.ALLOW, as they are otherwise redundant to default Effective Permissions resolution.

The scope concept is defined by magpie.permissions.Scope enum. This tells Magpie whether the Applied Permissions should impact only the immediate Resource (i.e.: when match) or should instead be applied recursively for it and all its children. By applying a recursive Permission on a higher-level Resource, this modifier avoids having to manually set the same Permission on every sub-Resource when access as to be provided over a large hierarchy. Also, when combined with the access component, the scope modifier can provide advanced control over granted or denied access.

As a general rule of thumb, all Permissions are resolved such that more restrictive access applied closer to the actual Resource for the targeted User will have priority, both in terms of inheritance by tree hierarchy and by Group memberships.

Warning

Whenever possible, it is preferable and strongly advised to define new Permission definitions using Access.ALLOW as close as possible to the target child Resource for which to allow access, and leave the parent Resource without any Permission to let it be resolved by default to Access.DENY as well as any other Resource under it except the explicitly allowed one. This is safer than the error prone alternative to Access.ALLOW everything at the root and revoke access at lower levels using Access.DENY to add “allowed exceptions” to the Resource hierarchy. In case of incorrect request parsing, this second approach could potentially erroneously grant access to Resource intended to be blocked. Using the first approach (only explicitly Access.ALLOW granted items) would still block by default all incorrectly parsed requests, ensuring children Resource would still be protected.

Permissions Representation

Added in version 3.0: Prior to this version, only plain permission-names where employed. These are represented by the implicit string representation in following versions of Magpie.

Basic Representation

As presented in the previous section, every Permission in Magpie is represented by three (3) elements, namely the name, the access and the scope. These are represented in API responses by both explicit and implicit string representations, as well as one extensive JSON representation. The implicit representation is mostly preserved for backward compatibility reasons, and represents the previous naming convention which can partially be mapped to the explicit string representation, due to the addition of access and scope modifiers.

Therefore, it can be noted that all API responses that contain details about permissions will return both the permission_names and permissions fields as follows.

{
    "permission_names": [
        "[permission-name]",
        "[name-access-scope]"
    ],
    "permissions": [
        {
            "name": "permission-name",
            "access": "allow|deny",
            "scope": "match|recursive",
            "type": "allowed|applied|direct|inherited|effective",
            "reason": "<optional>"
        }
    ]
}

The permission_names will return the combination of all applicable implicit and explicit string representations, and could therefore contain duplicate entries in terms of representation. For example, the value "read" (implicit) and the value "read-allow-recursive" (explicit) are both equivalent after interpretation to the JSON extended format. The permissions list will ensure that no such duplicates will exist using JSON representation.

Note

Single Permission operations, such as creation, deletion or update of a permission entry will also provide all above variants, but without the plural s in the field names.

Extended Representation

It can be noted that the previous JSON representation also provides a fourth type parameter which serves as indicative detail about the kind of Permission being displayed. This field is provided in attempt to reduce the ambiguity described in Types of Permissions.

Added in version 3.5: Fifth field named reason is introduced. It is also an informative field such as type and does not impact the stored Permission, but helps comprehend how ACL gets resolved for a given User.

Using field reason, it is possible to obtain an even more detailed explanation of the returned Permission set. This field can be represented with the following combinations:

Value/Format of reason

Description

"administrator"

Indicates that permission was granted because the User has full administrative access over the corresponding Resource. Typically, this means the User is a member of MAGPIE_ADMIN_GROUP and no further Permission resolution needs to take place.

"user:<id>:<name>"

The resolved access to the Resource is caused by the Direct Permissions of the User.

"group:<id>:<name>"

The resolved access to the Resource is caused by an Inherited Permissions the User obtains through the specified Group membership.

"multiple"

The resolved access to the Resource is simultaneously caused by multiple Resolved Permissions of equal priority. This can be displayed when using resolve detailed below, and that not only a single Group affects the resulting Permission.

"no-permission"

The resolved access results into not a single Permission found, defaulting to denied access. This occurs only during Effective Permissions resolution where explicit Access values must be returned for every possible Permission name. Unspecified Permission entries are simply omitted (as they don’t exist) for every other type of request.

Field reason is specifically useful when employed with inherited query parameter onto User-scoped request paths, as this option will simultaneously return all Inherited Permissions, both applied for the User itself and all its Group memberships. Each of the listed Permission would then individually have its appropriate reason field indicated, giving a broad overview of the applicable permissions for that User when processing Effective Permissions. Not using inherited would obviously only return Direct Permissions, which will only contain "user:<id>:<name>" formatted reason fields.

Furthermore, a localized preview of the Resolved Permissions can be obtained by using query parameter resolve. When this option is provided, Magpie will merge all Applied Permissions of the User and its Group memberships into a single entry (one per distinct Permission name) over the targeted Resource. This offers a simplified view of the Permissions Resolution (although only locally), to ease interpretation of Applied Permissions, notably when multiple Group memberships with redundant, complementary or even contradicting Permission entries are defined on the same Resource, which the User would inherit from.

Warning

Field resolve does not return the final Effective Permissions resolution (Scope.RECURSIVE is not considered in this case). It only indicates, locally for a given Resource, the most important Applied Permission of a User amongst all of its existing Inherited Permissions.

Permissions Resolution

This section details the step-by-step process employed to resolve Effective Permissions to grant or refuse User access to a given Resource. Some of the steps also apply to Inherited Permissions resolution.

Changed in version 3.5: Previous versions of Magpie considered every Group with equal priority (step (2.2) in below list), not making any distinction between them although there are usually some implied priorities in practice. Later versions include step (2.3) to remediate this issue.

Below are the resolution steps which are applied for every distinct Permission name over a given Resource for which ACL must be obtained:

  1. Any Direct Permissions applied explicitly for the evaluated User and Resource combination are obtained. Any such Permission, whether it is affected by Access.ALLOW or Access.DENY modifier dictates the Access result over that Resource.

  2. Following is the resolution of Inherited Permissions. In this case, there are three possibilities:

    1. There is only one Group for which a Permission is defined. The User inherits that specification, whether it is Access.ALLOW or Access.DENY.

    2. Many Group membership exist and share of same highest priority. In this case, if any Group has Access.DENY, the resolved access is marked as denied. If every equally prioritized Group indicate Access.ALLOW, then access is granted to the User.

    3. Otherwise, the highest priority Group dictates the Access resolution. This can potentially revert a previous Group decision.

The specific use case of step (2.3) is intended to give higher resolution precedence to any generic Group over the special MAGPIE_ANONYMOUS_GROUP definition. This means that a generic Group with a Permission affected by Access.ALLOW modifier can override special MAGPIE_ANONYMOUS_GROUP that would have Access.DENY for the same Resource, although resolution is opposite in every other situation. The reason for this exception is due to the nature of MAGPIE_ANONYMOUS_GROUP membership that is being automatically applied to every User in order to also grant them Public Access to any Resource marked as accessible to anyone. Since that special Group also represents “unauthenticated users”, it is both counter intuitive and not practical to equally resolve conflicting Inherited Permissions as it is naturally expected that an authenticated User with specific Group memberships should receive higher access privileges than its unauthenticated counterpart in case of contradictory Permission across both Group. In other words, when a Resource is blocked to the open public, it is expected that a User that would obtain access to that Resource through another one of its Group memberships doesn’t remain denied access due to its implicit MAGPIE_ANONYMOUS_GROUP membership. Step (2.3) handles this edge case specifically.

Every generic Group (i.e.: others than MAGPIE_ANONYMOUS_GROUP and MAGPIE_ADMIN_GROUP) share the same priority, and will therefore resolve conflicting Access using the normal step conditions and prioritizing Access.DENY (e.g.: step (2.2)).

When resolving only Inherited Permissions, the procedure stops here and provides the applicable result if any was found, with the corresponding reason. An empty set of Permission is returned if none could be found.

When instead resolving Effective Permissions, there are additional steps to the above Inherited Permissions resolution to consider special use-cases relative to administrative access as well as scoped inheritance over the Resource tree. The following resolution priority is accomplished:

  1. Resolve administrative access (i.e.: full access).
    [only during Effective Permissions]
  2. Resolution of Direct Permissions.
    [same as step (1) of Inherited Permissions resolution]
  3. Resolution of Inherited Permissions from Group memberships.
    [same as step (2) of Inherited Permissions resolution]
  4. Rewinding of the Resource tree to consider scoped inheritance.
    [only during Effective Permissions]

In this case, step (1) verifies if the User is a member for MAGPIE_ADMIN_GROUP. In such case, Access.ALLOW is immediately returned for every possible Allowed Permissions for the targeted Resource without further resolution involved. The reason why this check is accomplished only during Effective Permissions resolution is to avoid over populating the database with MAGPIE_ADMIN_GROUP Permission for every possible Resource. It can be noted that effectively, "administrator" as Permission reason will never be returned when requesting any other type of Permission than when specifying effective=true query, as there is no need to explicitly define MAGPIE_ADMIN_GROUP Applied Permissions. Furthermore, doing this pre-check step ensures that MAGPIE_ADMIN_GROUP members are always granted full access regardless of any explicit Applied Permission that could exist for that special Group.

When the User is not a member of MAGPIE_ADMIN_GROUP, Effective Permissions would then pursue to steps (2) and (3) with the traditional Inherited Permissions resolution listed earlier. The resolution process continues with step (4) by rewinding the parent Resource hierarchy until the first Permission is found, or until the root Service is reached. Only on the first iteration (when the targeted Resource is the same as the one looked for potential Inherited Permissions) does Scope.MATCH take effect. Only Scope.RECURSIVE are considered afterwards.

When the first Permission is found, the procedure remembers the Access of the Resolved Permissions for the current scope. If the found Permission is linked directly to the User, the procedure stops with the active Access as there cannot be any higher priority Inherited Permissions. Otherwise, the process continues rewinding further until an higher priority Group or any Direct Permissions are found. An higher priority would override the previously matched resolution scope and replaces the resolved Access, while equal or lower priorities are ignored. Doing so ensures that any Applied Permissions closer to the targeted Resource are respected, unless a more important User or Group precedence dictates otherwise.

Once the hierarchy rewinding process completes, the resolved Access is returned. If still no Permission could be found at that point, the result defaults to Access.DENY, and is indicated by "no-permission" for reason field. Following pseudo-code presents the overall procedure.

Effective Permissions algorithm
(1)     // Initialization
(1.1)   target ← resource to resolve access or closest one (when non existing)
(1.2)   match  ← enabled if target exists, otherwise disabled
(1.3)   found  ← "no-permissions"
(2)     // Verify administrative access
(2.1)   if user is member of MAGPIE_ADMIN_GROUP
(2.2)       found ← allow
(2.3)       (done)
(3)     // Resolve until completion
(3.1)   while not (done)
(3.2)       get applied permissions on target
(3.3)       resolve(applied permissions, match)     // see Inherited Permissions resolution
(3.4)       if resolved priority > found priority
(3.5)           found ← resolved permission
(4)         // Verify stopping rewind conditions
(4.1)       if found == user direct permission      // highest permission found
(4.2)           (done)
(4.3)       if target == service                    // top of hierarchy reached
(4.4)           (done)
(5)         // Rewind parent resource to continue resolution
(5.1)       target ← parent(target)
(5.2)       match  ← disabled
(done)  return found

In some cases, Service implementations will support simultaneous references to multiple Resources with a single request. One such example is when a request parameter allows a comma-separated list of values referring to distinct Resource items, for which Effective Resolution must be computed for each element of the list. When a Service supports this type of references, the above Effective Permissions algorithm is applied iteratively for every Resource until all have been validated for Access.ALLOW, or until the first Access.DENY is found. For this kind of Effective Permissions to be granted access, ALL requested Permission on every Resources in the set must be Access.ALLOW indiscriminately. Denied access to any element takes precedence over the whole set.

This procedure over multiple Resource only applies during ACL computation of an actual request to access the remote Service provider or one of its children Resource. When managing Applied Permissions on Resource definitions in Magpie, operations are always applied on elements individually.

Added in version 3.21: Resolution over multiple simultaneous Resource referred by a common request.

Examples

Finding User Permissions

One of the trickiest (and often confusing) 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 to go through.

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.

Changed in version 3.5: As of this version, API responses also provide reason field to help identify the source of every returned Permission. Please refer to Permissions Representation for more details.

Distinguishing 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.

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

Note

To simplify this example, having permissions on resource-type-1 also provides the same ones for child resources resource-type-2 and so on. More explicitly, it is assumed that every Permission is affected with (default) Scope.RECURSIVE and Access.ALLOW modifiers. See Permission Definition and Modifiers for further details on alternatives.

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 for them are defined 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 actual API 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]  (1)
/users/example-user/resources/service-2/permissions                     => []
/users/example-user/resources/resource-A/permissions                    => [read]   (1)
/users/example-user/resources/service-3/permissions                     => [write]  (1)
/users/example-user/resources/resource-B1/permissions                   => []
/users/example-user/resources/resource-B2/permissions                   => []

These Permissions (1) simply represent the Direct Permissions that example-user has, without considering all its Group memberships.

Using inherited=true 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]  (2)
/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]   (2)
/users/example-user/resources/resource-B2/permissions?inherited=true    => []

As illustrated, requesting for Inherited Permissions now also returns Group-related Permission (2) that where not returned before with only User-related Permission. Note that returned Permissions are all combinations of Applied Permissions originally defined.

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

/users/example-user/resources/service-1/permissions?effective=true      => [write]          (1)
/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      => [write]          (1)
/users/example-user/resources/resource-B1/permissions?effective=true    => [read, write]    (4)
/users/example-user/resources/resource-B2/permissions?effective=true    => [read, write]    (4)

In this case, all Resource entries that had Permission directly set on them, whether through User (1) or Group (2) 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 (1) and (2) is because there is no other Permission applied to any of their parent Service or Resource. Contrarily, resource-A (3) now additionally (in contrast to inherited=true) receives Permission write indirectly from its parent service-2. This is due to the recursive hierarchical inheritance of the parent-children permission scheme. Furthermore, resource-A (3) preserves the read Permission that was directly applied for example-user. Similarly, resource-B2 (4) which did not even have any immediate Applied Permissions on it, now receives both read and write access, respectively from its parents resource-B1 (through inherited Group Permission) and service-3 (through direct :term`User` Permission). 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 Permissions that are applied higher than 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 or its groups have on this resource alone”, and without any query, we obtain “which permissions that this user explicitly has itself on this resource”.

Note

For both inherited and effective query parameters, explicitly requesting false is equivalent to omitting the parameter entirely.

Effect of Permission Modifiers

See also

Section Permission Definition and Modifiers provides details about concepts relative to this example.

Below are examples of Permission definitions that can help better understand the different concepts. The definitions employ the [name]-[access]-[scope] convention to illustrate the applied Permission.

ServiceA                (UserA, read-allow-recursive)
    Resource1           (UserA, write-allow-match)
        Resource2       (UserA, read-deny-match)
            Resource3
ServiceB
    Resource4           (UserA, write-allow-match)
        Resource5
            Resource6   (UserA, read-allow-match) (UserA, write-allow-match)

In this example, UserA is granted read access to ServiceA, Resource1 and Resource3 because of the recursive scope applied on ServiceA. Access deny is explicitly applied on Resource2 with match scope, meaning that only that resource is specifically blocked by overriding (or reverting) the granted higher level read-allow-recursive. If recursive was instead used on Resource2, Resource3 would also have been blocked. The write permission is also granted to UserA for Resource1, but no other item in the ServiceA branch can be written by UserA since match scope was used and deny is the default resolution method. Similarly, only Resource4 and Resource6 will allow the write permission under branch ServiceB. Note that different permission names can be applied simultaneously, such as for the case of Resource6. This will effectively grant UserA both of these permissions on Resource6. Other access and scope concepts can only have one occurrence over same name combination on a given hierarchy item, as they would define conflicting interpretation of Effective Permissions.

The above example presents only the resolution of User permissions. When actually resolving Effective Permissions, all Inherited Permissions from its Group memberships are also considered in the same fashion. The Group permissions complement definitions specifically applied to a User. In case of conflicting situations, such as when allow is applied via Direct Permissions and deny is defined via Inherited Permissions for same Resource, Direct Permissions have priority over any Group Permission. Also, deny access is prioritized over allow to preserve the default interpretation of protected access control defined by Magpie. When match and recursive scopes cause ambiguous resolution, the match Permission is prioritized over inherited access via parent scope.

Resolution of Permissions

This example will demonstrate the simultaneous resolution of all following concepts to obtain Effective Permissions of a User over a given targeted Resource:

It is recommended to have a general understanding of all the concepts by going though corresponding sections that describe them individually and in more details.

We start by defining the following Service and Resource hierarchy. We employ the ServiceAPI implementation that only allows one type of Resource (i.e.: route), and that easily converts path elements into the given hierarchy. In this case, every Resource can be applied with either Permission.READ (r) or Permission.WRITE (w). For a compact display, we indicate Access.ALLOW (A), Access.DENY (D), Scope.MATCH (M) and Scope.RECURSIVE (R) using the [name]-[access]-[scope] representation for Applied Permissions.

Resource Hierarchy                | TestUser      | TestGroup1    | TestGroup2    | Anonymous
                                  | [user]        | [group]       | [group]       | [special-group]
==================================+=======+=======+=======+=======+=======+=======+=======+========
service-A [api]                   | r-A-M |       |       |       |       |       |       | w-A-R
    resource-1 [route]            |       |       |       |       |       |       | r-D-R |
        [unspecified-1]           |   -   |   -   |   -   |   -   |   -   |   -   |   -   |   -
        resource-2 [route]        |       |       |       | w-A-R | r-A-R |       |       | w-D-R
            [unspecified-2]       |   -   |   -   |   -   |   -   |   -   |   -   |   -   |   -
            resource-3 [route]    |       | w-D-M |       |       |       |       |       |
                [unspecified-3]   |   -   |   -   |   -   |   -   |   -   |   -   |   -   |   -
    resource-4 [route]            |       |       | r-D-R |       | r-A-R |       |       | w-D-R
        resource-5 [route]        |       |       |       |       | r-A-R |       |       |

Note

Items with [unspecified-#] identifiers are employed to indicate path element that would land onto non existing Resource (e.g.: /service-A/resource-1/Unknown mapped to [unspecified-1]), but that will still obtain Effective Permissions affected by any applied Scope.RECURSIVE modifier on parent Resource locations (i.e.: resources that would be its parent if it did exist). Because [unspecified-#] items do not exist, there cannot be any corresponding Applied Permissions on them, as indicated by - mark.

Presented below is the resolved Effective Permissions matrix of TestUser considering above definitions.

Target

Permission

Access

Detail

service-A

read

Access is granted because Direct Permissions on service-A takes precedence over everything. Only TestUser has this permission, other users in TestGroup1, TestGroup2 and public access are all denied, unless other user also has some explicit permission or other group membership that grants access.

service-A

write

Access is granted because of Inherited Permissions from MAGPIE_ANONYMOUS_GROUP. In this case, anyone will obtain public access, not only TestUser.

resource-1

read

Applied Permission with recursively denied access for MAGPIE_ANONYMOUS_GROUP makes resource-1 publicly inaccessible for reading. Since the other read permission on parent service-A is affected to match scope, it does not propagate its scope onto resource-1 for resolution.

resource-1

write

No Applied Permissions is defined directly on resource-1, but the inherited scope from service-1 makes it publicly writable with MAGPIE_ANONYMOUS_GROUP permission.

resource-2

read

TestUser obtains access from its membership from TestGroup2 that allows access. It overrides MAGPIE_ANONYMOUS_GROUP defined access on resource-1 by group priority.

resource-2

write

Similarly to previous case, TestGroup1 grants access over MAGPIE_ANONYMOUS_GROUP denied access.

resource-3

read

TestUser obtains access again from its membership to TestGroup2 that allows recursive read access. Contrary to resource-2 that only resolved group priority, scope inheritance is also involved in this case.

resource-3

write

Explicit denied access by Direct Permissions onto resource-3 overrides anything specified at higher level in the hierarchy. Although granted access is defined by TestGroup1 at higher level, user permission takes precedence over Inherited Permissions.

[unspecified-1]

read

Because the resource does not exist, this path element can only inherit from recursive parent scope. The only applicable permission is the denied read access on resource-1 for MAGPIE_ANONYMOUS_GROUP. The resource is therefore blocked. Not having any permission would result by default to the same refused access.

[unspecified-1]

write

Special MAGPIE_ANONYMOUS_GROUP provides recursive access, and therefore publicly allows write access to this path segment. Any combination of /service-A/resource-1/<ANYTHING> will allow writing operations.

[unspecified-2]

read

All values matching this position are allowed because of TestGroup2 recursive access, as previous cases.

[unspecified-2]

write

All values are again allowed except resource-3. Because that entry exists and has explicit deny for TestUser, it will be blocked. Another unspecified value at same position will not be blocked.

[unspecified-3]

read

As all other resources nested under resource-2, TestGroup2 memberships grants access to this location for reading. The permission will also keep propagating indefinitely, allowing even deeper request path such as /service-A/resource-1/resource-2/a/b/c/d.

[unspecified-3]

write

Although access was explicitly denied to TestUser on resource-3, scope match does not propagate to lower resources. Anything at this level inherits from allowed access of TestGroup1 membership. Non TestGroup1 members are still forbidden access due to recursive denial on MAGPIE_ANONYMOUS_GROUP.

resource-4

read

Both groups TestGroup1 and TestGroup2 contradict (one deny and the other allow). Because they have equal group priority, the resolved permission favors denied access.

resource-4

write

Allowed from MAGPIE_ANONYMOUS_GROUP recursive access.

resource-5

read

Although both groups TestGroup1 and TestGroup2 specify opposite permissions on parent resource-4, the definition on resource-5 from TestGroup2 is closer than the TestGroup1 denied access. The allowed access takes precedence in this case because of scoped access that is not overridden by equal priority groups at higher hierarchy levels.

resource-5

write

Allowed from MAGPIE_ANONYMOUS_GROUP recursive access.