wiki:CubeSecurityProposal

Cubical security

This is a security design proposal by fluffy. It's a work in progress and doesn't reflect the current code.

Right now there is only one entry point into the security system: security_query(user, action, object) in common/security.php. It takes and user, action(as a string) and an object and returns whether the user is allowed to do that action on the object. All other security eventually goes through it and the function is properly logged. I say we keep the function but turn it into a database query.

The cube

All the possible values of that function form a cube with users, actions and objects as the three edges. Such a cube would be prohibitive to store in the the database and contain a lot of duplicates so we group elements on the edges (users, actions, objects) and build the cube with allow/deny on subcubes. The ia_security table contains a bunch of rules like:

  • Actions(int): A group of actions
  • Users(int): A group of users
  • Objects(int): A group of objects
  • Priority(int): The priority of applications. Higher priorities completely override lower priorities.
  • Allow/Deny?(bool): This should be obvious.

If the table is sorted like that (desc on priority) then the following query would be blazing fast:

SELECT allow FROM ia_security
    WHERE action_group = %d AND user_group = %d AND object_group = %d
    SORT BY priority DESC LIMIT 1

It could become so incredibly fast we might even use it to filter objects for a certain use in SQL. This is a remarkable and difficult feature, even though we could live without it.

Groups

For this to function properly (and insanely fast) we need an ia_object_group table which maps every object to ALL the groups it's part of. Recursive groups (which include, but don't exclude other groups) can be implemented by storing a bool for every field which say whether it's a direct or indirect inclusion. Here is a proposed table design:

  • Object type(enum): If the object is an user, task/ group, etc.
  • Object id(string): Object unique identifier in it's own namespace. Numerical id's for everything would be faster, GUIDs would be even faster (no namespaces).
  • Group(int): The group the object is part of.
  • Direct inclusion(bool): If this is a direct inclusion, and should be shown in the UI.

When we add/remove an inclusion we're actually adding/removing a link in a DAG, and might be slow and difficult, but only administrators should be able to tweak it anyway. Cycles are NOT an issue. Careful sql and proper usage can alleviate most issues: For instance we grant textblock-view to anonymous-users on public-pages, we don't grant it to users-who-can-view-public-pages on pages-visible-by-anonymous-user as that would create an inclusion explosion.

Because all inclusions are stored and with careful indexing the following query is really, really fast:

SELECT allow FROM ia_security
    WHERE 
      action_group IN (SELECT group FROM ia_object_group
          WHERE type='action' AND object='%s') AND
      user_group IN (SELECT group FROM ia_object_group
          WHERE type='user' AND object='%s') AND
      task_group IN (SELECT group FROM ia_object_group
          WHERE type='%s' AND object='%s')
    SORT BY priority DESC LIMIT 1

The inner queries should all be evaluated into tiny temporary tables only once. If storage order in ia_object_group is 'type', 'object', 'group' all DESC then they only involve seeking in the btree and reading the temp table at once. This is query can get so fast in a proper db that you might actually JOIN on it when browsing objects.

Interface

It's important to point out that we don't need to expose groups when editing objects. We can make little dropdowns for common options (like private/protected/public in textblocks) and add a complex option which shows a full editor. We don't even a reusable security editor, the complex option can just tell you to use a separate editor. We could also make do with an input box which contains a list of groups separated by space.

In code, all we have to do is call task_set_security_groups($task_id, $groups).

Good and bad points

Bad points:

  • Heavy on the database, mysql might not be up to it.
  • Requires large changes.

Good points/Rebukes:

  • Large changes can be tested.
  • task-adunare-grader group?
  • Round registration based on round-preoni-contestants?
  • Give preoni-task-authors access to preoni-tasks?

Implementation

For a first implementation it can be vastly simplified:

  • Attachments can depend on their textblocks w/o the db knowing.
  • Object security editor: input box with list of groups. For textblock, user, task, round, only visible to adimn
  • We can skip recursive groups.
  • Edit rules with phpMyAdmin. Booo-yah.

Unresolved/Debatable? issues

  • Should we absolutely require a group for every object? Speed and simplicity says yes, non-existant storage limitations say no.
  • Separate action/user/object groups? Code says a loud NO, but sometimes it might be confusing, especially with users.
  • If not used properly, the group inclusion system could take Obj * Group space. What about dropping recursive groups?
  • Globally unique identifies for all objects in the database would remove the need for namespacing and self-groups.