|
DLI REST-style API Reference
|
The legacy API allowed to create "convenient" links for actions like "/outlet?a=OFF" or "/script.cgi?run100".
Such "convenience", however, comes at a cost: if, while being logged in to the controller, the user visits a malicious page with something like
<img src="http://192.168.0.100/outlet?a=OFF"/>
or
<script src="http://192.168.0.100/outlet?a=OFF"></script>
or similar, the corresponding action will be taken. More malicious page examples would include e.g. erasing some non-administrative users, etc. You could receive an HTML email containing a reference to such an "image", and it could work.
This is an extreme case of a vulnerability named CSRF (cross-site request forgery) [9]. It is not affected by authentication or encryption used. Using POST to send such requests would only be a bit more secure, as those can be imitated by an HTML form, and the user could be tricked into submitting it.
The legacy API will stay in place for the time being, but the new API is designed to make it more resistant to such kind of attacks.
In modern browsers, XMLHttpRequest can be used to perform arbitrary requests, but if the sender's origin doesn't match the request target, a preflight request is sent, which is currently denied by the controller, so it can't by itself be used to forge a request. Some old Flash plugins didn't play by the rules and allowed arbitrary requests, but they should be fixed by now. So, the remaining sources of potentially-problematic requests are HTML forms, which are currently limited to GET and POST methods with Content-Type of application/x-www-form-urlencoded (among those supported by the REST API as input content types). However, any state-modifying operation by default requires additional request headers which indicate that the source of operation is not an HTML form:
X-CSRF: <any value>
or
X-Requested-With: XMLHttpRequest
The first option is supported because it's shorter and self-descriptive, and the second one because it is what JavaScript frameworks usually send.
More sophisticated CSRF protection methods, like passing a token around, are not much more effective and aren't easy to implement considering REST design restrictions.
Additionally, the requirement to add such headers may be lifted if the following options are set:
Relax non-HTML method CSRF checks (/restapi/config/relax_nonhtml_content_types/)
This setting allows HTTP clients to perform API requests with e.g. application/json or application/json-rpc without a CSRF protection header (such content currently cannot be sent via an HTML form).
Relax non-HTML content type CSRF checks (/restapi/config/relax_nonhtml_methods/)
This setting allows HTTP clients to perform PUT/PATCH/DELETE API requests without a CSRF protection header (such requests currently cannot be sent via an HTML form).
As of the time of this writing, these settings don't reduce security; however, more HTML form methods or content types may be standardized in the future, making the setup less secure.
If you have problems sending PUT, PATCH or DELETE requests, or if you are sending non-idempotent PUT or DELETE requests (see PUT and DELETE methods not always idempotent below), you can wrap an arbitrary method request in a POST request using, e.g.
POST /restapi/auth/users/2/ HTTP/1.0 X-HTTP-Method: DELETE
Matrix URIs [2] are a way of encoding URI-segment-specific request parameters in the segment (URI part delimited by '/') itself. In the API, a matrix URI segment defines a selector which performs filtering of matching subresources. Consider the following 'collection' of resources, each of which is the current physical state of the appropriate outlet:
/restapi/relay/outlets/0/physical_state/ /restapi/relay/outlets/1/physical_state/ ... /restapi/relay/outlets/7/physical_state/
You can manipulate them, or any subset of them, as a whole, by replacing '0', '1', etc. (the outlet index) with a matrix URI selector. Here are some samples:
/restapi/relay/outlets/all;locked=true/physical_state/
/restapi/relay/outlets/=0,2,4/physical_state/
/restapi/relay/outlets/name=lamp/physical_state/
/restapi/relay/outlets/all;name=lamp,server/physical_state/
/restapi/relay/outlets/all;physical_state=true;locked=false/cycle/Matrix URI segment parsing is activated by the presence of '=',';' or ','. If you need to include them in a regular path segment (you shouldn't need to in the current data model), you need to percent-encode them.
A matrix URI segment contains one or more parts, separated by an (unescaped) ';'. The first part may be a filter policy, and must be followed by a ';' even if no other parts are present. The supported filter policies are:
All other must parts contain key=value entries, the meaning of which depends on the key:
If multiple key=value parts are specified, only the resources matching all of them are selected. For example,
/restapi/relay/outlets/=0,2,4;locked=true/physical_state/
will select physical states of locked outlets, but only among outlets #1, #3 and #5.
These seemingly complex rules allow for short URIs for common use cases.
In many cases, you don't want to see all of the output of a request. Range: bytes makes no sense for most API calls. You likely won't get a well-formed HTML or JSON document if you request a byte range. As resources are arranged in an hierarchy, the natural way to do output limiting is by referring to a depth level in this hierarchy. You can use the Range header with a dli-depth range unit to perform limiting. When a level of hierarchy is not descended into, the value is replaced by a JSON reference to it, e.g.:
The $ref parameter is a relative URI (not to be confused with a JSON pointer) to the value.
For example,
Range: dli-depth=1
will give you the immediate child resources of this resource as references, and
Range: dli-depth=0
will give you
if the resource exists.
The default is:
Range: dli-depth=infinity
which means infinite depth.
The limiting is only supported for non-HTML output formats (JSON, plain text). HTML output depth is context-dependent, but can be thought of as fixed at 1 in most use cases.