Permissions
Since Decidim has multiple roles, we needed a permissions system to discover what actions can a user perform, given their roles. The basis of the current permissions system were added on #3029, so be sure to check that PR (and the related ones) to read the discussion and the motivations behind the change.
Overview
When checking for permission to perform an action, we check this chain:
-
The component permissions
-
The participatory space permissions
-
The core permissions
This way we are going from more specific to more general.
Explanation
We wrap the permission and its context in a PermissionsAction
object. It also holds the state of the permission (whether it has been allowed or not).
Each component and space must define a Permissions
class, inheriting from Decidim::DefaultPermissions
. The Permissions
class must define a permissions
instance method. this class will receive the permission action, and the permissions
method must return the permission action. The Permissions
class can set the action as allowed or disallowed.
There is a small limitation in the permission action state machine: once it has been disallowed it cannot be reallowed. This is to avoid mischievous modules modifying permissions.
Permission actions have a scope. It is usually either :public
or :admin
, and the Permissions
class usually handles the :public
scope, while it delegates the :admin
one to another specialized class.
Add a new Action
Proposals example
We are going to reproduce the steps to add an action (endorse) for a proposal step by step.
Configuring a new 'endorse' action
-
Edit decidim-proposals/lib/decidim/proposals/component.rb
-
Add the new 'endorse' action into the
component.actions
array and save the file:
component.actions = %w(endorse vote create)
-
Translate the action for the corresponding key:
en.decidim.components.proposals.actions.endorse = Endorse
-
Edit
app/permissions/decidim/proposals/permissions.rb
and add the corresponding permission, usingauthorized?
method to run theauthorization handler
logic. -
In the endorse proposals controller, enforce that the user has permissions to perform the
endorse
action before actually doing it, using theenforce_permission_to
method:
enforce_permission_to :endorse, :proposal, proposal: proposal
-
In the proposals templates, change regular links and buttons to endorse a proposal with calls to
action_authorized_link_to
andaction_authorized_button_to
helpers. -
Restart the server to pick up the changes.
Using the new 'endorse' action
-
Now an admin user should be able to go to a participatory space with a
proposals
component panel and click on itsPermissions
link (the icon with a key). There, anauthorization handler
can be set for theendorse
action. -
Go to a Proposal detail in the front-end
-
Try to endorse the current proposal
-
If the user has not granted the selected permission, the 'endorse' button should block the action and allow the user to grant the permission with the selected
authorization handler
logic. -
If the logger user has already granted the selected permission, the user should be able to perform the endorsement.
-
Allow to configure the related permissions at resource level
Permissions settings could also be set for an specific resources apart from the full component. Actions should also be related to the resource.
-
Edit decidim-proposals/lib/decidim/proposals/component.rb
-
Add the 'endorse' action into the
resource.actions
array for theproposal
resource definition and save the file:
resource.actions = %w(endorse vote)
-
Only if this is the first resource action added, edit the proposals admin templates and use the
resource_permissions_link
helper to link the resource permissions page from each resource in the listing.
<%= resource_permissions_link(proposal) %>
-
In the proposals permission class, pass an extra
resource
named parameter to the calls to theauthorized?
method.
authorized?(:endorse, resource: proposal)
-
In the proposals templates, also add the extra
resource
parameter to theaction_authorized_link_to
andaction_authorized_button_to
helpers. -
Restart the server to pick up the changes.