$auth
$auth : \midcom_services_auth
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'):
On users and groups during authentication (when building the basic privilege set for the user, which applies generally):
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.
MidCOM Core Privileges
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:
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.
$auth : \midcom_services_auth
$_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.
$_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 ), )
$_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.
$_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
__construct(\midcom_services_auth $auth)
Constructor.
\midcom_services_auth | $auth |
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.
array | $privileges | An associative privilege_name => default_values listing. |
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
mixed | $user | The user to check for as string or object. |
The identifier
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.
string | $name | The name of the privilege to check. |
Indicating whether the privilege does exist.
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.
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. |
Associative listing of all privileges and their value.
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.
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. |
True if the privilege has been granted, false otherwise.
_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.
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(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.
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 |
An array of privilege_name => privilege_value pairs valid for the given user.
_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
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. |
True when privilege was found, otherwise false
_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.
string | $privilege | The privilege to check for |
True if the privilege has been granted, false otherwise.