Lightning.Policies.Sandboxes (Lightning v2.19.0-pre)

View Source

The Bodyguard Policy module for sandbox project operations.

Sandbox authorization mirrors regular projects: access is decided by the acting user's role on the project they're acting on (or the workspace root, where the cascade applies). User.role (:user / :superuser) is a user-type for global user-management screens; it is not a project-access bypass and has no effect on sandbox policy decisions, in line with Lightning.Policies.ProjectUsers.

  • Sandbox owners/admins can manage their own sandboxes
  • Root project owners/admins can manage any sandbox in their workspace
  • Editors (and above) on the parent project can provision sandboxes

Every action except provisioning and merging acts on a sandbox, and a workspace root is not one. A root is refused before any role is considered, which is what keeps the root cascade from admitting the root itself.

Destructive actions on a sandbox (delete, update, merge) are scoped to admin/owner on the sandbox itself (or the root cascade above). This matches the rest of Lightning, where destructive actions are admin/owner scoped, and it keeps the merge button on the sandboxes list aligned with the cleanup step that runs after merge submission (which calls :delete_sandbox and so requires admin/owner on the source).

Summary

Functions

Authorize sandbox operations based on the user's role on the project involved.

Bulk manage check for multiple sandboxes, avoiding N+1 queries.

Types

actions()

@type actions() ::
  :delete_sandbox
  | :update_sandbox
  | :provision_sandbox
  | :merge_sandbox
  | :cancel_scheduled_deletion

Functions

authorize(action, user, parent_project)

Authorize sandbox operations based on the user's role on the project involved.

Authorization Rules

:delete_sandbox, :update_sandbox and :cancel_scheduled_deletion

The subject must be a sandbox: a project with no parent is refused outright, whoever is asking. Beyond that the user must be one of:

  • Owner/admin of the sandbox itself
  • Owner/admin of the root project (workspace)

:provision_sandbox

User must be editor/admin/owner of the parent project they're creating the sandbox under.

:merge_sandbox

This check authorises the target side of a merge: the user must be editor/admin/owner on the target project (the project being merged into). The merge flow also requires admin/owner on the source sandbox itself, enforced by manage_permissions/3 (button gate) and by the post-merge cleanup, which calls :delete_sandbox to retire the source and so requires admin/owner there.

Support access

No sandbox action honours it: every clause decides on role alone, so a support user with no membership row is refused even on a consenting project. Inherited behaviour, not a ruling.

Parameters

  • action - The action being attempted
  • user - The user attempting the action
  • project - The sandbox project (for delete/update), parent project (for provision), or target project (for merge)

manage_permissions(sandboxes, user, root_project)

@spec manage_permissions(
  [Lightning.Projects.Project.t()],
  Lightning.Accounts.User.t(),
  Lightning.Projects.Project.t()
) :: %{required(binary()) => boolean()}

Bulk manage check for multiple sandboxes, avoiding N+1 queries.

Returns a map sandbox_id => boolean() where true means the user can perform the destructive actions the sandbox list offers on that row: they are an owner/admin on the sandbox itself, or an owner/admin on the root project (cascade). The workspace root is always false, which is what the list relies on to withhold edit, delete and merge from the root. Note this is the merge source side; :merge_sandbox authorises the target and a root is a legal target.

Assumes root_project.project_users and each sandbox.project_users are preloaded (as ensured by Projects.list_workspace_projects/2).

Reads those preloaded rows rather than resolving a Scope, so it does not hold the actor to the project's MFA requirement. Nothing reaches it that has not already been held to it: the list it feeds mounts LightningWeb.Hooks.:project_scope, which redirects an unenrolled member to /mfa_required before the page renders, and every action a true enables refuses on its own.