\midcom_services_auth_acl

This class is responsible for ACL checks against classes and content objects.

Privilege definition

Privileges are represented by the class midcom_core_privilege and basically consist of three parts: Name, Assignee and Value:

The privilege name is a unique identifier for the privilege. The mRFC 15 defines the syntax to be $component:$name, where $component is either the name of the component or one of 'midgard' or 'midcom' for core privileges. Valid privilege names are for example 'net.nehmer.static:do_something' or 'midgard:update'.

The assignee is the entity to which the privilege applies, this can be one of several things, depending on where the privilege is taken into effect, I'll explain this below in more detail:

On content objects (generally every object in the system used during 'normal operation'):

  • A Midgard User encapsulated by a midcom_core_user object.
  • A Midgard Group encapsulated by a midcom_core_group object or subtype thereof.
  • The magic assignee 'EVERYONE', which applies the privilege to every user unconditionally, even to unauthenticated users.
  • The magic assignee 'USERS', which applies to all authenticated users.
  • The magic assignee 'ANONYMOUS, which applies to all unauthenticated users.
  • The magic assignee 'OWNER', which applies for all object owners.

On users and groups during authentication (when building the basic privilege set for the user, which applies generally):

  • The magic string 'SELF', which denotes that the privilege is set for the user in general for every content object. SELF privileges may be restricted to a class by using the classname property available at both midcom_core_privilege and various DBA interface functions.

The value is one of MIDCOM_PRIVILEGE_ALLOW or MIDCOM_PRIVILEGE_DENY, which either grants or revokes a privilege. Be aware, that unsetting a privilege does not set it to MIDCOM_PRIVILEGE_DENY, but clears the entry completely, which means that the privilege value inherited from the parents is now in effect.

How are privileges read and merged

First, you have to understand, that there are actually three distinct sources where a privilege comes from: The systemwide defaults, the currently authenticated user and the content object which is being operated on. We'll look into this distinction first, before we get on to the order in which they are merged.

Systemwide default privileges

This is analogous to the MidCOM default configuration, they are taken into account globally to each and every check whether a privilege is granted. Whenever a privilege is defined, there is also a default value (either ALLOW or DENY) assigned to it. They serve as a basis for all privilege sets and ensure that there is a value set for all privileges.

These defaults are defined by the MidCOM core and the components respectively and are very restrictive, basically granting read-only access to all non sensitive information.

Currently, there is no way to influence these privileges unless you are a developer and writing new components.

Class specific, systemwide default privileges (for magic assignees only)

Often you want to have a number of default privileges for certain classes in general. For regular users/groups you can easily assign them to the corresponding users/groups, there is one special case which cannot be covered there at this time: You cannot set defaults applicable for the magic assignees EVERYONE, USERS and ANONYMOUS. This is normally only of interest for component authors, which want to have some special privileges assigned for their objects, where the global defaults do no longer suffice.

These privileges are queried using a static callback of the DBA classes in question, see the following example:

public function get_class_magic_default_privileges() { return Array ( 'EVERYONE' => [], 'ANONYMOUS' => [], 'USERS' => ['midcom:create' => MIDCOM_PRIVILEGE_ALLOW] ); }

See also the documentation of the $_default_magic_class_privileges member for further details.

User / Group specific privileges

This kind of privileges are rights, assigned directly to a user. Similar to the systemwide defaults, they too apply to any operation done by the user / group respectively throughout the system. The magic assignee SELF is used to denote such privileges, which can obviously only be assigned to users or groups. These privileges are loaded at the time of user authentication only.

You should use these privileges carefully, due to their global nature. If you assign the privilege midgard:delete to a user, this means that the user can now delete all objects he can read, unless there are again restricting privileges set to content objects.

To be more flexible in the control over the top level objects, you may add a classname which restricts the validity of the privilege to a class and all of its descendants.

Content object privileges

This is the kind of privilege that will be used most often. They are associated with any content object in the system, and are read on every access to a content object. As you can see in the introduction, you have the most flexibility here.

The basic idea is that you can assign privileges based on the combination of users/groups and content objects. In other words, you can say the user x has the privilege midgard:update for this object (and its descendants) only. This works with groups as well.

The possible assignees here are either a user, a group or one of the magic assignees EVERYONE, USERS or ANONYMOUS, as outlined above.

Be aware, that persons and groups are treted as content objects when loaded from the database in a tool like org.openpsa.user, as the groups are not used for authentication but for regular site operation there. Therefore, the SELF privileges mentioned above are not taken into account when determining the content object privileges!

Privilege merging

This is where we get to the guts of privilege system, as this is not trivial (but nevertheless straight-forward I hope). The general idea is based on the scope of object a privilege applies:

System default privileges obviously have the largest scope, they apply to everyone. The next smaller scope are privileges which are assigned to groups in general, followed by privileges assigned directly to a user.

From this point on, the privileges of the content objects are next in line, starting at the top-level objects again (for example a root topic). The smallest scope finally then has the object that is being accessed itself.

Let us visualize this a bit:

^ larger scope     System default privileges
|                  Class specific magic assignee default privileges
|                  Root Midgard group
|                  ... more parent Midgard groups ...
|                  Direct Midgard group membership
|                  User
|                  SELF privileges limited to a class
|                  Root content object
|                  ... more parent objects ...
v smaller scope    Accessed content object

Privileges assigned to a specific user always override owner privileges; owner privileges are calculated on a per-content-object bases, and are merged just before the final user privileges are merged into the privilege set. It is of no importance from where you get ownership at that point.

Implementation notes: Internally, MidCOM separates the "user privilege set" which is everything down to the line User above, and the content object privileges, which constitutes the rest. This separation has been done for performance reasons, as the user's privileges are loaded immediately upon authentication of the user, and the privileges of the actual content objects are merged into this set then. Normally, this should be of no importance for ACL users, but it explains the more complex graph in the original mRFC.

Predefined Privileges

The MidCOM core defines a set of core privileges, which fall in two categories:

Midgard Core Privileges

These privileges are part of the MidCOM Database Abstraction layer (MidCOM DBA) and have been originally proposed by me in a mail to the Midgard developers list. Unless otherwise noted, all privileges are denied by default and no difference between owner and normal default privileges is made.

  • midgard:read controls read access to the object, if denied, you cannot load the object from the database. This privilege is granted by default.
  • midgard:update controls updating of objects. Be aware that you need to be able to read the object before updating it, it is granted by default only for owners.
  • midgard:delete controls deletion of objects. Be aware that you need to be able to read the object before updating it, it is granted by default only for owners.
  • midgard:create allows you to create new content objects as children on whatever content object that you have the create privilege for. This means that you can create an article if and only if you have create permission for either the parent article (if you create a so-called 'reply article') or the parent topic, it is granted by default only for owners.
  • midgard:parameters allows the manipulation of parameters on the current object if and only if the user also has the midgard:update privilege on the object. This privileges is granted by default and covers the full set of parameter operations (create, update and delete).
  • midgard:attachments is analogous to midgard:parameters but covers attachments instead and is also granted by default.
  • midgard:autoserve_attachment controls whether an attachment may be autoserved using the midcom-serveattachmentguid handler. This is granted by default, allowing every attachment to be served using the default URL methods. Denying this right allows component authors to build more sophisticated access control restrictions to attachments.
  • midgard:privileges allows the user to change the permissions on the objects they are granted for. You also need midgard:update and midgard:parameters to properly execute these operations.
  • midgard:owner indicates that the user who has this privilege set is an owner of the given content object.

MidCOM Core Privileges

  • midcom:approve grants the user the right to approve or unapprove objects.
  • midcom:component_config grants the user access to configuration management system, it is granted by default only for owners.
  • midcom:isonline is needed to see the online state of another user. It is not granted by default.

Assigning Privileges

You assign privileges by using the DBA set_privilege method, whose static implementation can be found in midcom_baseclasses_core_dbobject::set_privilege(). Here is a quick overview over that API, which naturally only works on MidCOM DBA level objects:

  • TODO: Update with the set/unset call variants
  • TODO: Add get_privilege
Array get_privileges();
midcom_core_privilege create_new_privilege_object($name, $assignee = null, $value = MIDCOM_PRIVILEGE_ALLOW)
boolean set_privilege(midcom_core_privilege $privilege);
boolean unset_all_privileges();
boolean unset_privilege(midcom_core_privilege $privilege);

These calls operate only on the privileges of the given object. They do not do any merging whatsoever, this is the job of the ACL framework itself (midcom_services_auth_acl).

Unsetting a privilege does not deny it, but clears the privilege specification on the current object and sets it to INHERIT internally. As you might have guessed, if you want to clear all privileges on a given object, call unset_all_privileges() on the DBA object in question.

See the documentation of the DBA layer for more information on these five calls.

Summary

Methods
Properties
Constants
__construct()
register_default_privileges()
get_default_privileges()
get_owner_default_privileges()
get_user_id()
privilege_exists()
get_privileges_by_guid()
can_do_byclass()
can_do_byguid()
No public properties found
No constants found
No protected methods found
No protected properties found
N/A
_get_class_magic_privileges()
_get_user_per_class_privileges()
_load_privileges_byguid()
_collect_content_privileges()
_load_content_privilege()
get_parent_data()
_can_do_internal_sudo()
$auth
$_internal_sudo
$_default_privileges
$_owner_default_privileges
$_default_magic_class_privileges
$_privileges_cache
$_content_privileges_cache
N/A

Properties

$_internal_sudo

$_internal_sudo : boolean

This is an internal flag used to override all regular permission checks with a sort-of read-only privilege set. While internal_sudo is enabled, the system automatically grants all privileges except midgard:create, midgard:update, midgard:delete and midgard:privileges, which will always be denied. These checks go after the basic checks for not authenticated users or admin level users.

Type

boolean

$_default_privileges

$_default_privileges : array

Internal listing of all default privileges currently registered in the system. This is a privilege name/value map.

Type

array

$_owner_default_privileges

$_owner_default_privileges : array

Internal listing of all default owner privileges currently registered in the system.

All privileges not set in this list will be inherited. This is a privilege name/value map.

Type

array

$_default_magic_class_privileges

$_default_magic_class_privileges : array

This listing contains all magic privileges assigned to the existing classes. It is a multi-level array, example entry:

'class_name' => Array
(
    'EVERYONE' => [],
    'ANONYMOUS' => [],
    'USERS' => Array
    (
        'midcom:create' => MIDCOM_PRIVILEGE_ALLOW,
        'midcom:update' => MIDCOM_PRIVILEGE_ALLOW
    ),
)

Type

array

$_privileges_cache

$_privileges_cache : Array

Internal cache of the effective privileges of users on content objects, this is an associative array using a combination of the user identifier and the object's guid as index. The privileges for the anonymous user use the magic EVERYONE as user identifier.

Type

Array

$_content_privileges_cache

$_content_privileges_cache : Array

Internal cache of the content privileges of users on content objects, this is an associative array using a combination of the user identifier and the object's guid as index. The privileges for the anonymous user use the magic EVERYONE as user identifier.

This must not be merged with the class-wide privileges_cache, because otherwise class_default_privileges for child objects might be overridden by parent default privileges

Type

Array

Methods

__construct()

__construct(\midcom_services_auth  $auth) 

Constructor.

Parameters

\midcom_services_auth $auth

register_default_privileges()

register_default_privileges(array  $privileges) 

Merges a new set of default privileges into the current set.

Existing keys will be silently overwritten.

This is usually only called by the framework startup and the component loader.

If only a single default value is set (type integer), then this value is taken for the default and the owner privilege is unset (meaning INHERIT). If two values (type array of integers) is set, the first privilege value is used for default, the second one for the owner privilege set.

Parameters

array $privileges

An associative privilege_name => default_values listing.

get_default_privileges()

get_default_privileges() : Array

Returns the system-wide basic privilege set.

Returns

Array —

Privilege Name / Value map.

get_owner_default_privileges()

get_owner_default_privileges() : Array

Returns the system-wide basic owner privilege set.

Returns

Array —

Privilege Name / Value map.

get_user_id()

get_user_id(mixed  $user = null) : string

Determine the user identifier for accessing the privilege cache. This is the passed user's identifier with the current user and anonymous as fallback

Parameters

mixed $user

The user to check for as string or object.

Returns

string —

The identifier

privilege_exists()

privilege_exists(string  $name) : boolean

Validate whether a given privilege exists by its name. Essentially this checks if a corresponding default privilege has been registered in the system.

Parameters

string $name

The name of the privilege to check.

Returns

boolean —

Indicating whether the privilege does exist.

get_privileges_by_guid()

get_privileges_by_guid(string  $object_guid, string  $object_class, string  $user_id) : Array

Returns a full listing of all currently known privileges for a certain object/user combination.

The information is cached per object-guid during runtime, so that repeated checks to the same object do not cause repeating checks. Be aware that this means, that new privileges set are not guaranteed to take effect until the next request.

Parameters

string $object_guid

A Midgard Content Object's GUID

string $object_class

A Midgard Content Object's class

string $user_id

The user against which to check the privilege, defaults to the currently authenticated user. You may specify "EVERYONE" instead of an object to check what an anonymous user can do.

Returns

Array —

Associative listing of all privileges and their value.

can_do_byclass()

can_do_byclass(  $privilege,   $user,   $class) 

Parameters

$privilege
$user
$class

can_do_byguid()

can_do_byguid(string  $privilege, string  $object_guid, string  $object_class, string  $user_id) : boolean

Checks whether a user has a certain privilege on the given (via guid and class) content object.

Works on the currently authenticated user by default, but can take another user as an optional argument.

Parameters

string $privilege

The privilege to check for

string $object_guid

A Midgard GUID pointing to an object

string $object_class

Class of the object in question

string $user_id

The user against which to check the privilege, defaults to the currently authenticated user. You may specify "EVERYONE" instead of an object to check what an anonymous user can do.

Returns

boolean —

True if the privilege has been granted, false otherwise.

_get_class_magic_privileges()

_get_class_magic_privileges(string  $class, mixed  $user) 

Load and prepare the list of class magic privileges for usage.

Parameters

string $class

The class name for which defaults should be loaded.

mixed $user

The user to check

_get_user_per_class_privileges()

_get_user_per_class_privileges(  $classname,   $user) 

Parameters

$classname
$user

_load_privileges_byguid()

_load_privileges_byguid(string  $object_guid, string  $object_class, string  $user_id) 

Returns a full listing of all currently known privileges for a certain object/user combination (object is specified by guid/class combination)

The information is cached per object-guid during runtime, so that repeated checks to the same object do not cause repeating checks. Be aware that this means, that new privileges set are not guaranteed to take effect until the next request.

Parameters

string $object_guid

A Midgard GUID pointing to an object

string $object_class

DBA Class of the object in question

string $user_id

The user against which to check the privilege, defaults to the currently authenticated user. You may specify "EVERYONE" instead of an object to check what an anonymous user can do.

_collect_content_privileges()

_collect_content_privileges(string  $guid, string  $user_id, string  $class) : Array

Collects all privileges applicable for a user over a content object.

It takes the complete content object tree (using the get_parent methods) into account and merges the privileges according to mRFC 15. It will only look at privileges that are applicable to the current user, with user privileges taking precedence over group privileges.

Known Issue: Currently, group privileges are merged in no particular order, in reality they should at least take the group tree into account when merging. They do take the "depth" into account correctly though when looking at a single group chain.

This function tries to operate on GUIDs whenever possible, to keep runtime up, only in the case of nonpersistent objects (no valid GUID yet), it will revert to regular object usage.

Parameters

string $guid

The GUID for which we should load privileges.

string $user_id

The MidCOM user assignee for which we should collect the privileges.

string $class

The DBA classname

Returns

Array —

An array of privilege_name => privilege_value pairs valid for the given user.

_load_content_privilege()

_load_content_privilege(string  $privilegename, string  $guid, string  $class, string  $user_id) : boolean

Look up a specific content privilege and cache the result. This implements the same semantics as _collect_content_privileges, but only works for one privilege at a time, which allows us to cut a few corners and therefore be faster

Parameters

string $privilegename

The privilege to check for

string $guid

A Midgard GUID pointing to an object

string $class

DBA Class of the object in question

string $user_id

The user against which to check the privilege, defaults to the currently authenticated user.

Returns

boolean —

True when privilege was found, otherwise false

get_parent_data()

get_parent_data(  $guid,   $class) 

Parameters

$guid
$class

_can_do_internal_sudo()

_can_do_internal_sudo(string  $privilege) : boolean

This internal helper checks if a privilege is available during internal sudo mode, as outlined in the corresponding variable.

Parameters

string $privilege

The privilege to check for

Returns

boolean —

True if the privilege has been granted, false otherwise.