
Aeacus is a powerful permissions management system that's shared across any number of applications.
Boston University uses Kerberos for all authentication, so we can easily identify who has logged into an application. Determining what that person is authorized to do is then passed off to Aeacus.
We can grant permits at a very low level, perhaps noting that a particular user is authorized to add (but not edit or delete) events on a certain calendar.
Groups of permits can then be combined to form standard roles for easy administration. Aeacus automatically adds sophistication to its user interface as the permits required get more complex. The screenshot above is for the simplest interface, where all users are administrators with the same set of privileges.
Below, we see the other extreme: where each user can have access to many parts of the application (e.g., different calendars, departments, pages) with a different access level in each.

Code Sample
For an application using Aeacus, the code is fantastically simple. Here's a simple way to decide whether to display a "delete" button:
require_once('PrivilegeManager/PrivilegeManager.php'); AccessControl::application('the-foobar-application'); if (AccessControl::has('delete', 17)) { echo '<a href="delete.php?id=17">Delete</a> }
Of course, I'll also want to verify that the user has access
in delete.php
before doing anything destructive:
AccessControl::assert('delete', $_GET['id']); // Now perform the delete (assuming a performDelete() function): performDelete($_GET['id']);
Notice that there's nothing here about handling access
errors. The ::assert
call will return only if the user
has the right access. If not, Aeacus automatically renders an error
(in HTML or JSON), which each application can customize as needed.
Checking privileges is important, of course, but how about user management? How do those beautiful pages get added to your application? Very easily:
<script src="/aeacus/main.js"></script> <div id="privilege-manager" data-privilege-manager="the-foobar-application"> (Optionally include some text here to display before Aeacus loads.) </div>
That's it! Each application can register JavaScript plugins, which tweak default behaviors, but generally this will just work out of the box.
Under the Hood
With such a simple interface, what's actually happening behind the scenes? I can't just publish the entire library, since it belongs to Boston University, but here's a peek at one of the low-level functions that gets the job done.
private function _has(/* string */ $privilege, /* string */ $subapplication = null) { $this->log(LOG_DEBUG, ' +------ _has?', " $privilege.$subapplication"); if (isset($this->_cachedHas[$privilege])) { if (isset($this->_cachedHas[$privilege][$subapplication]) || $this->_cachedHas[$privilege][null] || ($subapplication === null && count($this->_cachedHas[$privilege]) > 0)) { $this->log(LOG_DEBUG, 'verified _has', " (cache) $privilege.$subapplication"); return true; } } if ($this->_is_superadmin && $this->_allow_superadmin) { $this->log(LOG_DEBUG, 'verified _has', "SUPERADMIN $privilege.$subapplication"); return true; } if (isset($this->_cachedHasNot[$privilege][$subapplication])) { $this->log(LOG_DEBUG, 'failed! _has', " (cache) $privilege.$subapplication"); return false; } $checkers = array(); foreach($this->_other_permits as $p) { $type = $p['type']; $permit = $p['permit']; $this->log(LOG_DEBUG, ' +----- permit ', "+ «{$p['permit']->principal_type} {$p['permit']->principal}» has {$p['permit']->application}.{$p['permit']->privilege}.{$p['permit']->subapplication}"); if (!isset($checkers[$type])) $checkers[$type] = PrivilegeManager::getPrincipalType($type); if (!isset($checkers[$type])) { $this->log(LOG_ERR, '', 'No matching principal type checker for type=(' . $type . ')'); continue; } if ($permit->privilege == $privilege) { $this->log(LOG_DEBUG, ' +----- permit', '| privileges match'); if ($subapplication === null || $permit->subapplication == null || $this->subapplicationsAreEqual($subapplication, $permit->subapplication, $privilege)) { $this->log(LOG_DEBUG, ' +----- permit', '| subapplications match'); if ($checkers[$type]->userIs($permit->principal, $subapplication)) { $this->log(LOG_DEBUG, ' +----- permit', '| user matches (privilege verified)'); $this->log(LOG_INFO, 'verified permit', '', array('privilege' => $privilege, 'subapplication' => $subapplication, 'principal' => $permit->principal, 'principal_type' => $permit->principal_type)); $this->_cachedHas[$permit->privilege][$permit->subapplication] = true; return true; } } } else if ($permit->application == 'system' && $permit->privilege == 'SUPERADMIN') { if ($checkers[$type]->userIs($permit->principal, $subapplication)) { $this->log(LOG_INFO, 'setting', 'SUPERADMIN'); $this->log(LOG_INFO, 'verified _has', '', array('privilege' => $privilege, 'subapplication' => $subapplication)); $this->_is_superadmin = true; return true; } } } foreach($this->_verification_hook as $obj) { $this->log(LOG_DEBUG, ' +----- hook', ' Hook: ' . $obj); if ($obj->has($privilege, $subapplication)) { $this->log(LOG_INFO, 'verified _has', ' Hook: ' . $obj, array('privilege' => $privilege, 'subapplication' => $subapplication)); $this->_cachedHas[$privilege][$subapplication] = true; return true; } } $this->log(LOG_DEBUG, 'failed! _has', " $privilege.$subapplication"); $this->_cachedHasNot[$privilege][$subapplication] = true; return false; }
You surely noticed a lot of calls to log
in
there. Most of this logging is done at the DEBUG
level,
so we only see it when developing new applications where the permits
are less predictable, but we also record more permanently every permit
result. And when an ::assert()
fails, that's a warning.