Jump to content

Strategy to define permission state


NotionCommotion

Recommended Posts

I am working on niche documentation management application (PDF, Excel, etc), need to set up access permissions, and will be creating classes, persisting them in a DB, and transmitting them via a REST API using JSON.

For my application, group is the strongest, then owner, and finally public.

For example, maybe I want to allow everyone to read and create, owner (and group) to update, and only group to delete.

One way to do it is like the following, and have first positive prevails (i.e. first check if public can delete, then check if owner can, and finally determine that only group can do so).  With this approach, I would ignore that owner.read is false because public.read already allows.  Also, it doesn't really make sense for owner to be able to create because there is no document in the first place.

{
    "public": {
        "read": true,
        "create": true,
        "update": false,
        "delete": false
    },
    "owner": {
        "read": false,
        "create": true,
        "update": true,
        "delete": false
    },
    "group": {
        "read": true,
        "create": true,
        "update": true,
        "delete": true
    }
}


Alternatively, I could do the following (and while not really important, a personal added benefit is getting to try PHP8's enumerations for the first time).

{
    "read": "public",
    "create": "public",
    "update": "owner",
    "delete": "group"
}

The anticipated workflow is for a user who belongs to a given tenant to first create a "project" and add access requirements to it and for all documents added to that project to be subject to those access requirements.  To minimize user interaction, there will be a default tenant permission prototype which will be cloned and injected in the project and the user can then make changes to the project permission without changing the default prototype (the default tenant permission prototype can also be changed so that future projects will use the new settings, and permission will have a one-to-one to both tenant and project).

Our access control logic has been fairly thought out, but not completely and I am certain there will be some tweaks.  While currently, there is no requirement for individual documentation access permissions, maybe doing so will some day in the future be required, and would like to allow some flexibility.

Also, there is no real need for the classes, database structure, and JSON requests/responses to mimic each other unless it makes it simpler to implement and maintain.

Any thoughts of whether one of the two above approaches or some other approach should be taken?  Thanks

Link to comment
Share on other sites

If you also wanted permissions on some sort of "container" then you've basically discovered the Unix permissions system.

Can you say that, for each permission, "if public can do X then owner and group can" as well as "if owner can do Y then group can"? If so then you have a hierarchy-based model and your system needs only know the lowest level in the hierarchy to permit the action - the second example you have.
If not then you probably have a role-based model and you'll likely end up granting individual permissions to each group - the first example.

In other words, first you need to know at a high "business" level what the permissions system must be capable of supporting: how people and groups relate to each other. If it isn't clear what that needs to look like, consider looking at existing permissions systems (which are mostly hierarchy- or role-based), see which seems most appropriate, and copy it.

Link to comment
Share on other sites

Laminas has a flexible generic acl component based on roles and permissions.  You might consider using it and save yourself a lot of time?  https://docs.laminas.dev/laminas-permissions-acl/usage/

Laminas was formerly Zend Framework, and has a lot of well known PHP developers working on it.  It's highly de-coupled, even to the degree that the acl system leaves storage and retrieval of your acl's so you can use it with any scheme you might currently have for persisting data.

Link to comment
Share on other sites

  • 2 weeks later...
On 3/9/2022 at 10:56 AM, requinix said:

If you also wanted permissions on some sort of "container" then you've basically discovered the Unix permissions system.

Can you say that, for each permission, "if public can do X then owner and group can" as well as "if owner can do Y then group can"? If so then you have a hierarchy-based model and your system needs only know the lowest level in the hierarchy to permit the action - the second example you have.
If not then you probably have a role-based model and you'll likely end up granting individual permissions to each group - the first example.

In other words, first you need to know at a high "business" level what the permissions system must be capable of supporting: how people and groups relate to each other. If it isn't clear what that needs to look like, consider looking at existing permissions systems (which are mostly hierarchy- or role-based), see which seems most appropriate, and copy it.

Thanks requinix, This access control thing gets complicated fast.  Yeah, was using Unix permissions for inspiration (wish I could say I independently discovered it!).  For some cases, I have containers (i.e. documents live in a project container) but don't have infinite hierarchy like Unix files have and other entities don't have this concept at all.  But assigning a given entity type to one model today makes changing tomorrow difficult so "maybe" implement some generic approach today...  I am sure that Linux doesn't use SQL but likely some c++ or even assembly language code to do similar as the following.  Not sure where this rabbit hole is leading me...

AbstractControlledEntity
- id
- owner (FK to User.  not null?)
- permission (initially an object but maybe future a byte for optimization)
- discriminator

SomeResource extends AbstractControlledEntity

User
- id
- permission
- other_properties

Group
- resource_id
- user_id
- permission

 

Link to comment
Share on other sites

On 3/10/2022 at 9:58 PM, gizmola said:

Laminas has a flexible generic acl component based on roles and permissions.  You might consider using it and save yourself a lot of time?  https://docs.laminas.dev/laminas-permissions-acl/usage/

Laminas was formerly Zend Framework, and has a lot of well known PHP developers working on it.  It's highly de-coupled, even to the degree that the acl system leaves storage and retrieval of your acl's so you can use it with any scheme you might currently have for persisting data.

Thanks gizmola,

Laminas use of resources does a good job implementing my concept of groups.  I also like how it provides flexibility regarding role inherence and also allows one to accommodate entity ownership.  While I am always nervous about needing to integrating yet to another 3rd party platform, I like how decoupled it is and that it doesn't even deal with persistence.  I see it only has 25 stars on github, however, if it meets the task at hand, not an issue.  Questions/uncertainties/concerns I have with it are:

  1. Must the ID returned by Resource::getResourceId() and Role::getRoleId() be unique only for the given entity type or across all entities?  For instance, can Project, Document, and SomethingElse all provide an ID of #123?  I expect not and will likely need to implement either some super/sub DB schema or store some identifier hash of each, but don't know for sure.
  2. Best approach to database persistence.  Per the docs, one option is to serialize the ACL object before storage.  Also, existing integration to 3rd party persistence systems exist.  Guess I could figure this out after getting the above figured out other parts first.
  3. How can it be used to filter multiple records directly from a DB without first creating objects of each first?  While I acknowledge that I might need to re-think my workflow if using Laminas, I would like to use my original example.  I have Projects which have permissions assigned to it, and therefore will create a resource for each (i.e. resource_project_123).  Then, I want to add users to the project to allow additional access, but can't add users but only roles, so I create a new "member_of_project_123" role and give that role permissions on resource_project_123 as applicable, and assign resource_project_123 to User #321.  But this just doesn't seem right...  Regardless, so, now User #321 logs on and requests the list of all documents, and I need to create a query which will only return records allowed by $acl.  The documentation shows examples of whether the user can perform some operation on a given entity only.
Link to comment
Share on other sites

I've no experience with Laminas or any other common ACL package, but based on a quick look at the Laminas site here's some thoughts.

5 hours ago, NotionCommotion said:

Must the ID returned by Resource::getResourceId() and Role::getRoleId() be unique only for the given entity type or across all entities?

It would need to be unique with an ACL.  If you wanted to have separate ACLs for your Project/Document/Something else entities you could do that and use the entity IDs directly.  Otherwise, you'd need to augment them in some way.  One relativly simple way might be just to combine it with the entity class name, eg:

class Document implements ResourceInterface {
    private $id; //Entity ID from database

    public function getResourceId():string{
        return self::class.'#'.$this-id;
    }
}

 

5 hours ago, NotionCommotion said:

How can it be used to filter multiple records directly from a DB without first creating objects of each first?

This ties into your question #2, you need to persist the acl in a way that you can query against it easily.  This obviously means you can't just serialize the the ACL and stick it into the DB.  Sure, that's possible according to the docs, but not very useful for any decently large system.  The solution you linked I don't think is for the ACL specifically, just entities in general using the Laminas framework, but you could make your ACL configuration into various entities and use whatever solution you have currently to save and load them.  The resource id could be split into separate fields in the table that you can query against, for example:

select * 
from documents d
inner join acl_resource res on ar.type='document' and ar.entity_id=d.id
...

I don't remember if you were using symfony or not in your project, but if so note that it has it's own ACL bundle you could consider.  It's a little more integrated I think which could be good or bad, I don't know.  Like I mentioned, I haven't actually used any of these.

Link to comment
Share on other sites

On 3/24/2022 at 1:41 PM, kicken said:

It would need to be unique with an ACL.. I don't remember if you were using symfony or not in your project, but if so note that it has it's own ACL bundle you could consider.  It's a little more integrated I think which could be good or bad, I don't know.

Yes, I have considered Symfony's bundle.  Agree more integrated is a mixed blessing.  It too needs unique IDs for ACLs and has some helper methods which are pretty close to your proposed way to do so.  The symfony ACL bundle uses the following schema instead of coupling the permissions with the entity data as I originally planned on doing.  I found documentation on this bundle is pretty sparse and maybe shouldn't be even using them per this warning.

 

image.png.853b157974c4f3e1afcf652c6be89954.png

image.png.e43496cad76a4c3ff3b50caee1ef07f8.png

Edited by NotionCommotion
Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.