District Core Developer DocsDistrict Core Developer Docs
Developers
Boilerplate
Modules
Bitbucket
Developers
Boilerplate
Modules
Bitbucket
  • Modules

    • ABN
    • ActivityLog
    • AnalyticsApi
    • ApiConnector
    • BlockApi
    • CategoryApi
    • CloneApi
    • CommentApi
    • ContentApi
    • Core
    • Documents
    • EmbedApi
    • Event
    • ExportApi
    • FeatureApi
    • FormApi
    • GTM
    • GalleryApi
    • HelpApi
    • Hotspot
    • IdeaSurvey
    • ImportApi
    • InteractionsApi
    • Intercom
    • MailApi
    • MapApi
    • MapSurvey
    • MediaApi
    • MenuApi
    • MetaTagApi
    • NlpApi
    • NotificationApi
    • Page
    • ParentableContent
    • PaymentApi
    • PermissionsApi
    • Postcode
    • ReCaptcha
    • Redirects
    • Renderer
    • ReportApi
    • RestrictionApi
    • RevisionApi
    • SearchApi
    • Settings
    • ShareableApi
    • Slack
    • SlugApi
    • SubscribableApi
    • Survey
    • Team
    • TenantApi
    • TestApi
    • ThemeApi
    • Timeline
    • TranslationApi
    • Update
    • Users
    • VisualisationApi
    • WorkflowApi
    • Wysiwyg

PermissionsAPI module

Facilitates protection of the app by allowing you to define authorization of actions on specific content (i.e models, etc...) via roles and permissions.

Integrates with Laravel Gate system so that permission checks can be done natively.

Relies heavily on spatie/laravel-permission

Quick start

Defining permissions is simple. You will need a permissions list and a definition to let the Permissions API know about it.

1. Create a Permissions folder and Permissions/Enums folder

Add a Permissions and Permissions/Enums folder to your module at Modules/[your-module]/Plugins/Permissions or to the app at app/Plugins/Permissions

2. List permission names for content to protect

  • List permission names by creating a new Enum, e.g YourContentPermissions under Permissions/Enums. Copy the example at PermissionsApi/Plugins/Permissions/Enums/RolePermissions.
  • Add a SUBJECT constant to refer to content to be protected, e.g your_content. In case of a model, this should refer to the model name in snake case.
  • Add an ACTION_ENUMS constant with an array containing a list of action enums classes defined Permissions-Api/Plugins/Permissions/Enums/Actions that contain lists of common actions ('create', 'view', etc...). e.g const ACTION_ENUMS = [PermissionActions::class,];

Permission names will then be generated dynamically using [SUBJECT].[ACTION] such as your_content.create.

3. Create a permissions definition

  • Create a permissions definition under Modules/[your-module]/Plugins/Permissions or app/Plugins/Permissions, e.g YourContentPermissionDefinition Copy the example at PermissionsApi/Plugins/Permissions/RolePermissionDefinition.
  • Indicate the group your permissions will be part of, used on UI for role permissions assignment.
  • Indicate a unique type that your permissions define, usually refer to the SUBJECT added to the Permissions enum previously created. e.g YourContentPermissions::SUBJECT
  • Update permissionsEnum with permissions enum class previously created, e.g YourContentPermissions::class
  • Optionally add a model class when protecting a model, e.g YourContent::class

4. Protect your content

The last step is to protect your content.

  • Via your resource controller constructor: $this->authorizeResource(YourModel::class, 'your_model_route_param'); This will protect all standard resource routes. Any extra routes to be explicitly protected in your route declaration file
  • Or via route middleware:
Route::get('/', [Controller::class, 'index'])
  ->middleware('can:'.YourContentPermissionDefinition::SUBJECT.'.'.PermissionActions::VIEW)

And in case of models passed by route model binding:

Route::get('/{your_content}', [Controller::class, 'show'])
  ->middleware('can:'.PermissionActions::VIEW.',your_content')`

5. Restrict UI by permissions

  • Whether declared in frontend or backend, restricted menu items should have a permission value, e.g {"text": "Dashboard", "url": "/dash", "permission": 'dashboard.view'}
  • Each menu should pass through the function filterNavLinksPermissions(links) defined in Mixins/PermissionHelper Note that the permissions list of each user is passed to the frontend via the SharedInertiaData middleware and permission_list attribute from Trait HasRoles.

6. Consider adding default role permissions

Extend app\Plugins\Permissions\Roles\AppDefaultRolePermissions by adding a new subject and actions for each role.
More info below under Default roles, migrations and seeding. Run the seeder and test.

7. Create tests

You can speed up Unit and Feature tests creation by extending PermissionsAPi\Tests\PermissionTestCase or simply using the handy trait PermissionsAPi\Tests\Trait\PermissionTestTrait.

In Depth.

Default roles, migrations and seeding.

Seeding
The PermissionsApi seeder will seed the full permissions list defined across all modules and application. It will also seed Default roles and default role permissions.

Default roles

  • The PermissionsApi module comes with a default role Super admin and default permissions for it.
  • You can define default roles at app level by extending the enum: app\Plugins\Permissions\Roles\AppDefaultRoles. Simply add a new role as a constant.

Default role permissions
You can define default role permissions for each role at app level by extending the enum: app\Plugins\Permissions\Roles\AppDefaultRolePermissions.
Either:

  • Add a new array to each role with a new subject that matches your Permissions subject (e.g role) and an array of permission actions [create,view]. Permissions will be assigned to this role accordingly, e.g role.create, role.view
  • Or add a new array with a plain list of permissions, e.g ['do something different', 'another permission']

Subtypes.

Additional subjects can be added to permission names for more granular control over different variations of content.
These are called SUBTYPE in permissions enums.
For instance, settings.update.general will grant access to edit all general settings but not user settings, controlled by settings.update.user
These also answer to wild cards so granting settings.update will allow users to update all type of settings (equivalent to settings.update.*)

Wild cards.

Permission wildcards get generated from definitions to simplify permission assignment.
There are three types of wildcards used:

  • Global, e.g * users assigned this permission can do anything in the system, like super admins
  • Subject, e.g *.create, users assigned this permission can create any content.
  • Action, e.g role.*, users assigned this permission can do anything with roles. When setting up default permissions for a role, you for instance grant action wildcard permissions on a subject by adding:
[
  'subject' => 'my_content',
  'actions' => PermissionActions::ALL_ACTIONS
]

Custom permission names and actions.

You can add a custom list of permissions if pre-defined actions are not suitable.

  • Ideally define a new set of actions as en enum under YourModule/Plugins/Permissions/Enums/Actions/NewActionList. Add a new constant for each action, e.g const ACCESS = 'access'
  • Add constants to your permissions enum (YourContentPermissions) made from [ACTION] + [SUBJECT]. e.g const ACCESS_YOURCONTENT = self::SUBJECT.'.'.NewActionList::ACCESS
  • Add your new action to the base model policy PermissionsApi/Plugins/Permissions/Policies/BaseModelPolicy

Custom permission policies

Your permissions definition can use a custom permission policy for more complex business logic.

  • Create a new policy under YourModule/Plugins/Permissions/Policies/YourContentPolicy or YourModule/Plugins/Permissions/Policies/YourContentPolicy
  • Add the property $policy = YourContentPolicy::class to your permissions definition ( YourContentPermissionDefinition)

Protect content in functions (e.g in Controllers) rather than routes

You can use Laravel Gate Facade to protect your content. For example:

  • Gate::authorize(PermissionActions::VIEW, $model) will throw a 403 exception if current user is not authorized to view the model.
  • Use the allows method for conditional logic if (!Gate::allows(PermissionActions::VIEW, $model) { do something }.
  • If no models are involved, simply check the permission directly: Gate::authorize(PermissionActions::VIEW .' '.YourContentPermissionDefinition::SUBJECT)

Permissions API Concepts explained

Permissions list build

  • Permissions list gets accessed from a PermissionsRepositoryInterface
  • Permissions lists are dynamically built from the app and modules by parsing "Permission definitions" files that extend BasePermissionsDefinition
  • Each definition concerns a type which is usually a model but can be something else such as a section of the app (e.g dashboard).
  • Modules and app can define as many permissions as required.

Permissions list in database

  • The permissions list in the database is used to assign permissions to roles. It is maintained by a seeder called upon each deploy and looking for changes.
  • Permissions are intentionally categorised by type and group to assist with UI building of role management pages.

Roles

  • Permissions are not assigned to users directly. Permissions are assigned to roles and roles assigned to users.
  • We check if users have permission to do something, we do not check roles since a user may cumulate multiple roles. As per permissions best practices

Model policies

  • Most permission checks go through model permission policies that are mapped to each model.
  • Policy mapping is done thanks to $model and $policy properties specified in Permission definitions.
  • When a permission check is made, passing the model to the middleware or gate will tell Laravel to look at that policy for checks. e,g Gate::allows(PermissionActions::VIEW, $model) will lookup the $model policy.
  • In most cases, you should not worry about policies. The BaseModelPolicy should cover most cases.
  • Non model permissions do not use policies but check the permission directly using Gate::before logic defined by Spatie/Laravel-permissions

Guidelines

  • Ref. to full Laravel doc on Authorization: https://laravel.com/docs/9.x/authorization
  • Permission names are defined using Enums to avoid hardcoded permission names, errors and promote reusability. We use the Laravel package BenSampo/laravel-enum to allow inheritance and for handy helpers.
  • Use constants!

Edit this page
Prev
PaymentApi
Next
Postcode