Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with user permissions when dealing with admin system users and front-end website users?

Can anyone recommend a source of information or working example showing how to deal with access permissions when dealing simultaneously with admin system users and website users?

Our code at the moment is kind of like a big version of an online shop, with several members of staff administrating the orders using the admin system, and any number of customers using the website. The admin system is built around an access control list (like LiveUser) but there is a fairly big 'band-aid' holding that together with one 'dummy' user holding the role of all website users (all customers).

Ideally the application code will use phrases like can_place_order() to determine if the user can perform a task. A customer can place an order as long as the stock levels are at least as much as the order quantity and they have made a payment to the value of the order. An admin user can place an order as long as they have the 'customer_services' role.

Ideally, we'd like to extend the system so that we can have certain registered customers place an order without having sufficient stock or paying, simply having an invoice and backorder generated instead. That would be at least two customer 'roles' in addition to the many staff 'roles'...

  • How can I use the same permissions system with two separate lists of users?
  • Or, how can I combine the apparent two sets of users into one list?

We're using php & MySQL for the application, but any other language patterns are welcome.

like image 235
Bendos Avatar asked Oct 09 '22 23:10

Bendos


1 Answers

I've tried a lot of different schemes for permissions and I've only ever found one way to make them work that satisfies the programmer AND the clients in nearly all cases. And this is to make them partially data driven.

My most-recent attempt works like this:

  1. Each user has attached to it a permissions object. In my case created automatically when a user is requested. (In my case, permissions can be different for different sections. So a user might be allowed to do X for Y but not for Z.)
  2. Each page, code block, or view that needs to be permissive is wrapped with an if statement that checks the permission object. If that permission needs to worry about the section too, the current relevant section ids are passed in as an array, returning a new array of bools to match.
  3. The interface then does not expose this complicated mess to the users directly, instead a superadmin interface lets new user types be created. These types carry sets of permissions that will be enabled for that 'type' of user. There can be overlap in the permissions of different types, so both the Admin and the Editor might be able to "Edit Copy" or whatever.
  4. The normal admin interface lets individual users be set as different user types for different sections. So one user may be an Admin for section 2, and an Editor for sections 2, 3, 4, and 5. They can also be set globally which overloads an unused section (0).

This has many benefits and one drawback. First, the drawback. Because these keys are baked directly into the code, only a superadmin (aka a developer) should have access to that part of the interface. Indeed, an interface may not be needed at all, as the list of permissions should only change with the code. The real problem here is that you won't know anything is wrong with a title until your run the code, as the syntax will be just fine. Now, if this was coded in something like C#, this would be a very serious issue. But just about everything in PHP is not type safe, so it is really just par for the course. These values could also be loaded from a config file, or even baked into the GUI that you build user types from, although that seems wrong.

What you gain from this is the ability to setup new permission types on the fly. You gain the ability to rename those permissions with relative ease. (As the system just uses the number, the title is only used to capture the number so can be changed easily in just a few places.) And the code is very simple to use. It would look something like this:

if($current_user->permissions->can("View Sales Records"))
{
    //Code to view sales records
}

Or slightly more complicated

$sections = array(1,2,3,4); //Probably really a list of all sections in the system
$section_permissions = $current_user->permissions->these($sections)->can("Edit Section");
foreach($sections as $s)
{
    if($section_permissions[$s])
    {
        // Code for maybe displaying a list of sections to edit or something
    }
}

The method chaining is pretty simple too. ->these() sets an internal array to those values and then returns a reference to the object itself. ->can() then acts on that list if it exists, and then unsets it. In my own version I also have ->any() which always returns a complete list of sections, so I can check ->any()->can().

Finally, the permission object also contains a list of what types a user is. Which makes it really easy to show a list of users and what permissions they have active. In my case we just use ->types() to access that list an an array of ids => names.

Non-present permission titles are simply ignored. They are also matched in lowercase and with spaces and special characters removed to help decrease problems with typing errors. I've considered even doing a levenshtein distance check and picking the closest match. But in the end it is probably better not to rely on something like that. (maybe log the error, but gracefully select the nearest match.)

Outside of the code and in the interface, the users only need to relate to each other in a So-in-so is an Admin, Editor, Publisher, Writer, and CEO, so on so on. It would also be trivial to create different sets of user types for different organizations.

All parts of this can be heavily cached, setting up a basic user for people who aren't logged in at all is very easy, and I've yet to run into a permission-based issue that isn't obvious to solve using this structure.

I only wish I could share the actual code with you. I just don't own it myself and therefore can't post it.

like image 81
DampeS8N Avatar answered Oct 13 '22 10:10

DampeS8N