Lightning.Projects.Scope (Lightning v2.19.0-pre)
View SourceWhat standing does an actor have in a project, right now.
fetch/2 establishes facts, not permission: it reports the actor's role, and
refuses outright for a project that does not exist or is scheduled for
deletion. Whether that standing is enough for an action is the policy's
judgement — see Lightning.Policies.ProjectUsers.permitted?/2.
{:ok, %Scope{role: :editor}}
{:ok, %Scope{role: nil}} no membership row
{:error, :no_such_project}
{:error, :project_scheduled_for_deletion}
{:error, :connection_not_for_this_project} repo-connection actor onlySo {:ok, _} is not itself an authorisation result — a non-member of a live
project gets role: nil, which support access can still make meaningful.
Two support fields, deliberately. support_user? is the raw account flag.
support? additionally requires the project's allow_support_access.
Anything granting access to a customer's project wants support?.
A %ProjectRepoConnection{} has no membership row, so its standing is
which project it belongs to; fetch/2 checks that, and its role is always
nil. Ownership is checked before liveness, so a connection learns nothing
about a project that is not its own.
Scheduling deletion removes no membership rows and revokes no token, so this refusal is the whole of the offboarding gate during the purge window.
When not to reach for it
ProjectUsersself-actions — they act on a specific membership row and ask whether it is the caller's own, which a scope cannot answer.Sandboxes:cancel_scheduled_deletion— its subject is scheduled by definition, so the liveness gate would make restore impossible.Provisioning's%Project{id: nil}clause — no row to read, no members to consult.
Every other project-scoped decision resolves here; no policy module reads
Project.scheduled_deletion directly.
Likewise, no policy module reads requires_mfa/mfa_enabled directly. That
rule is written once, privately, below; mfa_satisfied? is the only way to
reach it.
Summary
Types
Whoever is asking. A %User{} may hold a role; a %ProjectRepoConnection{}
never does.
Anything that identifies a project: a loaded %Project{}, a project id, or
any struct or map carrying a :project_id — a %Dataclip{}, a
%Collection{}, a %Projects.File{}, a %ProjectUser{}.
Types
@type actor() :: Lightning.Accounts.User.t() | Lightning.VersionControl.ProjectRepoConnection.t()
Whoever is asking. A %User{} may hold a role; a %ProjectRepoConnection{}
never does.
@type error() ::
:no_such_project
| :project_scheduled_for_deletion
| :connection_not_for_this_project
@type subject() :: Lightning.Projects.Project.t() | Ecto.UUID.t() | %{:project_id => Ecto.UUID.t(), optional(any()) => any()} | nil
Anything that identifies a project: a loaded %Project{}, a project id, or
any struct or map carrying a :project_id — a %Dataclip{}, a
%Collection{}, a %Projects.File{}, a %ProjectUser{}.
@type t() :: %Lightning.Projects.Scope{ actor: actor(), mfa_satisfied?: boolean(), project: Lightning.Projects.Project.t(), project_user: Lightning.Projects.ProjectUser.t() | nil, role: :owner | :admin | :editor | :viewer | nil, support?: boolean(), support_user?: boolean() }
Functions
The actor's standing in the project.
Returns {:error, :project_scheduled_for_deletion} for a project that is
winding down, {:error, :no_such_project} when the subject does not identify
a project that exists, and — for a %ProjectRepoConnection{} actor —
{:error, :connection_not_for_this_project} when the resolved project is not
the one the connection belongs to.
@spec role_in?(Lightning.Accounts.User.t(), subject(), [atom()]) :: boolean()
Whether the user holds one of roles in the project.
false for a project that does not exist or is scheduled for deletion,
false for a user with no membership row, and false for a user who has
not met the project's MFA requirement. It never consults support?, so a
rule that admits a support user has to decide on the %Scope{} itself.