API: Capabilties - QutEcoacoustics/baw-server GitHub Wiki
API: Capabilities
DRAFT PROPOSAL: NOT IMPLEMENTED
Capabilities are a method for the server to define what actions are available to the current user.
The intention is to simply the logic required in client applications to make professional user interfaces. Encoding complex business and security rules is hard enough to do once in the server application but to make an effective user interface, those rules need to be reflected in the user interface. There are three options here:
- Maintain dual implementations of business and security rules in the client and server
applications (and adjust both every time a change is needed)
- this method gets worse for every new client implementation that follows
- Expose all actions in the client - even ones that will fail - and present error messages to the user when the action is invalid
- terrible user experience
- Allow the server to list available actions that are valid at the time of the request
We currently have no real solution and thus by default are using option 1. Capabilities are an attempt to implement option 3.
Implementation details
Resources that support capabilities will expose a capabilities
endpoint nested under the resource.
The endpoint can apply to:
- list responses
- e.g.
GET /projects/capabilities
- Must be capabilities related to the type of the resource but not associated with any specific resource
- e.g.
- or individual resources
- e.g.
GET /projects/1/capabilities
- Must be related to the current resource
- e.g.
The capabilities endpoint will return a list of available actions for the current user.
Capabilities are always returned in a single-item standard API: Spec response body. They do not support:
- paging
- filtering
- sorting
- or the options pattern
Design choices multi-responses
There are no plans to add endpoints or support for returning capabilities for multiple resources at once.
Previous experience has shown that most approaches are complex or seriously bloat the response payload (either in meta
or as part of each object's fields).
Our API: Spec encourages chatty APIs instead.
Our current design enables fine-grained fetching for capabilities by adding endpoints for them. This again avoids payload bloat - especially when the client doesn't need capabilities for a resources.
This should also in theory improve performance on the sever as we do not need to hypothetically calculate all capabilities when returning a resource. This can left for a separate non-blocking request.
Standard capabilities
There are some standard capabilities:
create
: the current user is allowed toPOST /resource
update
: the current user is allowed toPUT /resource/:id
orPATCH /resource/:id
destroy
: the current user is allowed toDELETE /resource/:id
There is no need for a read
capability as:
- for list responses, any items that are not readable by the current user will not be included in the list
- for individual resources, any resources that are not readable by the current user will return a
40x
error
Custom capabilities
Custom can be defined and returned on resources where it makes sense. Examples:
allow_original_download
: is a setting on a project. Despite the field being available on the object it is not always determinable by the client what level of access a user will end up with. Thus it is useful to expose this as a capability on the projects resource.suspend
: is a custom capability on a job. It is only available when the job is in a running state. It is not available when the job is in a completed state. It makes sense to expose this as a capability.
Capabilities object definition
Capabilities are returned in a standard single item body response:
{
"meta": {
"status": 200,
"message": "OK"
},
// @type: CapabilityMap
"data": { /* ... */ }
}
The data
will follow the following specification (TS notation):
// The name of the capability, always in snake_case
type CapabilityName = string;
interface CapabilityMap {
[key: CapabilityName]: Capability;
}
interface Capability {
// Whether or not a user is capable of performing the action
can: boolean;
// A code representing the reason why the user is not capable of performing the action
// code is not human readable and is intended to be translated by the client application.
// Only included when `can == false`
code?: string;
// A more detailed human readable message about why the user is not capable of performing the action
// Only included when `can == false`
details?: string;
// A link to a resource this capability applies to. Relevant for "next-step" scenarios.
// When relevant, included despite the value of `can`.
// It will always be an absolute path.
link?: string
}
Example payloads
For GET /projects/capabilities
we would expect a response like:
{
"meta": {
"status": 200,
"message": "OK"
},
"data": {
"create": {
"can": true
},
}
}
For GET /projects/1/capabilities
we would expect a response like:
{
"meta": {
"status": 200,
"message": "OK"
},
"data": {
"update": {
"can": true
},
"destroy": {
"can": false,
"code": "forbidden",
"details": "You do not have permission to delete a project"
},
"allow_original_download": {
"can": true
}
}
}
For GET /analysis_jobs/1/capabilities
(using the API: Actions pattern):
{
"meta": {
"status": 200,
"message": "OK"
},
"data": {
"suspend": {
"can": true,
"link": "/analysis_jobs/1/suspend"
},
"resume": {
"can": false,
"code": "conflict",
"details": "Cannot resume a job that is not suspended",
"link": "/analysis_jobs/1/resume"
},
"retry": {
"can": false,
"code": "conflict",
"details": "The job must be completed before it can be retried",
"link": "/analysis_jobs/1/retry"
},
"amend": {
"can": false,
"code": "forbidden",
"details": "You do not have permission to amend this job",
"link": "/analysis_jobs/1/amend"
}
}
}
Historical notes
The previous spec (below) has multiple flaws: https://github.com/QutEcoacoustics/baw-server/issues/561
Implementation details
Requesting capabilities
OPTIONSCapabilities are theoretically supported by the HTTP
OPTIONS
verb. For any resource in the API a:OPTIONS /some_resource
request should return a list of available HTTP verbs in the
Allow
header and a capabilities object (described below) in the > payload.However: Browser based API requests automatically send
OPTIONS
pre-flight requests to satisfy CORS security requirements. The > wrinkle here is that CORS pre-flight requests are meant to [exclude user credentials](https://www.w3.org/TR/cors/> #cross-origin-request-with-preflight-0) and thus without authentication, most of our capabilities would be indeterminable.GET
Instead, we will package capabilities into the
meta
object returned for ANYGET
request (see the API: Spec document). This > also has the advantage of reducing the number of HTTP requests required.Capabilities are
notincluded by default in themeta
objectand must be enabled with either an. TODO: default inclusion or exclusion of capabilities in the > meta header is going through a field trial.include_capabilities
> query string parameter or aBAW-Include-Capabilities
HTTP headerCapabilities Object Definition
In a
GET
(or a#filter
POST
) request, a capabilities object is inserted in themeta
object as so:{ "meta": { "status": 200, "message": "OK", "capabilities": { ... } }, "data": [] }
The capabilities object itself is an object hash of action names with metadata as in:
{ // standard "create": { "can": true }, "update": { "can": false, "details": "You need to have created this resource to update it", "message": "forbidden" }, "destroy": { "can": false, "details": "You do not have permission to delete this resource", "message": "method_not_allowed" }, // non-standard (can vary by resource) "pause": { "can": false, "details": "Pausing is not yet implemented", "message": "not_implemented" }, "resume": { "can": false, "details": "Pausing is not yet implemented", "message": "not_implemented" }, "retry": { "can": false, "details": "The job must be completed before it can be retried", "message": "unprocessable_entity" } }
Standard actions
The standard actions map to the standard RAILS controller actions of
create
,update
, anddestroy
and are invoked with the > appropriate HTTP verbs.Non-standard actions
Several of our resources include state machines that allow transitions from one state to another on certain conditions. These can be > exposed as actions too. We assume the appropriate HTTP verb for updates (
PUT
orPATCH
) are used.Fields
can
: Whether or not this action is currently alloweddetails
: (optional ifcan == true
) A short reason that can be shown to the user if an action is not available. In some UI > patterns, it is useful to show unavailable actions rather than simply hiding them.message
: (optional ifcan == true
, [should implement?]) A machine to machine tag indicating the broad categorical reason > for the state of thecan
attribute.
- message was not implemented in V1 of capabilities
- e.g.
not_implemented
,forbidden
,unauthorized
,precondition_failed