A Laravel package for managing teams and user permissions, supporting multi-tenant dynamic roles, role groups, and team-specific permissions.
Users can be organized into groups within teams, each with custom permissions and abilities. Permissions assigned to a user group override individual user permissions within a team.
Additionally, users can be added to a global group to grant them access across all teams with the group’s permissions. This feature is ideal for scenarios like providing support across multiple teams without the need to add users to each team individually.
Note
The documentation for this package is currently being written. For now, please refer to this readme for information on the functionality and usage of the package.
- Requirements
- Schema
- Installation
- Teams
- Users
- Roles & Permissions
- Abilities
- Groups
- Middlewares
- License
PHP >= 8.1
and Laravel 8.x or higher
composer require jurager/teams
Always do backups, next command may overwrite your actual data.
php artisan teams:install
Run the migrations
php artisan migrate
Note
If you wish to use custom foreign keys and table names, make changes to config config/teams.php
before running migrations.
Then, add the HasTeams
trait to your existing User
model.
<?php
namespace App\Providers;
use Jurager\Teams\Traits\HasTeams;
class User extends Model {
use HasTeams;
}
A team can be accessed via $user->team
, providing methods for inspecting the team's attributes and relations:
// Access the team's owner...
$team->owner
// Get all the team's users, excluding owner
$team->users()
// Get all the team's users, including the owner...
$team->allUsers()
// Determine if the given user is a team member...
$team->hasUser(object $user)
// Adds a user to the team with a specified role by role ID or code
$team->addUser(object $user, string $role_keyword)
// Update the role of a specific user within the team
$team->updateUser(object $user, string $role_keyword)
// Remove the given user from the team.
$team->deleteUser(object $user);
// Get all the abilities belong to the team.
$team->abilities()
// Get all the team's roles.
$team->roles()
// Return the user role object from the team
$team->userRole(object $user)
// Check if the team has a specific role by ID or code or any roles at all
$team->hasRole(int|string|null $keyword)
// Get the role from the team by role id or code
$team->getRole(int|string $keyword)
// Add new role to the team
$team->addRole(string $code, array $permissions, string|null $name, string|null $description)
// Update the role in the team
$team->updateRole(int|string $keyword, array $permissions, string|null $name, string|null $description)
// Deletes the given role from team
$team->deleteRole(int|string $keyword)
// Get all groups of the team.
$team->groups()
// Get team group by its id or code
$team->getGroup(int|string $keyword)
// Add new group to the team
$team->addGroup(string $code, array|null $permissions = [], string|null $name)
// Update the group in the team
$team->updateGroup(int|string $keyword, array|null $permissions, string|null $name)
// Delete group from the team
$team->deleteGroup(int|string $keyword)
// Determine if the team has a member with the given email address...
$team->hasUserWithEmail(array $email)
// Determine if the given user is a team member with the given permission...
$team->userHasPermission(object $user, string|array $permissions, bool $require = false)
// Returns all team invitations
$team->invitations()
These methods allow you to efficiently manage and interact with teams, including roles, users, permissions, and invitations.
Note
By default, the package uses the built-in model. If you want to use your own model, or specify a custom table name in the database, use the settings in the configuration file - teams.models.team
, teams.tables.teams
, teams.foreign_keys.team_id
The Jurager\Teams\Traits\HasTeams
trait provides methods to inspect a user's teams:
// Access the team's that a user belongs to...
$user->teams : Illuminate\Database\Eloquent\Collection
// Access all of a user's owned teams...
$user->ownedTeams : Illuminate\Database\Eloquent\Collection
// Access all the team's (including owned teams) that a user belongs to...
$user->allTeams() : Illuminate\Database\Eloquent\Collection
// Determine if a user owns a given team...
$user->ownsTeam(object $team) : bool
// Determine if a user belongs to a given team...
$user->belongsToTeam(object $team) : bool
// Get the role that the user is assigned on the team...
$user->teamRole(object $team) : \Jurager\Teams\Role
// Determine if the user has the given role on the given team...
$user->hasTeamRole(object $team, string|array 'admin', bool $require = false) : bool
// Access an array of all permissions a user has for a given team...
// Scope identifies which model to take permissions from, by default getting all permissions ( ex. 'role', 'group')
$user->teamPermissions(object $team, string|null $scope = null) : array
// Determine if a user has a given team permission...
// $require = true (all permissions in the array are required)
// $require = false (only one or more permission in the array are required)
// $scope - identifies in which model to check permissions, by default in all ( ex. 'role', 'group')
$user->hasTeamPermission(object $team, string|array 'server:create', bool $require = false, string|null $scope = null) : bool
// Get list of abilities or forbidden abilities for users on certain model
$user->teamAbilities(object $team, object $server) : mixed
// Determine if a user has a given ability on certain model...
$user->hasTeamAbility(object $team, string 'server:edit', object $server) : bool
// Add an ability for user to action on certain model, if not found, will create a new one
$user->allowTeamAbility(object $team, string 'server:edit', object $server) : bool
// Forbid an ability for user to action on certain model, used in case if global ability or role allowing this action
$user->forbidTeamAbility(object $team, string 'server:edit', object $server) : bool
These methods enable you to efficiently manage and inspect a user's teams, roles, permissions, and abilities within your application.
Roles and permissions offer a flexible approach to managing access control within your application. Each team member can be assigned a role, with each role tied to a specific set of permissions. These roles and permissions are stored in your application's database, allowing for dynamic and easy management of access and enables features like role and permission management through your application's admin interface.
Example: Creating a New Team with Roles and Permissions
$team = new Team();
$team->name = 'Example Team';
$team->user_id = $user->id;
if ($team->save()) {
$team->addRole('admin', [
'employees.*',
'sections.*',
'articles.*',
'tags.*',
'comments.*',
'team.edit',
'stores.*',
'plan.edit',
]);
$team->addRole('user', [
'employees.view',
'articles.view',
'articles.add',
'sections.view',
'sections.add',
'comments.add',
'tags.view',
'stores.add',
'stores.delete',
'tags.add',
]);
}
In the above example, we create a new team and assign it two roles: "admin" and "user". Each role is associated with a set of permissions that define what actions users with that role can perform within the application.
The second argument for $team->addRole()
is an array of permissions, which determine the actions that users with the corresponding role can perform in the application.
To ensure that incoming requests initiated by a team member can be executed by that user, the application needs to verify the permissions of the user's team. This verification can be done using the hasTeamPermission
method, which is available through the Jurager\Teams\Traits\HasTeams
trait.
Note
In most cases, checking a user's role is often unnecessary. Instead, prioritize verifying specific granular permissions, as roles mainly serve to group these permissions for organizational clarity. Typically, you’ll use this approach within your application's authorization policies.
Example: Check if a user within a team has permission to update a server
return $user->hasTeamPermission(object $team, string 'server:update');
Abilities - enables users to perform specific actions on application entities or models. For example, you can grant a user within a team the ability to edit posts.
Adding abilities to users is easy — just pass the ability name, and it’ll be created automatically if it doesn’t exist.
To grant a user the ability to edit an article within a team, simply provide the relevant entities, such as the article and team objects:
$user->allowTeamAbility(object $team, string $action, object $action_entity, object|null $target_entity = null)
To verify if a user has a specific ability within the context of a team, based on various permission levels (role, group, user, and global), you can use the following method:
User::hasTeamAbility(object $team, string 'edit_post', object $post);
This method checks if the user can perform the specified ability (e.g., 'edit_post') on the given entity (e.g., a post) within the context of the specified team. It takes into account the user's role, groups, global permissions, and any entity-specific access rules.
Permissions are governed by different access levels, which are compared to determine whether an action is allowed or forbidden. There are two key indicators:
- allowed: The highest permission level granted to the user.
- forbidden: The highest restriction applied to the user.
If the allowed value is greater than or equal to the forbidden value, the action is permitted.
Level | Value | Description |
---|---|---|
DEFAULT |
0 | Base level with no explicit permissions or restrictions. |
FORBIDDEN |
1 | Base level denying access. |
ROLE_ALLOWED |
2 | Permission granted based on the user's role in the team. |
ROLE_FORBIDDEN |
3 | Restriction applied based on the user's role in the team. |
GROUP_ALLOWED |
4 | Permission granted based on the user's group within the team. |
GROUP_FORBIDDEN |
5 | Restriction applied based on the user's group within the team. |
USER_ALLOWED |
5 | Permission granted specifically for the user. |
USER_FORBIDDEN |
6 | Restriction applied specifically to the user for this entity. |
GLOBAL_ALLOWED |
6 | Global permissions applicable to the user regardless of the team context. |
- Ownership Check: If the user is the owner of the entity (via isOwner), access is immediately granted.
- Team-Level Permission Check: The method checks:
- Role-based permissions using hasTeamPermission.
- Group-based permissions using hasGroupPermission.
- Global permissions using hasGlobalGroupPermissions.
- Entity-Specific Rules: If the entity has specific rules (abilities), permissions and restrictions are evaluated for:
- The user's role within the team.
- The user's groups within the team.
- The specific user assigned to this entity.
- Final Decision: If the final allowed level is greater than or equal to the forbidden level, access is granted.
To prevent a user from having a specific ability (even if their role allows it), use the following method:
User::forbidTeamAbility(object $team, string $action, object $action_entity, object|null $target_entity = null)
Users within teams can be organized into groups, each with its own set of abilities and permissions. Groups work together with abilities and permissions, so you should use ability and permission checking methods to determine if users have specific access rights within groups.
Note
Access rights granted to a group of users take precedence over rights granted to a user within role in a team.
-
User can
server:edit
in the team, but is part of a group restricted fromserver:edit
for specific entities. -
User can't
server:edit
in the team, but is in a group permitted toserver:edit
specific entities.
The Jurager\Teams\Traits\HasTeams
trait provides methods to inspect a user's team groups:
// Add new group to the team
$team->addGroup(string $code, array $permissions = [], string|null $name)
// Update the group in the team, if permissions is empty array all exiting permissions will be detached
$team->updateGroup(int|string $keyword, array $permissions => [], string|null $name)
// Delete group from the team
$team->deleteGroup(string $code)
// Get all groups of the team.
$team->groups();
// Check if the team has a specific group by ID or code or any groups at all
$team->hasGroup(int|string|null $keyword)
// Get team group by its code
$group = $team->getGroup(int|string $keyword);
// Get all group users
$group->users();
// Attach users or user to a group
$group->attachUser(Collection|Model $user);
// Detach users or user from group
$group->detachUser(Collection|Model $user);
The middleware provided by this package is automatically registered as role
, permission
, and ability
.
However, if you wish to use your own customized middlewares, you can modify the middleware.register
in the config/teams.php
.
You can use middleware to filter routes and route groups based on permissions or roles.
Note
Consider, that team_id
represents the actual ID of the team in the database.
If you need to customize the name of this variable, adjust the foreign_keys.team_id
value in your config/teams.php
file to match your database structure.
Route::group(['prefix' => 'admin', 'middleware' => ['role:admin,team_id']], function() {
Route::get('/users', 'UserController@usersIndex');
Route::get('/user/edit', ['middleware' => ['permission:edit-users,team_id'], 'uses' => 'UserController@userEdit']);
});
Note
Middleware logic may vary based on how you pass the {team_id}
variable.
- You can pass the
{team_id}
variable as a route parameter:
Route::get('/{team_id}/users', ['middleware' => ['permission:views-users'], 'uses' => 'CommonController@commonUsers']);
- You can pass the
{team_id}
variable directly as a middleware option:
'middleware' => ['role:admin|root,{team_id}']
- You can send the
{team_id}
variable with each request type (GET/POST/PUT, etc.).
For OR operations, use the pipe symbol:
'middleware' => ['role:admin|root,{team_id}']
// $user->hasTeamRole($team, ['admin', 'root']);
'middleware' => ['permission:edit-post|edit-user,{team_id}']
// $user->hasTeamPermission($team, ['edit-post', 'edit-user']);
For AND functionality:
'middleware' => ['role:admin|root,team_id,require']
// $user->hasTeamRole($team, ['admin', 'root'], require: true);
'middleware' => ['permission:edit-post|edit-user,team_id,require']
// $user->hasTeamPermission($team, ['edit-post', 'edit-user'], require: true);
To check the ability to perform a specific action on a specific model item, use the ability middleware:
'middleware' => ['ability:edit,App\Models\Article,{article_id}']
// $user->hasTeamAbility($team, 'edit', $article);
In this case, pass {article_id}
as a request parameter or route parameter to allow the package to identify the model object.
This package is open-sourced software licensed under the MIT license.