mprythero's avatar

User Groups (Teams) & Permissions.

Hi All -

I was curious if anyone recently has come across something similar to what I describe below.

I've got three tables at the moment:

  1. Users 2)Teams 3)Teams_Users

Obviously, my pivot table is Teams_Users, with two columns (User ID and Team ID).

A user can be a member of multiple teams, but has only one set of permissions for that specific team.

I've got the relationship between the two set up and have it working in the current environment but am wondering how I can go about adding Roles to this project.

So say I've got a user, who is a member of the team, he is a site admin who can create, view, update and delete content and other users. Underneath them are a multitude of users (say we'll call them dispatchers) and these dispatchers can only create, view and update content and cannot do anything with other users.

But everyone who is assigned to a team cannot see another team's content unless they are specifically added to another team.

So I guess my questions revolve around the following:

1-Would this be a case where a three-way pivot table is necessary? 2-How would I go about setting up the middleware to first check team IDs associated with users? 3-I have noticed other posts and videos here, but they are older (one from Laravel 4), a few from last year and in January, would they still have mostly accurate information to assist in solving this project problem?

Thanks! Matt

0 likes
9 replies
Synatex's avatar

Hey MPRYTHERO!

So this in total is a really complex situation you are asking for, which is why I will just try to help with some short ideas on how to be able to accomplish your goals.

1.) This highly depends on the fact if you want a user or just the team to have an assigned role. If you want both, there are polymorphic relations (https://laravel.com/docs/5.5/eloquent-relationships#polymorphic-relations). You can create a permission table and a polymorphed relation table, which you either add to a user or team.

2.) You can set up requests for whatever you want to edit. Lets say you want to edit a team. What you can do is:

TeamController.php

    public function edit(ManageTeamRequest $request, Team $team)
    {
      // ...
    }

ManageTeamRequest.php

    public function authorize()
    {
        // Get the team from the URL
        $team = Team::findOrFail($this->route('team'))->first();

        // Check in users relation if this user is in the tam
        return $team->users->contains(auth()->user());
    }

3.) You can take a look at pre-made packages as Spatie (or maybe use it?) to get an idea of how those relations work. Laravel 4 will most likely not be working anymore. https://github.com/spatie/laravel-permission

robrogers3's avatar

Use policies -- Policies are designed exactly for this situation.

php artisan make:policy ContentPolicy.

you will then have methods for:

  • view
  • create
  • update
  • delete

you are interested in view for restricting team member access. (ps. as far as admin and dispatchers go, you can set policies for say create, etc.)

For role admin. You check his ROLE (or roles), and if role is admin every 'crud' method returns true.

Dispatchers, similar to admin. Except only create, and update, view.

Team members: return true view only if Content is for there team.

Similarly you can have an CreateUserPolicy (or what ever) here only those with Admin roles can access anything.

Now how would the policy work for any piece of content (TeamAContent [like schedule for team A]), how do know it's for TeamA or TeamB? I would tag it (tag access for the teams members). Here you check their Role (the are just a team member) and Team. They can then only view the TeamAContent or TeamBContent if they are associated with the team(s) they are in. (Team members and teams have a many to many relationship: a team member can belong to multiple teams, and a team can have multiple users -- here is your pivot table.)

If the tag(s) for ContentX include teamA or teamB tags, then they can view it

Finally, do you have to make policies for every piece of content. No you don't if you tag the content with a teamTag -- content could have a one to many: ContentY can be viewed by multiple teams.

In my opinion middleware route is not suitable because you'd need so much middleware to handle all the permissions , but Policies are designed for exactly this. Restricting access.

For a tutorial see this: https://laracasts.com/series/lets-build-a-forum-with-laravel/episodes/24

also here: https://laravel.com/docs/5.5/authorization#authorizing-actions-using-policies

1 like
mprythero's avatar

@robrogers3 -- I followed what you suggested, but because I'm still a little new to this and have to pin down some of the more finite details, I'm stuck on one issue (it appears). So I'll explain below.

In my PostPolicy.php, I have the following line:

public function view(User $user, Post $post) { return $post->team_id === 1; }

This returns true for those posts that have a team_id of 1, and not of 2, or 3 (this was just a test after multiple failures on my part).

I have my three tables with the following relevant fields:

  1. Users - id
  2. Teams_Users - team_id, user_id
  3. Teams - id

How would I make the above work to compare the current User's team ID, with that of the Team ID associated with the post?

I had a multitude of ideas, but all failed, this was what I assumed would be the proper format:

public function view(User $user, Post $post) { return $user->teams_users->team_id=== $post->team_id; }

I'm still progressing on relationships, and understand them in the models themselves, but not sure how to create a relationship here in a policy quite yet. I'm going over the tutorial video you shared again, and hopefully will have something new to go on while I wait for your response.

Best - Matt

robrogers3's avatar

First what was the failure. Always false? Or Error?

2nd have you defined your relationships. Make sure you read this:

3rd, actually watch this first: https://laracasts.com/series/how-do-i/episodes/8 (this shows you how to do polymorphic step by step). If you are not a member of laracasts join now!)

4th, create your migrations:

  • teams
  • posts
  • teams_user
  • posts_teams

5th, define your relationships.

Class User::belongsToMany('App\Team'); <-- see below about plural vs. singular i.e. A user can be a member of many teams.

public function teams()
{
    return $this->belongsToMany('App\Team') ;
}

Class Post: (don't do polymorphic first, just use regular relation.) i.e. Post->belongsToMany("App\Team');

public function teams()
{
    return $this->belongsToMany("App\Team'); 
}

Class Team:

public users()
{
    return $this->belongsToMany('App\User');
}

public function posts()
{
    return $this->belongsToMany('App\Post');
}

Now check the Policy for Post::view class PostPolicy

public function view(User $user, Post $post)
{

    return $post->users->teams->contains($user);
}

ps. change Teams to Team. Eloquent expects the model to be singular while table is plural. pps. This is not tested if it doesn't work I'll code something up. ppss. watch the video!

mprythero's avatar

@robrogers3 - My error seems to revolve around: "Trying to get property of non-object", while before was something along the lines of "You are not authorized" (though this is likely to be on my part with how I formed my previous Policy model.

I have made every change you have suggested so now I have the following tables and relevant fields:

  1. users - id
  2. teams - id
  3. teams_user - team_id, user_id
  4. posts_teams - post_id, teams_id

I have watched the video and agree with your suggestions on relationships that you suggested. I'm going to keep looking around for suggestions to this error and continue to review the videos and code until I happen to hear back from you.

I greatly appreciate all of your help! Matt

robrogers3's avatar

do a dd() for $user and $post. send my your results. paste the results in in your next reply.

one is coming through empty.

The non-authorized error is because the check you make will always be false.

mprythero's avatar

@robrogers3 (here is the return from $user):

User {#228 ▼ #fillable: array:3 [▼ 0 => "name" 1 => "email" 2 => "password" ] #hidden: array:2 [▼ 0 => "password" 1 => "remember_token" ] #connection: "mysql" #table: null #primaryKey: "id" #keyType: "int" +incrementing: true #with: [] #withCount: [] #perPage: 15 +exists: true +wasRecentlyCreated: false #attributes: array:9 [▼ "id" => 5 "name" => "Matthew Prythero" "email" => "myemail" "password" => "password" "remember_token" => "ZFGU4xwSxkF0qqOqtGE91UD6a3kTn07NHDi89o7XcFoOb8vrv0FzhrNOX2vJ" "created_at" => "2017-10-24 15:41:07" "updated_at" => "2017-10-24 15:41:07" "avatar" => "default.png" "level_id" => 5 ] #original: array:9 [▼ "id" => 5 "name" => "Matthew Prythero" "email" => "myemail" "password" => "password" "remember_token" => "ZFGU4xwSxkF0qqOqtGE91UD6a3kTn07NHDi89o7XcFoOb8vrv0FzhrNOX2vJ" "created_at" => "2017-10-24 15:41:07" "updated_at" => "2017-10-24 15:41:07" "avatar" => "default.png" "level_id" => 5 ] #changes: [] #casts: [] #dates: [] #dateFormat: null #appends: [] #dispatchesEvents: [] #observables: [] #relations: [] #touches: [] +timestamps: true #visible: [] #guarded: array:1 [▼ 0 => "*" ] #rememberTokenName: "remember_token" }

mprythero's avatar

And here is $post (by the way, if you'd prefer screenshots, I can upload them if that would help)....

Post {#233 ▼ #fillable: array:3 [▼ 0 => "delivery_id" 1 => "overall_status" 2 => "notes" ] #connection: "mysql" #table: null #primaryKey: "id" #keyType: "int" +incrementing: true #with: [] #withCount: [] #perPage: 15 +exists: true +wasRecentlyCreated: false #attributes: array:10 [▼ "id" => 1 "delivery_id" => "27456165" "overall_status" => "En Route" "created_at" => null "updated_at" => null "notes" => "These are notes" "user_id" => 1 "team_id" => 1 "status_id" => 3 "status_completed" => "0000-00-00 00:00:00" ] #original: array:10 [▼ "id" => 1 "delivery_id" => "27456165" "overall_status" => "En Route" "created_at" => null "updated_at" => null "notes" => "These are notes" "user_id" => 1 "team_id" => 1 "status_id" => 3 "status_completed" => "0000-00-00 00:00:00" ] #changes: [] #casts: [] #dates: [] #dateFormat: null #appends: [] #dispatchesEvents: [] #observables: [] #relations: [] #touches: [] +timestamps: true #hidden: [] #visible: [] #guarded: array:1 [▼ 0 => "*" ] }

robrogers3's avatar

this is a bit of a mess.

it doesn't look like you have defined your relations in the Models.

nor does you schema look like it can support a many to many. here a post belongsTo a team.e

User has no reference to team.

Watch some more videos, then post any questions you have. Get the relations first, then we can work on policy.

Please or to participate in this conversation.