Leonidas: Permissions

Keeping users away from the danger buttons

One of the challenges the previous systems had involved locking down certain features by user. For the most part, security was done through not sharing the link or wrapping each feature in an if-statement that checked the logged-in user’s ID against a hard-coded list. A primary requirement of Leonidas was to avoid this at all costs.

My initial plan was the Unix route: a big 64-bit bitmask that would go in the users table. I quickly realized we would have way more than 64 permissions, though. I settled on a permissions list with easy-to-read constant values instead of obscure numbers. From there, I implemented a global variable, $_access, that would contain all of the individual permissions for the logged in user. $_access gave way to two important methods: can() and require():

1
2
3
4
5
6
7
8
// This will throw a 403-forbidden error and log
// the invalid access in a table
$_access->require(Permissions::THIS_PAGE_READ);

// Check if user has permission for a small feature
if ($_access->can(Permission::THIS_PAGE_CREATE)) {
    // Provide the gated feature
}

Sticking to a CRUD (Create, Read, Update, Delete) suffix naming convention, it is very easy to remember and later read what the permission is protecting. As of this writing, we are up to 140 permissions! Here is a quick snippet of the Permission class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Permission
{
    // ...

    public const PROJECT_AUTO_CREATE = 'PROJECT_AUTO_CREATE';
    public const PROJECT_CREATE      = 'PROJECT_CREATE';
    public const PROJECT_UPDATE      = 'PROJECT_UPDATE';
    public const PROJECT_DELETE      = 'PROJECT_DELETE';
    public const PROJECT_PROFIT      = 'PROJECT_PROFIT';
    public const PROJECT_PROJECTIONS = 'PROJECT_PROJECTIONS';

    // ...
}

As you can see, I am storing the textual representation instead of something like Go’s iota, or incrementing number. I expected to spend a lot more time inside the database and wanted things like permissions to parse easily instead of having to cross reference with the Permission class.