This module defines a unique interface managing authorizations in Lightning.
Users in Lightning have instance-wide and project-wide roles which determine their level of access to resources in the application. For more details see the documentation.
Policy Modules
Authorization policies are implemented under lib/lightning/policies/:
users.ex- Instance-wide access levelsproject_users.ex- Project-wide access levelscredentials.ex- Credential management permissionsworkflows.ex- Workflow-related permissionscollections.ex- Collection access permissionsdataclips.ex- Dataclip permissionsexports.ex- Export functionality permissionsprovisioning.ex- Resource provisioning permissions
Interface
This module provides the can/4 and can?/4 interface, which wraps
Bodyguard.permit/4 to harmonize policy usage across the application.
Policy Resolution: You can reference policy modules in two ways:
- Full module names:
Lightning.Policies.Users - Atom shortcuts for sub-modules:
:users,:project_users,:credentials
Functions
can(policy, action, actor, resource)- Returns:okor{:error, :unauthorized}can?(policy, action, actor, resource)- Returnstrueorfalse
Examples
Using full module names:
can_edit = Lightning.Policies.ProjectUsers
|> Lightning.Policies.Permissions.can?(:edit_workflow, user, project)Using atom shortcuts:
can_create = Permissions.can?(:credentials, :create_keychain_credential, user, project)
can_delete = Permissions.can?(:project_users, :delete_project, user, project)All policies are comprehensively tested in test/lightning/policies/.
Summary
Functions
checks if user has the permissions to apply action using some policy module
same as can/4 but returns true if user can apply action and false otherwise
Resolves actor's scope on the subject's project and passes it to decide,
for a policy handed an actor rather than a %Scope{}. Bodyguard's contract
is boolean, and channel and LiveView callers match {:error, :unauthorized},
so a missing project, no standing and a scheduled project are all false.
Only callers going through permit/4 with a %Scope{} are told apart.
Whether the caller's grant already refuses action, before any target is
loaded. A pass here is not permission. The action still has to ask
permit/4 about its target.
Whether the caller described by scope may apply action to subject.
Functions
checks if user has the permissions to apply action using some policy module
Returns :ok if user can apply action and {:error, :unauthorized} otherwise
Examples
iex> can(Lightning.Policies.Users, :create_workflow, user, project)
:ok
iex> can(Lightning.Policies.Users, :create_project, user, %{})
{:error, :unauthorized}
same as can/4 but returns true if user can apply action and false otherwise
Examples
iex> can(Lightning.Policies.Users, :create_workflow, user, project)
true
iex> can(Lightning.Policies.Users, :create_project, user, %{})
false
@spec for_actor(Lightning.Scope.actor(), term(), (Lightning.Scope.t() -> term())) :: term()
Resolves actor's scope on the subject's project and passes it to decide,
for a policy handed an actor rather than a %Scope{}. Bodyguard's contract
is boolean, and channel and LiveView callers match {:error, :unauthorized},
so a missing project, no standing and a scheduled project are all false.
Only callers going through permit/4 with a %Scope{} are told apart.
@spec permit(atom(), atom(), Lightning.Scope.t()) :: :ok | {:error, :forbidden | Lightning.Policies.Decision.insufficient_scope() | :no_standing | :scheduled_for_deletion}
Whether the caller's grant already refuses action, before any target is
loaded. A pass here is not permission. The action still has to ask
permit/4 about its target.
@spec permit(atom(), atom(), Lightning.Scope.t(), term()) :: :ok | {:error, :forbidden | Lightning.Policies.Decision.insufficient_scope() | :no_standing | :scheduled_for_deletion}
Whether the caller described by scope may apply action to subject.
The one entry point for API callers. Unlike can/4, a refusal is
{:error, :forbidden}, so it renders as 403 rather than 401, and a policy's
more specific answer ({:insufficient_scope, grant}, :no_standing,
:scheduled_for_deletion) reaches the caller unchanged.