I found myself in a similar situation. I have my own teams implementation and I was hoping to enable the teams feature in permissions. With the help of your post, the documentation and some testing I have a working setup as well. I thought I would share my experiences too. (Side note: Also I am glad I found this thread, it was found through an obscured search and doesn't actually show forum search results. )
I have following all the details from the docs and the steps you listed as well. My middleware and controllers all look very similar to yours with the exception my team is named "team" vs "branch".
My steps to setup:
- Installed Laravel App
- Setup a Teams and Team Users Models for teams and assignents
- Added a
current_team_idto User model for team switching - Installed Spatie Permissions with Teams Enabled per docs
I created middleware as well for both setting current team into session and priming spatie permissions with this team id. The middleware are as follows:
SetCurrentTeam.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class SetCurrentTeam
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if ( ! empty(auth()->user())) {
session()->put(['current_team_id' => auth()->user()->current_team_id]);
}
return $next($request);
}
}
SetCurrentTeamPermission.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class SetCurrentTeamPermission
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if(!empty(auth()->user())){
// session value set on login
app(\Spatie\Permission\PermissionRegistrar::class)->setPermissionsTeamId(session('current_team_id'));
}
return $next($request);
}
}
I am using the permissions in a role based approach. I have seeded my system with global permissions and then the team based roles have assess to these permissions. As suggested by the package documentation this approach allows for us to make use of the can() authorization helpers to then check for these permissions.
To clean up and organize logic I also made the suggested Policy to contain this logic and then just point authentication to the appropriate policy using laravels built in methods.
Here is a copy of one of my policies for a Claim model that a user has access too:
<?php
namespace App\Policies;
use App\Models\Claim;
use App\Models\User;
use \Spatie\Permission\Models\Role;
use Illuminate\Auth\Access\HandlesAuthorization;
class ClaimPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $user
* @return \Illuminate\Auth\Access\Response|bool
*/
public function viewAny(User $user)
{
if($user->can('view-all-claims')) {
return true;
}
if($user->can('view-own-claims')) {
return true;
}
if($user->can('view-assigned-claims')) {
return true;
}
dd($user->roles);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $user
* @param \App\Models\Claim $claim
* @return \Illuminate\Auth\Access\Response|bool
*/
public function view(User $user, Claim $claim)
{
if($user->can('view-own-claims')) {
return $user->id == $claim->user_id;
}
if($user->can('view-assigned-claims')) {
return $claim->adjusters()->where('claim_users.user_id', $user->id)->count() > 0;
}
if($user->can('view-claims')) {
return true;
}
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $user
* @return \Illuminate\Auth\Access\Response|bool
*/
public function create(User $user)
{
if($user->can('create-claims')) {
return true;
}
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @param \App\Models\Claim $claim
* @return \Illuminate\Auth\Access\Response|bool
*/
public function update(User $user, Claim $claim)
{
if($user->can('edit-own-claims')) {
return $user->id == $claim->user_id;
}
if($user->can('edit-assigned-claims')) {
return $claim->adjusters()->where('claim_users.user_id', $user->id)->count() > 0;
}
if($user->can('edit-claims')) {
return true;
}
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Claim $claim
* @return \Illuminate\Auth\Access\Response|bool
*/
public function delete(User $user, Claim $claim)
{
if($user->can('delete-own-claims')) {
return $user->id == $claim->user_id;
}
if($user->can('delete-assigned-claims')) {
return $claim->adjusters()->where('claim_users.user_id', $user->id)->count() > 0;
}
if($user->can('delete-claims')) {
return true;
}
}
Then inside my web.php routes file you can make use of the policy through middleware:
Route::get('/claims', 'App\Http\Controllers\ClaimController@index')->middleware('can:viewAny,'.App\Models\Claim::class)->name('claim');
Route::post('/claims/{claim}', 'App\Http\Controllers\ClaimController@update')->middleware('can:update,claim')->name('claim.update');
Something important to note here. @upnorthal I had a route middleware priority set similar to yours wondering where they should be. I later discovered my answer. I have been trying to figure out a bug for some time now as to why the policy was working for everything except the viewAll method. After dumping the user many times I discovered the roles were not being pulled for the user which then means none of the permissions passing. So no matter what my viewAll method was failing. It took me a while to think about the issue.
Then I discovered it. I was wondering why the roles were not being pulled in and then I remember that they are associated with the teams. If for some reason the team was not set then it didn't know what roles to grab and defaults to none. I recalled that my middleware was supposed to be setting this team id and so I thought to look there. Everything checked out but then I realized the issue. The team id WAS being set but AFTER the authorization middleware. This meant that any middleware making use of can() was not seeing the set team id.
So this leads us to the question of the middleware priority. I found that my middleware must come BEFORE the authorization middleware in order for the team id to be set into session and primed into permissions before the authorization check take place. This results in the following route Kernel for my route priority:
Kernel.php
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var string[]
*/
protected $middlewarePriority = [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SetCurrentTeam::class,
\App\Http\Middleware\SetCurrentTeamPermission::class,
\Illuminate\Auth\Middleware\Authorize::class, // <-- Place permissions setup before this
];
I certainly hope this helps anyone else in similar situations and avoid some long debugging. @upnorthal thanks for your initial discussion on the matter!
Perhaps we can submit a PR to the package docs and add some additional information to the teams setup.
Happy coding all!