Team-Tea-Time/laravel-forum

Request: forum responsibility separation

DevHoracioRodriguez opened this issue · 7 comments

Hello Riari

If possible, I would like to have some forum responsibility separation. The idea is to keep the forum simple (as is) but at least have some restrictions or privileges depending the user role like Admin and Moderator. Please, let me know if this is possible.

Here is an example of role separation:

  1. Admin role, to be able to view, create, edit, delete and restore any category, thread and post from any user including soft delete. This would be the Power user!

  2. Moderator role, to be able to edit and delete any category, thread and post - not including Admin publications. Not able to view Soft Deleted categories, threads and posts from other users.

  3. Any other role, basically been responsible for only own posts. Not able to view Soft Deleted categories, threads and posts from other users.

Riari commented

Hi,

This is supported by the package, but the implementation of permission management (including roles) is up to you. This is because there are lots of different ways to manage roles and permissions, so instead of the package shipping with an opinionated solution, it simply uses Laravel Authorization to provide policies for all category, thread, and post actions. You just need to override those policies to customise the logic.

See the package docs for further details. Once you have your policies defined, you can override the methods to do whatever role/permission checks you need. A popular solution for this is to use Spatie's Laravel Permission package, which works nicely alongside Laravel Authorization policies.

You can also build a rudimentary role/perm system yourself with simple Role and Permission models (using a many-to-many relationship between User and Role, then another between Role and Permission). With this approach, you can run queries on users to see if they have a given permission (a has-many-through relationship on the User would be useful for this).

Totally up to you which approach you use :)

My knowledge isn't there yet to create my own solution. So, since you mentioned Spatie's Laravel Permission package, it seems like the way to go. Will see...

Thank you for the useful information.

Riari, unfortunately Spatie's Laravel Permission package isn't compatible - prerequisites. Wave already has roles and permissions. Anyway, would it be too much asking for an example following package docs? I read the documentation but I still don't get what I need to do. I guess that will be great if I can figure out how to define policies, and override methods to check role/permission without adding another package.

Riari commented

Sure, I can give you a quick example. Let's say you want to limit who can delete posts - here are the steps you'd follow:

Step 1: Create policy classes

There are two policy methods relating to post deletion:

  • ThreadPolicy::deletePosts: mostly used in views to determine whether or not to display bulk post deletion controls (e.g. the checkboxes next to each post).
  • PostPolicy::delete: used in combination with the above method to determine if a specific post can be deleted (this does a simple "is the user the author of the post" check by default).

You'd probably also want to override the ThreadPolicy::restorePosts and PostPolicy::restore methods, which mirror the ones above.

With that in mind, you'd need to override both policies. To do that, you can create each policy either manually or by using the php artisan make:policy command:

php artisan make:policy ForumThreadPolicy
php artisan make:policy ForumPostPolicy

Doing this will place new policy classes in App\Policies.

Step 2: Extend the original policies

You should make your newly created policies extend the original ones (so that any methods you don't override are still accessible):

<?php

namespace App\Policies;

use TeamTeaTime\Forum\Policies\ThreadPolicy;

class ForumThreadPolicy extends ThreadPolicy
{
}
<?php

namespace App\Policies;

use TeamTeaTime\Forum\Policies\PostPolicy;

class ForumPostPolicy extends PostPolicy
{
}

Step 3: Override methods

For each policy method you want to override, you can copy the original method into your corresponding new policy class to use as a starting point:

You can then replace the logic with whatever checks you want to do for each method. For this example, let's say you have a delete_forum_posts permission that you've granted to admins and moderators (but not to regular users). I've never used Wave, but looking at their docs, I think this is how it would look:

ThreadPolicy

    public function deletePosts($user, Thread $thread): bool
    {
        return Voyager::can('delete_forum_posts');
    }

    public function restorePosts($user, Thread $thread): bool
    {
        return Voyager::can('delete_forum_posts');
    }

Here, we use the same permission used in both methods, because you might just want to keep it simple by saying anyone who can delete posts can also restore them.

PostPolicy

    public function delete($user, Post $post): bool
    {
        return Voyager::can('delete_forum_posts') || $user->getKey() === $post->author_id;
    }

    public function restore($user, Post $post): bool
    {
        return Voyager::can('delete_forum_posts') || $user->getKey() === $post->author_id;
    }

Here, we use the same permission again, but also keep the author check as a fallback. This is essentially saying "the given user can [delete/restore] this post if they are generally permitted to delete posts OR if they are the author of the post".

Step 4: Specify overrides in config

This is just a matter of updating the forum.integration.policies value. Specifically, you'd replace the thread and post policy classes with the ones you created, so it should look like this:

    'policies' => [
        'forum' => TeamTeaTime\Forum\Policies\ForumPolicy::class,
        'model' => [
            TeamTeaTime\Forum\Models\Category::class => TeamTeaTime\Forum\Policies\CategoryPolicy::class,
            TeamTeaTime\Forum\Models\Thread::class => App\Policies\ForumThreadPolicy::class,
            TeamTeaTime\Forum\Models\Post::class => App\Policies\ForumPostPolicy::class,
        ],
    ],

After following these steps, the forum package should be using your custom policy methods. You might want to override all of the policies and explicitly define every method so that all policy checks use your custom logic, but they should still extend the originals (as in step 2) in case a future package update introduces more policy methods.

Let me know if anything is unclear.

Hello

Thank you for this pretty good example. It helped me! This is what I ended with:

'policies' => [
    'forum' => App\Policies\TeamTeaTime\LaravelForum_ForumPolicy::class,
    'model' => [
        TeamTeaTime\Forum\Models\Category::class => App\Policies\TeamTeaTime\LaravelForum_CategoryPolicy::class,
        TeamTeaTime\Forum\Models\Thread::class => App\Policies\TeamTeaTime\LaravelForum_ThreadPolicy::class,
        TeamTeaTime\Forum\Models\Post::class => App\Policies\TeamTeaTime\LaravelForum_PostPolicy::class,
    ],
],

First, I started to work with LaravelForum_ForumPolicy. When I return a true or false, works like a charm. The blade shows the results as expected. Now, when I try to return something like Voyager::can('add_forum_categories') it gives me the following error: https://flareapp.io/share/yPaONXp5#F70 .... Do you have an idea of what I'm missing?

As you can see in the code, as an alternative, I'm returning $user->can('add_forum_categories') assuming that I'm connecting to or using Voyager roles/permission, but doesn't work either. I contacted devdojo and I expect an answer during the next couple of days.

So, the idea is to extend laravel-forum classes (done) and use Voyager role/permissions (pending). This last part is what I'm trying to figure out.

If you can give me any clues will be greatly appreciated.

Riari commented

I would expect the $user->can method to work, but as I've never used Wave or Voyager before, I'm not sure how it integrates with that method as it's also provided by Laravel itself.

In order to use the Voyager facade, you need to either create an alias for it in your config/app.php, or specify a use statement for it (this is considered better practice):

use TCG\Voyager\Facades\Voyager;

Riari, thank you for your help.

Devdojo was able to get it working with the following: return $user->hasPermission('add_forum_categories');

So rather than using the Voyager facade or the can() helper function, you can just do that with the $user->hasPermission method!

Since there are some configuration required by Devdojo (Voyager BREAD), here is the link of the original question with devdojo explanation related to roles and permissions: https://devdojo.com/question/role-and-permissions

At last, here is the final solution. BREAD must have empty Modals, and the new file policy to extende laravel-forum policies must use the model. Here are the details: https://devdojo.com/questions/bread

Once again, thank you.