Resolves the project an API action works on, asks the policy whether the
caller may act there, and only then looks up the record inside it. It takes
the Lightning.Scope that LightningWeb.Plugs.AuthorizeAction assigned, and
every refusal it returns is one LightningWeb.API.FallbackController
answers.
A caller the policy refuses gets the same 403 whether the record exists, is missing or has an id that isn't a UUID, and only a caller it permits learns which.
Summary
Types
Looks a record up by id inside a project, given the project's id and the record's.
What identifies a project: an id the request sent, as a
LightningWeb.API.Request.param/0, or a record loaded from the database,
a %Project{} or anything carrying its project_id. A bare id is not
accepted, so an id that isn't a UUID is always refused naming where the
request sent it.
Functions
The record id_param names in the project subject identifies, once the
caller may apply the permission to that project. The id is checked as a UUID
only then, and get is given the project's id and the record's.
The record id_param names on a route whose parent is the instance, such as
/api/jobs/:id, once the caller may apply the permission to the project the
record sits in. locate answers the id of that project, and the record is
then looked up in it with get, as in_project/5 does on the nested route.
The project subject identifies, once the caller may apply the permission to
it.
The project subject names, as project/3 answers it, or a new, unsaved
%Project{} when no project has that id, or subject carries no id, and the
caller holds create_permission.
The record param names on a route whose parent is the instance and whose
record sits in no project, such as /api/credentials/:id, once the caller
may apply the permission to it. get is given the id once it parses as a
UUID and answers {:ok, record} or {:error, :not_found}.
Types
@type get(record) :: (Ecto.UUID.t(), Ecto.UUID.t() -> {:ok, record} | {:error, :not_found})
Looks a record up by id inside a project, given the project's id and the record's.
@type subject() :: LightningWeb.API.Request.param() | Lightning.Projects.Project.t() | %{:project_id => Ecto.UUID.t(), optional(any()) => any()}
What identifies a project: an id the request sent, as a
LightningWeb.API.Request.param/0, or a record loaded from the database,
a %Project{} or anything carrying its project_id. A bare id is not
accepted, so an id that isn't a UUID is always refused naming where the
request sent it.
Functions
@spec in_project( Lightning.Scope.t() | Lightning.Scope.actor(), subject(), permission(), LightningWeb.API.Request.param(), get(record) ) :: {:ok, record} | {:error, term()} when record: term()
The record id_param names in the project subject identifies, once the
caller may apply the permission to that project. The id is checked as a UUID
only then, and get is given the project's id and the record's.
Target.in_project(scope, Request.param(conn, "project_id"),
{:project_users, :edit_workflow},
Request.param(conn, "id"),
&Workflows.get_workflow_for_project/2
)
@spec in_project_of( Lightning.Scope.t(), LightningWeb.API.Request.param(), permission(), (Ecto.UUID.t() -> {:ok, Ecto.UUID.t()} | {:error, :not_found}), get(record) ) :: {:ok, record} | {:error, term()} when record: term()
The record id_param names on a route whose parent is the instance, such as
/api/jobs/:id, once the caller may apply the permission to the project the
record sits in. locate answers the id of that project, and the record is
then looked up in it with get, as in_project/5 does on the nested route.
Only an operator learns that an id names no record ({:error, :not_found})
or isn't a UUID ({:error, {:invalid_id, location}}). Anyone else gets
{:error, :no_standing} for both. A record in a project the caller may not
apply the permission to is {:error, :no_standing} or
{:error, :forbidden}. All three answer 403 forbidden.
A project deleted between locating it and reading it is
{:error, :no_such_project} to a caller who may learn which projects exist,
as Lightning.Scope.fetch/2 answers it.
Target.in_project_of(scope, Request.param(conn, "id"),
{:project_users, :access_project},
&Jobs.get_project_id/1,
&Jobs.get_job_for_project/2
)
@spec project(Lightning.Scope.t() | Lightning.Scope.actor(), subject(), permission()) :: {:ok, Lightning.Projects.Project.t()} | {:error, term()}
The project subject identifies, once the caller may apply the permission to
it.
A project subject doesn't identify is refused as Lightning.Scope.fetch/2
refuses it.
Target.project(conn.assigns.scope, Request.param(conn, "project_id"),
{:project_users, :access_project}
)
@spec project_or_new( Lightning.Scope.t() | Lightning.Scope.actor(), LightningWeb.API.Request.param(), permission(), permission() ) :: {:ok, Lightning.Projects.Project.t()} | {:error, term()}
The project subject names, as project/3 answers it, or a new, unsaved
%Project{} when no project has that id, or subject carries no id, and the
caller holds create_permission.
An id that isn't a UUID is {:error, {:invalid_id, location}} to a caller
who holds create_permission. An id held by a project the caller may not
apply permission to is that refusal, never a new project.
@spec record( Lightning.Scope.t(), LightningWeb.API.Request.param(), permission(), (Ecto.UUID.t() -> {:ok, record} | {:error, :not_found}) ) :: {:ok, record} | {:error, term()} when record: term()
The record param names on a route whose parent is the instance and whose
record sits in no project, such as /api/credentials/:id, once the caller
may apply the permission to it. get is given the id once it parses as a
UUID and answers {:ok, record} or {:error, :not_found}.
Only a caller who may apply the permission to the instance, asked of the
policy with a nil subject, learns that an id names nothing
({:error, :not_found}) or isn't a UUID
({:error, {:invalid_id, location}}). Anyone else gets
{:error, :no_standing}, as for a record they can't see.
Target.record(scope, Request.param(conn, "id"),
{:credentials, :see_credential},
&fetch_credential/1
)