tl;dr
Is there a better approach to implement a role/permission system that works based on individual resources, without using a third party package?
Requirements I'm trying to implement
- The application uses Laravel
- In the application you can create articles
- You need the permission to create articles
- You can invite other users of the application to edit the text of the article
- Invited users can not change the title or the published state of the article, only the user that created the article can do that
- It is possible to transfer ownership, each article only has one owner
- Users can be invited to any number of articles
- Users can own any number of articles
- Admins have full control over articles, even if they are not owners
I know of spatie/laravel-permission. But I'm trying to solve as much as I can without third party packages.
Here are the roles and their permissions based on the requirements:
- Guest
- User
- Editor
- Owner
- Can edit
- Can publish
- Can change title
- Can delete
- Admin
- all of the above regardles of ownership
Approach 1
Tables:
- users
- articles
- article_user (pivot)
-
article_id
-
user_id
-
role_id
- roles
- role_user (pivot)
- permissions
- permission_role (pivot)
The table "article_user" has an extra column role_id. The role_id allows me to map the permissions associated with the role to any given action I want to allow/deny (e.g. "update-title", "publish-article", "edit-text").
This works for articles only. But now I store the relation between roles and users in two different tables and for each new model I would have to create a new pivot which doesn't seem to be a good solution.
Approach 2
Tables:
- users
- articles
- roles
- role_user (pivot)
- permissions
- permission_role (pivot)
-
permission_id
-
role_id
-
model (e.g. "App\Article")
-
model_id
The permissions table contains the columns model and model_id. It maps any given permission to the model/resource it applies to. model stores the string representation of the model (e.g. "App\Article") and model_id is the ID of the specific resource.
With this I can map permissions to any model/resource I want without the need to create new pivots for each new model that may be introduced later. But now I can't guarantee data integrity when resources get deleted because I lost the foreign key constraint. I could keep track of deletions myself (which I would like to avoid). model_id no longer refers to any specific table, but instead the column model tells me which type of resource I should query.
Approach 3
As suggested by @smnhunt in this thread, which is basically Approach 1 but it also has the drawback of not having data integrity for free since deleting a user would leave an orphan record in the json field.