|Version 9 (modified by benoitg, 6 years ago)|
- New user permission architecture, General model
- Handling insufficient permission
- Data model
Original author: Benoit Grégoire, last modified: 2006-12-05
New user permission architecture, General model
Why our own rights and permissions framework
We couldn't find one that allowed tying rights to objects instances (and not classes). Otherwise, LiveUser would have almost done the trick.
Stakeholders (Role and permission types)
Each role and permissions (rights) are relevant to one of the following object type or static object:
These define the stakeholder types.
Note: The right to access a specific Content type will be a network right. This way, we can define a "Beginer editor" and "Advanced editor" roles
Permissions are defined in the code, all in a big PHP lookup table to allow easy translations.
$PERMISSIONS['PERMISSION_ID'] = array(_("permission description"), STAKEHOLDER_TYPE);
These roles id's must also be listed in a database table for relationships. The database table will be generated by a synchronization script to keep it in sync. An elegant to make sure it stays in sync is to run the script inside the catch block of an exception thrown when there is a mismatch, easing the maintenance burden (only code needs to be modified when a permission type is added or deleted) at no performance cost.
An example of permission would be NODE_PERM_EDIT_METADATA. Being a Node permission type, the user who get's this permission (a node stakeholder) gets to be allowed to edit the metadata of the specific node instance that this permission applies to.
Roles are administrator-defined groups of permissions of the same type as the role (Node, Network, etc.). For example, if we define the "Tech support" Node role, we can assign various Node permissions to this role (but only node permissions).
If we then assign a user the "Tech support" role for a specific node, that user becomes a "Tech support stakeholder" for that node.
User types (system roles)
While administrators can define new roles, there is a need for a few SYSTEM defined roles that are always present.
- Anyone (anonymous)
- maybe just special-case an ID, like SPLASH_ONLY_USER?
- Exists for server, network, node, etc.
- Logged-in user
- Validated users
- Users in validation
User validity levels (Allowed, Validation, etc.) will map to system roles, and may eventually be deprecated.
To keep things simple, there is no concept of groups in the classic sense. Roles allow doing much the same thing, and are simpler to implement.
To keep things simple, defining implicit permissions (such as making NETWORK_EDIT_ANY_NODE imply NODE_PERM_EDIT_METADATA for each node) will not be supported. While this is a desirable feature, implementing a simple UI for this is not easy, and since most permissions apply to specific objects, the code to check them would be complex and have low performance.
This means that when using permissions in the code, BOTH will have to be specified as allowed. The API will be written to make this easy.
Note that once again, properly defined roles do much the same thing. (For example, a node owner can have a certain number of implicit node permissions).
It would be extremely useful to allow an administrator to masquerade as another user for testing and tech support purposes. Logging out would then get him back to his original user. As user handling is centralized, this should be reasonably easy to implement.
Handling insufficient permission
In the common case, missing permissions should be handled through exceptions. This will solve two annoying problems, while getting rid of just about all current permission error handling code:
- If the user is not logged in (because of a timeout, or never logged-in), allow him to login.
- If the user doesn't have the necessary permission(s), inform him (telling which permission is missing) and allow to login as another user.
In both cases, if the user successfully logs-in (with the rights permission), the get and post parameters are regenerated and the original operation is attempted again.
In think the simplest and cleanest way is to use set_exception_handler() to handle all exceptions outside MainUI, by calling a new MainUI with the prettily formatted error message and the request-repetition code.
- The downside is that all access control exceptions are now fatal.
- The upside is that it's really easy not to get stuck in the middle of a half-rendered page, and that we don't have to wrap all the top level scripts in try-catch blocks.
Probably only two classes: Roles and Security These function are to be prefered to the next ones, as they will throw exceptions to allow the user to login (or login as another user) to retry the operation:
Security::requirePermission($permission, $target_object, $user); Security::requireEitherPermissions(array $permission, $target_object, $user); Security::requireAllPermissions(array $permission, $target_object, $user);
Internal implementation, and corner cases (gateway interaction maybe)
Security::hasPermission($permission, $targetObject, $user); //user is optional, if unspecified, the current user is used. User can also be null, meaning that there is no user currently logged-in Security::hasRole($role, $targetObject, $user); //user is optional, if unspecified, the current user is used. User can also be null, meaning that there is no user currently logged-in Security::hasEitherPermissions(array $permission, $targetObject, $user); Security::hasAllPermissions(array $permission, $targetObject, $user);
Mostly for reporting
Security::getUsersWithPermission($permission, $targetObject); //Return list of users with this permission Security::getUsersWithRole($role, $objectId); //Return an array of users with the given role. If objects_id is null, all users with the specific role are returned, along with an array of objects for which they have this role. Maybe this function won't actually be implemented, as it's there mostly for reporting and sending notification of hotspots going down. Security::getPermissions($user); //returns array of PERMISSION constants for this user.
- permission_id REFERENCES permission_type
- stakeholder_table (Implicitely gives the object type)
- role_name (langstring_id ?)
- role_id REFERENCES roles
- permission_id REFERENCES role
There is a check constraint that verifies that the role and permission type matches.
One table per stakeholder type Node (ex: Node tech support, Node owner, etc.) Network (ex: User allowed to login multiple times, Content curator (can edit other people content(, Content editor, etc.) Server Content
Note that the stakeholder tables list user-role pairs (and NOT user-permission pairs)
Problems to be solved by this
Example of things this will allow (this is only the list from Max Horvath, there are many more things waiting for this).
- Allow owners to have more granular permission to edit content (some can edit login, some not, etc.)
- Restrict user access to a single node (user_can_access_all_nodes, netowork permission)
- Fon-like: user can login only if his node is online
- Time limited internet usage
TODO: Discuss where this fits into auto-node creation